diff --git a/.github/actions/build/APP/action.yaml b/.github/actions/build/APP/action.yaml index 413616f99..7a5a5f2c1 100644 --- a/.github/actions/build/APP/action.yaml +++ b/.github/actions/build/APP/action.yaml @@ -15,7 +15,7 @@ runs: - uses: actions/checkout@v3.2.0 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.16.9' + flutter-version: '3.24.1' channel: 'stable' - name: Set up diff --git a/.github/actions/build/windows/action.yaml b/.github/actions/build/windows/action.yaml index ae575d4a6..abd9ec844 100644 --- a/.github/actions/build/windows/action.yaml +++ b/.github/actions/build/windows/action.yaml @@ -11,7 +11,7 @@ runs: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.16.9' + flutter-version: '3.24.1' channel: 'stable' - name: Windows flutter build diff --git a/.github/workflows/app-unit-test.yml b/.github/workflows/app-unit-test.yml index 28a91eeaa..8ba94a193 100644 --- a/.github/workflows/app-unit-test.yml +++ b/.github/workflows/app-unit-test.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: - flutter-version: '3.16.9' + flutter-version: '3.24.1' channel: 'stable' - name: Set up diff --git a/API/models/impact.go b/API/models/impact.go index 6e2eab4bd..dba689f5f 100644 --- a/API/models/impact.go +++ b/API/models/impact.go @@ -1,7 +1,6 @@ package models import ( - "fmt" u "p3/utils" "reflect" "strings" @@ -17,6 +16,10 @@ type ImpactFilters struct { } func GetImpact(id string, userRoles map[string]Role, filters ImpactFilters) (map[string]any, *u.Error) { + directChildren := map[string]any{} + indirectChildren := map[string]any{} + clusterRelations := map[string][]string{} // map of clusterId and list of objIds linked to that cluster + // Get target object for impact analysis target, err := GetObjectById(id, u.HIERARCHYOBJS_ENT, u.RequestFilters{}, userRoles) if err != nil { @@ -28,12 +31,13 @@ func GetImpact(id string, userRoles map[string]Role, filters ImpactFilters) (map if err != nil { return nil, err } - fmt.Println(filters) // Find relations - directChildren := map[string]any{} - indirectChildren := map[string]any{} - clusterRelations := map[string][]string{} + // Cluster associated to this target + if targetAttrs, ok := target["attributes"].(map[string]any); ok { + setClusterRelation(id, targetAttrs, clusterRelations) + } + // Direct/indirect children and associated clusters targetLevel := strings.Count(id, ".") for childId, childData := range allChildren { childAttrs := childData.(map[string]any)["attributes"].(map[string]any) @@ -44,6 +48,7 @@ func GetImpact(id string, userRoles map[string]Role, filters ImpactFilters) (map setClusterRelation(childId, childAttrs, clusterRelations) continue } + // indirect child setIndirectChildren(filters, childData.(map[string]any), childAttrs, indirectChildren, clusterRelations) } diff --git a/APP/Dockerfile b/APP/Dockerfile index c12f952e2..ac396c482 100644 --- a/APP/Dockerfile +++ b/APP/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get install -y curl git wget unzip libgconf-2-4 gdb libstdc++6 libglu1-m RUN apt-get clean # Download Flutter SDK from Github repo -RUN git clone --depth 1 --branch 3.16.9 https://github.com/flutter/flutter.git /usr/local/flutter +RUN git clone --depth 1 --branch 3.24.1 https://github.com/flutter/flutter.git /usr/local/flutter # Set flutter environment path ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}" diff --git a/APP/lib/common/api_backend.dart b/APP/lib/common/api_backend.dart index 6bd3e8950..2f4ad7f79 100644 --- a/APP/lib/common/api_backend.dart +++ b/APP/lib/common/api_backend.dart @@ -1,10 +1,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; + import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:http/http.dart' as http; -import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/models/alert.dart'; import 'package:ogree_app/models/container.dart'; import 'package:ogree_app/models/domain.dart'; @@ -14,25 +17,22 @@ import 'package:ogree_app/models/tag.dart'; import 'package:ogree_app/models/tenant.dart'; import 'package:ogree_app/models/user.dart'; import 'package:universal_html/html.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'definitions.dart'; part 'api_tenant.dart'; // Globals String apiUrl = ""; -var token = ""; +String token = ""; String tenantName = ""; bool isTenantAdmin = false; // a tenant admin can access its config page String tenantUrl = ""; // used by SuperAdmin to connect between tenant APIs -var tenantToken = ""; // used by SuperAdmin to connect between tenant APIs +String tenantToken = ""; // used by SuperAdmin to connect between tenant APIs BackendType backendType = BackendType.tenant; enum BackendType { docker, kubernetes, tenant, unavailable } // Helper Functions -getHeader(token) => { +Map getHeader(token) => { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': 'Bearer $token', @@ -40,12 +40,12 @@ getHeader(token) => { String reformatDate(String date) { // dd/MM/yyyy -> yyyy-MM-dd - List dateParts = date.split("/"); + final List dateParts = date.split("/"); return "${dateParts[2]}-${dateParts[1]}-${dateParts[0]}"; } String urlDateAppend(String dateRange) { - var ranges = dateRange.split(" - "); + final ranges = dateRange.split(" - "); String urlAppend = "&startDate=${reformatDate(ranges[0])}"; if (ranges.length > 1) { urlAppend = "$urlAppend&endDate=${reformatDate(ranges[1])}"; @@ -61,8 +61,12 @@ String wrapResponseMsg(http.Response response, {String? message}) { } // API calls -Future, Exception>> loginAPI(String email, String password, - {String userUrl = "", bool stayLoggedIn = false}) async { +Future, Exception>> loginAPI( + String email, + String password, { + String userUrl = "", + bool stayLoggedIn = false, +}) async { // Make sure it is clean tenantUrl = ""; isTenantAdmin = false; @@ -75,20 +79,21 @@ Future, Exception>> loginAPI(String email, String password, } else { apiUrl = dotenv.get('API_URL', fallback: 'http://localhost:3001'); } - print("API login ogree $apiUrl"); try { - Uri url = Uri.parse('$apiUrl/api/login'); - final response = await http.post(url, - body: json.encode({ - 'email': email, - 'password': password, - 'stayLoggedIn': stayLoggedIn - })); + final Uri url = Uri.parse('$apiUrl/api/login'); + final response = await http.post( + url, + body: json.encode({ + 'email': email, + 'password': password, + 'stayLoggedIn': stayLoggedIn, + }), + ); // Handle response Map data = json.decode(response.body); if (response.statusCode == 200) { - data = (Map.from(data["account"])); + data = Map.from(data["account"]); token = data["token"]!; if (data["isTenant"] == null && data["roles"][allDomainsTag] == "manager") { @@ -110,14 +115,14 @@ Future, Exception>> loginAPI(String email, String password, } } -Future> fetchApiVersion(String urlApi, - {http.Client? client}) async { - print("API get TenantName"); +Future> fetchApiVersion( + String urlApi, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$urlApi/api/version'); + final Uri url = Uri.parse('$urlApi/api/version'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { Map data = json.decode(response.body); if (data["isKubernetes"] != null) { @@ -129,11 +134,10 @@ Future> fetchApiVersion(String urlApi, return const Success(BackendType.docker); } } else { - data = (Map.from(data["data"])); + data = Map.from(data["data"]); if (data.isNotEmpty || data["Customer"] != null) { tenantName = data["Customer"]; backendType = BackendType.tenant; - print(tenantName); return const Success(BackendType.tenant); } else { backendType = BackendType.unavailable; @@ -152,48 +156,50 @@ Future> fetchApiVersion(String urlApi, } Future> changeUserPassword( - String currentPassword, newPassword) async { - print("API change password"); + String currentPassword, + newPassword, +) async { try { - Uri url = Uri.parse('$apiUrl/api/users/password/change'); - final response = await http.post(url, - body: json.encode({ - 'currentPassword': currentPassword, - 'newPassword': newPassword - }), - headers: getHeader(token)); - print(response.statusCode); - Map data = json.decode(response.body); + final Uri url = Uri.parse('$apiUrl/api/users/password/change'); + final response = await http.post( + url, + body: json.encode({ + 'currentPassword': currentPassword, + 'newPassword': newPassword, + }), + headers: getHeader(token), + ); + final Map data = json.decode(response.body); if (response.statusCode == 200) { token = data["token"]!; return const Success(null); } else { - return Failure(Exception(("Error: ${data["message"]}"))); + return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { return Failure(e); } } -Future> userForgotPassword(String email, - {String userUrl = ""}) async { - print("API forgot password"); +Future> userForgotPassword( + String email, { + String userUrl = "", +}) async { if (userUrl != "") { apiUrl = userUrl; } else { apiUrl = dotenv.get('API_URL', fallback: 'http://localhost:3001'); } try { - Uri url = Uri.parse('$apiUrl/api/users/password/forgot'); + final Uri url = Uri.parse('$apiUrl/api/users/password/forgot'); final response = await http.post( url, body: json.encode({'email': email}), ); - print(response.statusCode); if (response.statusCode == 200) { return const Success(null); } else { - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -202,16 +208,17 @@ Future> userForgotPassword(String email, } Future> userResetPassword( - String password, String resetToken, - {String userUrl = ""}) async { - print("API reset password"); + String password, + String resetToken, { + String userUrl = "", +}) async { if (userUrl != "") { apiUrl = userUrl; } else { apiUrl = dotenv.get('API_URL', fallback: 'http://localhost:3001'); } try { - Uri url = Uri.parse('$apiUrl/api/users/password/reset'); + final Uri url = Uri.parse('$apiUrl/api/users/password/reset'); final response = await http.post( url, body: json.encode({'newPassword': password}), @@ -220,7 +227,7 @@ Future> userResetPassword( if (response.statusCode == 200) { return const Success(null); } else { - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -228,12 +235,11 @@ Future> userResetPassword( } } -Future>>, Exception>> fetchObjectsTree( - {Namespace namespace = Namespace.Physical, - String dateRange = "", - bool isTenantMode = false}) async { - print("API get tree: NS=$namespace"); - +Future>>, Exception>> fetchObjectsTree({ + Namespace namespace = Namespace.Physical, + String dateRange = "", + bool isTenantMode = false, +}) async { // Define URL and token to use String localUrl = '/api/hierarchy'; String localToken = token; @@ -244,7 +250,7 @@ Future>>, Exception>> fetchObjectsTree( localUrl = apiUrl + localUrl; } // Add filters, if any - String namespaceStr = namespace.name.toLowerCase(); + final String namespaceStr = namespace.name.toLowerCase(); if (namespace == Namespace.Physical || namespace == Namespace.Logical) { localUrl = '$localUrl?namespace=$namespaceStr&withcategories=true'; } else { @@ -256,33 +262,29 @@ Future>>, Exception>> fetchObjectsTree( // Request try { - Uri url = Uri.parse(localUrl); + final Uri url = Uri.parse(localUrl); final response = await http.get(url, headers: getHeader(localToken)); - print(response.statusCode); if (response.statusCode == 200) { // Convert dynamic Map to expected type Map data = json.decode(response.body); - data = (Map.from(data["data"])); - Map> converted = {}; - Map> converted2 = {}; - Map> tree = {}; - Map> categories = {}; - for (var item in data.keys) { - converted[item.toString()] = Map.from(data[item]); + data = Map.from(data["data"]); + final Map> converted = {}; + final Map> converted2 = {}; + final Map> tree = {}; + final Map> categories = {}; + for (final item in data.keys) { + converted[item] = Map.from(data[item]); } - for (var item in converted["tree"]!.keys) { - converted2[item.toString()] = - Map.from(converted["tree"]![item]!); + for (final item in converted["tree"]!.keys) { + converted2[item] = Map.from(converted["tree"]![item]!); } - for (var item in converted2[namespaceStr]!.keys) { - tree[item.toString()] = - List.from(converted2[namespaceStr]![item]); + for (final item in converted2[namespaceStr]!.keys) { + tree[item] = List.from(converted2[namespaceStr]![item]); } // Namespace adaptations if (namespace == Namespace.Physical || namespace == Namespace.Logical) { - for (var item in converted["categories"]!.keys) { - categories[item.toString()] = - List.from(converted["categories"]![item]); + for (final item in converted["categories"]!.keys) { + categories[item] = List.from(converted["categories"]![item]); } if (namespace == Namespace.Physical) { if (tree["*stray_object"] != null) { @@ -295,61 +297,61 @@ Future>>, Exception>> fetchObjectsTree( return Success([tree, categories]); } else { return Failure( - Exception('${response.statusCode}: Failed to load objects')); + Exception('${response.statusCode}: Failed to load objects'), + ); } } on Exception catch (e) { - print(e.toString()); return Failure(e); } } Future>, Exception>> fetchAttributes() async { - print("API get Attrs"); try { - Uri url = Uri.parse('$apiUrl/api/hierarchy/attributes'); + final Uri url = Uri.parse('$apiUrl/api/hierarchy/attributes'); final response = await http.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { // Convert dynamic Map to expected type Map data = json.decode(response.body); - data = (Map.from(data["data"])); - Map> converted = {}; - for (var item in data.keys) { - converted[item.toString()] = Map.from(data[item]); + data = Map.from(data["data"]); + final Map> converted = {}; + for (final item in data.keys) { + converted[item] = Map.from(data[item]); } return Success(converted); } else { return Failure( - Exception('${response.statusCode}: Failed to load objects')); + Exception('${response.statusCode}: Failed to load objects'), + ); } } on Exception catch (e) { return Failure(e); } } -Future, Exception>> fetchProjects(String userEmail, - {http.Client? client}) async { - print("API get Projects"); +Future, Exception>> fetchProjects( + String userEmail, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/projects?user=$userEmail'); + final Uri url = Uri.parse('$apiUrl/api/projects?user=$userEmail'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { - print(response); - print(response.body); // Convert dynamic Map to expected type Map data = json.decode(response.body); - data = (Map.from(data["data"])); - List projects = []; - for (var project in data["projects"]) { + data = Map.from(data["data"]); + final List projects = []; + for (final project in data["projects"]) { projects.add(Project.fromMap(project)); } return Success(projects); } else { - return Failure(Exception( - wrapResponseMsg(response, message: 'Failed to load objects'))); + return Failure( + Exception( + wrapResponseMsg(response, message: 'Failed to load objects'), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -357,12 +359,10 @@ Future, Exception>> fetchProjects(String userEmail, } Future> modifyProject(Project project) async { - print("API modify Projects"); try { - Uri url = Uri.parse('$apiUrl/api/projects/${project.id}'); + final Uri url = Uri.parse('$apiUrl/api/projects/${project.id}'); final response = await http.put(url, body: project.toJson(), headers: getHeader(token)); - print(response); if (response.statusCode == 200) { return const Success(null); } else { @@ -375,12 +375,10 @@ Future> modifyProject(Project project) async { } Future> createProject(Project project) async { - print("API create Projects"); try { - Uri url = Uri.parse('$apiUrl/api/projects'); + final Uri url = Uri.parse('$apiUrl/api/projects'); final response = await http.post(url, body: project.toJson(), headers: getHeader(token)); - print(response); if (response.statusCode == 200) { return const Success(null); } else { @@ -393,12 +391,10 @@ Future> createProject(Project project) async { } Future> createAlert(Alert alert) async { - print("API create Projects"); try { - Uri url = Uri.parse('$apiUrl/api/alerts'); + final Uri url = Uri.parse('$apiUrl/api/alerts'); final response = await http.post(url, body: alert.toJson(), headers: getHeader(token)); - print(response); if (response.statusCode == 200) { return const Success(null); } else { @@ -410,50 +406,51 @@ Future> createAlert(Alert alert) async { } } -Future, Exception>> fetchAlerts( - {http.Client? client}) async { - print("API get Alerts"); +Future, Exception>> fetchAlerts({ + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/alerts'); + final Uri url = Uri.parse('$apiUrl/api/alerts'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { - print(response); - print(response.body); // Convert dynamic Map to expected type Map data = json.decode(response.body); - data = (Map.from(data["data"])); - List alerts = []; - for (var alert in data["alerts"]) { + data = Map.from(data["data"]); + final List alerts = []; + for (final alert in data["alerts"]) { alerts.add(Alert.fromMap(alert)); } return Success(alerts); } else { - return Failure(Exception( - wrapResponseMsg(response, message: 'Failed to load objects'))); + return Failure( + Exception( + wrapResponseMsg(response, message: 'Failed to load objects'), + ), + ); } } on Exception catch (e) { return Failure(e); } } -Future> fetchAlert(String id, - {http.Client? client}) async { - print("API get Alert by id"); +Future> fetchAlert( + String id, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/alerts/$id'); + final Uri url = Uri.parse('$apiUrl/api/alerts/$id'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { // Convert dynamic Map to expected type Map data = json.decode(response.body); - data = (Map.from(data["data"])); + data = Map.from(data["data"]); return Success(Alert.fromMap(data)); } else { return Failure( - Exception(wrapResponseMsg(response, message: 'Failed to get alert'))); + Exception(wrapResponseMsg(response, message: 'Failed to get alert')), + ); } } on Exception catch (e) { return Failure(e); @@ -461,22 +458,24 @@ Future> fetchAlert(String id, } Future> createObject( - Map object, String category) async { - print("API create Object"); + Map object, + String category, +) async { try { - Uri url = Uri.parse('$apiUrl/api/${category}s'); - final response = await http.post(url, - body: json.encode(object), headers: getHeader(token)); - print(response); + final Uri url = Uri.parse('$apiUrl/api/${category}s'); + final response = await http.post( + url, + body: json.encode(object), + headers: getHeader(token), + ); if (response.statusCode == 200 || response.statusCode == 201) { return const Success(null); } else { final Map data = json.decode(response.body); if (data["errors"] != null) { - var errors = List.from(data["errors"]); - print(errors.toString()); + final errors = List.from(data["errors"]); String errStr = ""; - for (var err in errors) { + for (final err in errors) { errStr = "$errStr\n$err"; } return Failure(Exception(errStr)); @@ -489,15 +488,16 @@ Future> createObject( } Future, Exception>> fetchObject( - String id, AppLocalizations localeMsg, - {String idKey = "id"}) async { - print("API fetch Object"); + String id, + AppLocalizations localeMsg, { + String idKey = "id", +}) async { try { - Uri url = Uri.parse('$apiUrl/api/objects?$idKey=$id'); + final Uri url = Uri.parse('$apiUrl/api/objects?$idKey=$id'); final response = await http.get(url, headers: getHeader(token)); if (response.statusCode == 200 || response.statusCode == 201) { - Map data = json.decode(response.body); - var list = List>.from(data["data"]); + final Map data = json.decode(response.body); + final list = List>.from(data["data"]); if (list.isEmpty) { return Failure(Exception(localeMsg.noObjectsFound)); } @@ -512,13 +512,13 @@ Future, Exception>> fetchObject( } Future, Exception>> fetchObjectChildren( - String id) async { - print("API fetch Object /all"); + String id, +) async { try { - Uri url = Uri.parse('$apiUrl/api/hierarchy_objects/$id/all?limit=2'); + final Uri url = Uri.parse('$apiUrl/api/hierarchy_objects/$id/all?limit=2'); final response = await http.get(url, headers: getHeader(token)); if (response.statusCode == 200 || response.statusCode == 201) { - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Success(Map.from(data["data"])); } else { final Map data = json.decode(response.body); @@ -529,9 +529,12 @@ Future, Exception>> fetchObjectChildren( } } -Future, Exception>> fetchObjectImpact(String id, - List categories, List ptypes, List vtypes) async { - print("API fetch Object Impact"); +Future, Exception>> fetchObjectImpact( + String id, + List categories, + List ptypes, + List vtypes, +) async { String queryParam = listToQueryParam("", categories, "categories"); queryParam = listToQueryParam(queryParam, ptypes, "ptypes"); queryParam = listToQueryParam(queryParam, vtypes, "vtypes"); @@ -540,10 +543,10 @@ Future, Exception>> fetchObjectImpact(String id, if (queryParam.isNotEmpty) { urlStr = "$urlStr?$queryParam"; } - Uri url = Uri.parse(urlStr); + final Uri url = Uri.parse(urlStr); final response = await http.get(url, headers: getHeader(token)); if (response.statusCode == 200 || response.statusCode == 201) { - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Success(Map.from(data["data"])); } else { final Map data = json.decode(response.body); @@ -556,7 +559,7 @@ Future, Exception>> fetchObjectImpact(String id, String listToQueryParam(String currentParam, List list, String key) { String param = currentParam; - for (String item in list) { + for (final String item in list) { if (param.isNotEmpty) { param = "$param&"; } @@ -566,17 +569,22 @@ String listToQueryParam(String currentParam, List list, String key) { } Future>, Exception>> fetchWithComplexFilter( - String filter, Namespace namespace, AppLocalizations localeMsg) async { - print("API fetch Complex Filter"); + String filter, + Namespace namespace, + AppLocalizations localeMsg, +) async { try { - Uri url = Uri.parse( - '$apiUrl/api/objects/search?namespace=${namespace.name.toLowerCase()}'); - final response = await http.post(url, - body: json.encode({'filter': filter}), - headers: getHeader(token)); + final Uri url = Uri.parse( + '$apiUrl/api/objects/search?namespace=${namespace.name.toLowerCase()}', + ); + final response = await http.post( + url, + body: json.encode({'filter': filter}), + headers: getHeader(token), + ); if (response.statusCode == 200 || response.statusCode == 201) { - Map data = json.decode(response.body); - var list = List>.from(data["data"]); + final Map data = json.decode(response.body); + final list = List>.from(data["data"]); if (list.isEmpty) { return Failure(Exception(localeMsg.noObjectsFound)); } @@ -591,22 +599,25 @@ Future>, Exception>> fetchWithComplexFilter( } Future> updateObject( - String objId, String category, Map object) async { - print("API update object"); + String objId, + String category, + Map object, +) async { try { - Uri url = Uri.parse('$apiUrl/api/${category}s/$objId'); - final response = await http.put(url, - body: json.encode(object), headers: getHeader(token)); - print(response.statusCode); + final Uri url = Uri.parse('$apiUrl/api/${category}s/$objId'); + final response = await http.put( + url, + body: json.encode(object), + headers: getHeader(token), + ); if (response.statusCode == 200) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); if (data["errors"] != null) { - var errors = List.from(data["errors"]); - print(errors.toString()); + final errors = List.from(data["errors"]); String errStr = ""; - for (var err in errors) { + for (final err in errors) { errStr = "$errStr\n$err"; } return Failure(Exception(errStr)); @@ -618,18 +629,19 @@ Future> updateObject( } } -Future> deleteObject(String objId, String category, - {http.Client? client}) async { - print("API delete object $category"); +Future> deleteObject( + String objId, + String category, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/${category}s/$objId'); + final Uri url = Uri.parse('$apiUrl/api/${category}s/$objId'); final response = await client.delete(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode >= 200 && response.statusCode < 300) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -638,13 +650,13 @@ Future> deleteObject(String objId, String category, } Future> createTemplate( - Uint8List file, String category) async { - print("API create template $category"); + Uint8List file, + String category, +) async { try { - Uri url = Uri.parse('$apiUrl/api/${category}s'); + final Uri url = Uri.parse('$apiUrl/api/${category}s'); final response = await http.post(url, body: file, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200 || response.statusCode == 201) { return const Success(null); } else { @@ -657,20 +669,22 @@ Future> createTemplate( } Future, Exception>> fetchGroupContent( - String id, category, AppLocalizations localeMsg) async { - print("API fetch GR content"); + String id, + category, + AppLocalizations localeMsg, +) async { try { - Uri url = Uri.parse('$apiUrl/api/objects?id=$id.*&category=$category'); + final Uri url = + Uri.parse('$apiUrl/api/objects?id=$id.*&category=$category'); final response = await http.get(url, headers: getHeader(token)); if (response.statusCode == 200 || response.statusCode == 201) { - Map data = json.decode(response.body); - var list = List>.from(data["data"]); - print(list); + final Map data = json.decode(response.body); + final list = List>.from(data["data"]); if (list.isEmpty) { return Failure(Exception(localeMsg.noObjectsFound)); } else { - List content = []; - for (var item in list) { + final List content = []; + for (final item in list) { content.add(item["name"].toString()); } return Success(content); @@ -686,21 +700,19 @@ Future, Exception>> fetchGroupContent( Future, List), Exception>> fetchApplications({http.Client? client}) async { - print("API get Apps"); client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/apps'); + final Uri url = Uri.parse('$apiUrl/api/apps'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { - Map data = json.decode(response.body); - List tenants = []; - for (var project in data["tenants"]) { + final Map data = json.decode(response.body); + final List tenants = []; + for (final project in data["tenants"]) { tenants.add(Tenant.fromMap(project)); } - List containers = []; - for (var tool in data["tools"]) { - var container = DockerContainer.fromMap(tool); + final List containers = []; + for (final tool in data["tools"]) { + final container = DockerContainer.fromMap(tool); if (container.ports.isNotEmpty) { container.ports = "http://${container.ports.split(",").last.split("-").first.trim()}"; @@ -711,8 +723,11 @@ Future, List), Exception>> } return Success((tenants, containers)); } else { - return Failure(Exception( - wrapResponseMsg(response, message: 'Failed to load objects'))); + return Failure( + Exception( + wrapResponseMsg(response, message: 'Failed to load objects'), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -720,7 +735,6 @@ Future, List), Exception>> } Future, Exception>> createTenant(Tenant tenant) async { - print("API create Tenants"); try { return connectStream('POST', '$apiUrl/api/tenants', tenant.toJson()); } on Exception catch (e) { @@ -729,17 +743,18 @@ Future, Exception>> createTenant(Tenant tenant) async { } Future, Exception>> updateTenant(Tenant tenant) async { - print("API update Tenants"); try { return connectStream( - 'PUT', '$apiUrl/api/tenants/${tenant.name}', tenant.toJson()); + 'PUT', + '$apiUrl/api/tenants/${tenant.name}', + tenant.toJson(), + ); } on Exception catch (e) { return Failure(e); } } Future, Exception>> stopTenant(String tenantName) async { - print("API stop Tenants"); try { return connectStream('POST', '$apiUrl/api/tenants/$tenantName/stop', ""); } on Exception catch (e) { @@ -748,7 +763,6 @@ Future, Exception>> stopTenant(String tenantName) async { } Future, Exception>> startTenant(String tenantName) async { - print("API start Tenants"); try { return connectStream('POST', '$apiUrl/api/tenants/$tenantName/start', ""); } on Exception catch (e) { @@ -757,7 +771,10 @@ Future, Exception>> startTenant(String tenantName) async { } Future, Exception>> connectStream( - String method, String urlStr, String body) async { + String method, + String urlStr, + String body, +) async { if (kIsWeb) { // Special SSE handling for web int progress = 0; @@ -785,37 +802,47 @@ Future, Exception>> connectStream( return Success(streamController.stream); } else { // SSE handle for other builds - Uri url = Uri.parse(urlStr); + final Uri url = Uri.parse(urlStr); final client = http.Client(); - var request = http.Request(method, url)..headers.addAll(getHeader(token)); + final request = http.Request(method, url)..headers.addAll(getHeader(token)); request.body = body; final response = await client.send(request); if (response.statusCode == 200) { return Success(response.stream.toStringStream()); } else { - return Failure(Exception(wrapResponseMsg( - http.Response("", response.statusCode), - message: 'Error processing tenant'))); + return Failure( + Exception( + wrapResponseMsg( + http.Response("", response.statusCode), + message: 'Error processing tenant', + ), + ), + ); } } } Future> uploadImage( - PlatformFile image, String tenant) async { - print("API upload Tenant logo"); + PlatformFile image, + String tenant, +) async { try { - Uri url = Uri.parse('$apiUrl/api/tenants/$tenant/logo'); - var request = http.MultipartRequest("POST", url); + final Uri url = Uri.parse('$apiUrl/api/tenants/$tenant/logo'); + final request = http.MultipartRequest("POST", url); request.headers.addAll(getHeader(token)); - request.files.add(http.MultipartFile.fromBytes("file", image.bytes!, - filename: image.name)); + request.files.add( + http.MultipartFile.fromBytes( + "file", + image.bytes!, + filename: image.name, + ), + ); - var response = await request.send(); - print(response.statusCode); + final response = await request.send(); if (response.statusCode == 200) { return const Success(null); } else { - String errorMsg = await response.stream.bytesToString(); + final String errorMsg = await response.stream.bytesToString(); return Failure(Exception(errorMsg)); } } on Exception catch (e) { @@ -824,51 +851,64 @@ Future> uploadImage( } Future> backupTenantDB( - String tenantName, String password, bool shouldDownload) async { - print("API backup Tenants"); + String tenantName, + String password, + bool shouldDownload, +) async { try { - Uri url = Uri.parse('$apiUrl/api/tenants/$tenantName/backup'); - final response = await http.post(url, - body: json.encode({ - 'password': password, - 'shouldDownload': shouldDownload, - }), - headers: getHeader(token)); - print(response); + final Uri url = Uri.parse('$apiUrl/api/tenants/$tenantName/backup'); + final response = await http.post( + url, + body: json.encode({ + 'password': password, + 'shouldDownload': shouldDownload, + }), + headers: getHeader(token), + ); if (response.statusCode == 200) { if (shouldDownload) { return Success(response.bodyBytes); } else { - return Success(response.body.toString()); + return Success(response.body); } } else { - String data = json.decode(response.body); - return Failure(Exception( - wrapResponseMsg(response, message: "Error backing up tenant $data"))); + final String data = json.decode(response.body); + return Failure( + Exception( + wrapResponseMsg(response, message: "Error backing up tenant $data"), + ), + ); } } on Exception catch (e) { return Failure(e); } } -Future> restoreTenantDB(PlatformFile backup, - String tenantName, String password, bool shouldDrop) async { - print("API upload Tenant restore"); +Future> restoreTenantDB( + PlatformFile backup, + String tenantName, + String password, + bool shouldDrop, +) async { try { - Uri url = Uri.parse('$apiUrl/api/tenants/$tenantName/restore'); - var request = http.MultipartRequest("POST", url); + final Uri url = Uri.parse('$apiUrl/api/tenants/$tenantName/restore'); + final request = http.MultipartRequest("POST", url); request.fields['password'] = password; request.fields['shouldDrop'] = shouldDrop.toString(); request.headers.addAll(getHeader(token)); - request.files.add(http.MultipartFile.fromBytes("file", backup.bytes!, - filename: backup.name)); - var response = await request.send(); - print(response.statusCode); + request.files.add( + http.MultipartFile.fromBytes( + "file", + backup.bytes!, + filename: backup.name, + ), + ); + final response = await request.send(); if (response.statusCode == 200) { - String msg = await response.stream.bytesToString(); + final String msg = await response.stream.bytesToString(); return Success(msg); } else { - String errorMsg = await response.stream.bytesToString(); + final String errorMsg = await response.stream.bytesToString(); return Failure(Exception(errorMsg)); } } on Exception catch (e) { @@ -877,37 +917,51 @@ Future> restoreTenantDB(PlatformFile backup, } Future> createBackendServer( - Map newBackend) async { - print("API create Back Server"); + Map newBackend, +) async { try { - Uri url = Uri.parse('$apiUrl/api/servers'); - final response = await http.post(url, - body: json.encode(newBackend), headers: getHeader(token)); - print(response); + final Uri url = Uri.parse('$apiUrl/api/servers'); + final response = await http.post( + url, + body: json.encode(newBackend), + headers: getHeader(token), + ); if (response.statusCode == 200) { return const Success(null); } else { - return Failure(Exception(wrapResponseMsg(response, - message: "Error creating backend: ${response.body}"))); + return Failure( + Exception( + wrapResponseMsg( + response, + message: "Error creating backend: ${response.body}", + ), + ), + ); } } on Exception catch (e) { return Failure(e); } } -Future> deleteTenant(String objName, - {http.Client? client}) async { - print("API delete Tenant"); +Future> deleteTenant( + String objName, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/tenants/$objName'); + final Uri url = Uri.parse('$apiUrl/api/tenants/$objName'); final response = await client.delete(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { return const Success(null); } else { - return Failure(Exception(wrapResponseMsg(response, - message: "Error deleting tenant: ${response.body}"))); + return Failure( + Exception( + wrapResponseMsg( + response, + message: "Error deleting tenant: ${response.body}", + ), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -915,45 +969,50 @@ Future> deleteTenant(String objName, } Future, Exception>> fetchTenantDockerInfo( - String tenantName, - {http.Client? client}) async { - print("API get Tenant Docker Info"); + String tenantName, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/tenants/$tenantName'); + final Uri url = Uri.parse('$apiUrl/api/tenants/$tenantName'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { - List data = json.decode(response.body); - List converted = []; - for (var item in data) { + final List data = json.decode(response.body); + final List converted = []; + for (final item in data) { converted.add(DockerContainer.fromMap(item)); } return Success(converted); } else { - print('${response.statusCode}: ${response.body}'); - return Failure(Exception(wrapResponseMsg(response, - message: "Error backing up tenant ${response.body}"))); + return Failure( + Exception( + wrapResponseMsg( + response, + message: "Error backing up tenant ${response.body}", + ), + ), + ); } } on Exception catch (e) { return Failure(e); } } -Future> fetchContainerLogs(String name, - {http.Client? client}) async { - print("API get Container Logs $name"); +Future> fetchContainerLogs( + String name, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$apiUrl/api/containers/$name'); + final Uri url = Uri.parse('$apiUrl/api/containers/$name'); final response = await client.get(url, headers: getHeader(token)); - print(response.statusCode); if (response.statusCode == 200) { - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Success(data["logs"].toString()); } else { return Failure( - Exception(wrapResponseMsg(response, message: "Failed to load logs"))); + Exception(wrapResponseMsg(response, message: "Failed to load logs")), + ); } } on Exception catch (e) { return Failure(e); @@ -961,18 +1020,19 @@ Future> fetchContainerLogs(String name, } Future> createNetbox(Nbox netbox) async { - print("API create Netbox"); try { - Uri url = Uri.parse('$apiUrl/api/tools/netbox'); + final Uri url = Uri.parse('$apiUrl/api/tools/netbox'); final response = await http.post(url, body: netbox.toJson(), headers: getHeader(token)); - print(response); if (response.statusCode == 200) { return const Success(null); } else { - String data = json.decode(response.body); - return Failure(Exception( - wrapResponseMsg(response, message: "Error creating netbox $data"))); + final String data = json.decode(response.body); + return Failure( + Exception( + wrapResponseMsg(response, message: "Error creating netbox $data"), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -980,18 +1040,22 @@ Future> createNetbox(Nbox netbox) async { } Future> createNautobot(Nbox nautobot) async { - print("API create nautobot"); try { - Uri url = Uri.parse('$apiUrl/api/tools/nautobot'); - final response = await http.post(url, - body: nautobot.toJson(), headers: getHeader(token)); - print(response); + final Uri url = Uri.parse('$apiUrl/api/tools/nautobot'); + final response = await http.post( + url, + body: nautobot.toJson(), + headers: getHeader(token), + ); if (response.statusCode == 200) { return const Success(null); } else { - String data = json.decode(response.body); - return Failure(Exception( - wrapResponseMsg(response, message: "Error creating nautobot $data"))); + final String data = json.decode(response.body); + return Failure( + Exception( + wrapResponseMsg(response, message: "Error creating nautobot $data"), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -999,23 +1063,28 @@ Future> createNautobot(Nbox nautobot) async { } Future> createOpenDcim( - String dcimPort, adminerPort) async { - print("API create OpenDCIM"); + String dcimPort, + adminerPort, +) async { try { - Uri url = Uri.parse('$apiUrl/api/tools/opendcim'); - final response = await http.post(url, - body: json.encode({ - 'dcimPort': dcimPort, - 'adminerPort': adminerPort, - }), - headers: getHeader(token)); - print(response); + final Uri url = Uri.parse('$apiUrl/api/tools/opendcim'); + final response = await http.post( + url, + body: json.encode({ + 'dcimPort': dcimPort, + 'adminerPort': adminerPort, + }), + headers: getHeader(token), + ); if (response.statusCode == 200) { return const Success(null); } else { - String data = json.decode(response.body); - return Failure(Exception( - wrapResponseMsg(response, message: "Error creating netbox $data"))); + final String data = json.decode(response.body); + return Failure( + Exception( + wrapResponseMsg(response, message: "Error creating netbox $data"), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -1023,17 +1092,21 @@ Future> createOpenDcim( } Future> deleteTool(String tool) async { - print("API delete Tool"); try { - Uri url = Uri.parse('$apiUrl/api/tools/$tool'); + final Uri url = Uri.parse('$apiUrl/api/tools/$tool'); final response = await http.delete(url, headers: getHeader(token)); - print(response); if (response.statusCode == 200) { return const Success(null); } else { - String data = json.decode(response.body); - return Failure(Exception(wrapResponseMsg(response, - message: "Error creating application $data"))); + final String data = json.decode(response.body); + return Failure( + Exception( + wrapResponseMsg( + response, + message: "Error creating application $data", + ), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -1041,22 +1114,26 @@ Future> deleteTool(String tool) async { } Future> uploadNetboxDump(PlatformFile file) async { - print("API upload netbox dump"); try { - Uri url = Uri.parse('$apiUrl/api/tools/netbox/dump'); - var request = http.MultipartRequest("POST", url); + final Uri url = Uri.parse('$apiUrl/api/tools/netbox/dump'); + final request = http.MultipartRequest("POST", url); request.headers.addAll(getHeader(token)); request.files.add( - http.MultipartFile.fromBytes("file", file.bytes!, filename: file.name)); - var response = await request.send(); - print(response.statusCode); + http.MultipartFile.fromBytes("file", file.bytes!, filename: file.name), + ); + final response = await request.send(); if (response.statusCode == 200) { return const Success(null); } else { - String errorMsg = await response.stream.bytesToString(); - return Failure(Exception(wrapResponseMsg( - http.Response(errorMsg, response.statusCode), - message: errorMsg))); + final String errorMsg = await response.stream.bytesToString(); + return Failure( + Exception( + wrapResponseMsg( + http.Response(errorMsg, response.statusCode), + message: errorMsg, + ), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -1064,17 +1141,21 @@ Future> uploadNetboxDump(PlatformFile file) async { } Future> importNetboxDump() async { - print("API import dump Netbox"); try { - Uri url = Uri.parse('$apiUrl/api/tools/netbox/import'); + final Uri url = Uri.parse('$apiUrl/api/tools/netbox/import'); final response = await http.post(url, headers: getHeader(token)); - print(response); if (response.statusCode == 200) { return const Success(null); } else { - String data = json.decode(response.body); - return Failure(Exception(wrapResponseMsg(response, - message: "Error importing netbox dump: $data"))); + final String data = json.decode(response.body); + return Failure( + Exception( + wrapResponseMsg( + response, + message: "Error importing netbox dump: $data", + ), + ), + ); } } on Exception catch (e) { return Failure(e); @@ -1082,12 +1163,11 @@ Future> importNetboxDump() async { } Future, Exception>> fetchSchema(String id) async { - print("API fetch Schema $id"); try { - Uri url = Uri.parse('$apiUrl/api/schemas/$id'); + final Uri url = Uri.parse('$apiUrl/api/schemas/$id'); final response = await http.get(url, headers: getHeader(token)); if (response.statusCode == 200) { - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Success(data); } else { final Map data = json.decode(response.body); diff --git a/APP/lib/common/api_tenant.dart b/APP/lib/common/api_tenant.dart index 9f9b9d1be..231d6ecc9 100644 --- a/APP/lib/common/api_tenant.dart +++ b/APP/lib/common/api_tenant.dart @@ -3,21 +3,23 @@ part of 'api_backend.dart'; // This API is used by SuperAdmin mode to connect to multiple tenant APIs // still being connected to the SuperAdmin backend Future> loginAPITenant( - String email, String password, String userUrl) async { - print("API login to ogree-api $userUrl"); + String email, + String password, + String userUrl, +) async { try { - Uri url = Uri.parse('$userUrl/api/login'); - final response = await http.post(url, - body: json - .encode({'email': email, 'password': password})); + final Uri url = Uri.parse('$userUrl/api/login'); + final response = await http.post( + url, + body: json.encode({'email': email, 'password': password}), + ); if (response.statusCode == 200) { Map data = json.decode(response.body); - data = (Map.from(data["account"])); + data = Map.from(data["account"]); tenantUrl = userUrl; tenantToken = data["token"]!; return const Success(null); } else { - print(response.statusCode); return Failure(Exception()); } } on Exception catch (e) { @@ -25,17 +27,15 @@ Future> loginAPITenant( } } -Future, Exception>> fetchTenantStats( - {http.Client? client}) async { - print("API get Tenant Stats $tenantUrl"); +Future, Exception>> fetchTenantStats({ + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$tenantUrl/api/stats'); + final Uri url = Uri.parse('$tenantUrl/api/stats'); final response = await client.get(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode == 200) { - print(response.body); - Map data = json.decode(response.body); + final Map data = json.decode(response.body); return Success(data); } else { return Failure(Exception('${response.statusCode}: Failed to load stats')); @@ -45,46 +45,40 @@ Future, Exception>> fetchTenantStats( } } -Future, Exception>> fetchTenantApiVersion( - {http.Client? client}) async { - print("API get Tenant Version $tenantUrl"); +Future, Exception>> fetchTenantApiVersion({ + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$tenantUrl/api/version'); + final Uri url = Uri.parse('$tenantUrl/api/version'); final response = await client.get(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode == 200) { - print(response.body); Map data = json.decode(response.body); - data = (Map.from(data["data"])); + data = Map.from(data["data"]); return Success(data); } else { return Failure( - Exception('${response.statusCode}: Failed to load version')); + Exception('${response.statusCode}: Failed to load version'), + ); } } on Exception catch (e) { return Failure(e); } } -Future, Exception>> fetchApiUsers( - {http.Client? client}) async { - print("API get users $tenantUrl"); +Future, Exception>> fetchApiUsers({ + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$tenantUrl/api/users'); + final Uri url = Uri.parse('$tenantUrl/api/users'); final response = await client.get(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode == 200) { - print(response.body); - Map data = json.decode(response.body); - print(data["data"]); - print(data["data"].runtimeType); - List users = []; - for (var user in List>.from(data["data"])) { + final Map data = json.decode(response.body); + final List users = []; + for (final user in List>.from(data["data"])) { users.add(User.fromMap(user)); } - print(users); return Success(users); } else { return Failure(Exception('${response.statusCode}: Failed to load users')); @@ -95,16 +89,17 @@ Future, Exception>> fetchApiUsers( } Future> createUser(User user) async { - print("API create User"); try { - Uri url = Uri.parse('$tenantUrl/api/users'); - final response = await http.post(url, - body: user.toJson(), headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/users'); + final response = await http.post( + url, + body: user.toJson(), + headers: getHeader(tenantToken), + ); if (response.statusCode == 201) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -113,20 +108,22 @@ Future> createUser(User user) async { } Future> modifyUser( - String id, Map roles) async { - print("API modify User"); + String id, + Map roles, +) async { try { - Uri url = Uri.parse('$tenantUrl/api/users/$id'); - final response = await http.patch(url, - body: json.encode({ - 'roles': roles, - }), - headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/users/$id'); + final response = await http.patch( + url, + body: json.encode({ + 'roles': roles, + }), + headers: getHeader(tenantToken), + ); if (response.statusCode == 200) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -135,16 +132,17 @@ Future> modifyUser( } Future> createDomain(Domain domain) async { - print("API create Domain"); try { - Uri url = Uri.parse('$tenantUrl/api/domains'); - final response = await http.post(url, - body: domain.toJson(), headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/domains'); + final response = await http.post( + url, + body: domain.toJson(), + headers: getHeader(tenantToken), + ); if (response.statusCode == 201) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -153,19 +151,18 @@ Future> createDomain(Domain domain) async { } Future> createBulkFile( - Uint8List file, String type) async { - print("API create bulk $type"); + Uint8List file, + String type, +) async { try { - Uri url = Uri.parse('$tenantUrl/api/$type/bulk'); + final Uri url = Uri.parse('$tenantUrl/api/$type/bulk'); final response = await http.post(url, body: file, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode == 200) { - var data = json.decode(response.body); - print(data.toString()); + final data = json.decode(response.body); return Success(data.toString()); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -173,18 +170,19 @@ Future> createBulkFile( } } -Future> removeObject(String objName, String objType, - {http.Client? client}) async { - print("API delete object $objType"); +Future> removeObject( + String objName, + String objType, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$tenantUrl/api/$objType/$objName'); + final Uri url = Uri.parse('$tenantUrl/api/$objType/$objName'); final response = await client.delete(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode >= 200 && response.statusCode < 300) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -193,15 +191,12 @@ Future> removeObject(String objName, String objType, } Future> fetchDomain(String name) async { - print("API create Domain"); try { - Uri url = Uri.parse('$tenantUrl/api/domains/$name'); + final Uri url = Uri.parse('$tenantUrl/api/domains/$name'); final response = await http.get(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode >= 200 && response.statusCode < 300) { - print(response.body); - Map data = json.decode(response.body); - Domain domain = Domain.fromMap(data["data"]); + final Map data = json.decode(response.body); + final Domain domain = Domain.fromMap(data["data"]); return Success(domain); } else { return Failure(Exception("Unable to load domain")); @@ -212,17 +207,20 @@ Future> fetchDomain(String name) async { } Future> updateDomain( - String currentDomainId, Domain domain) async { - print("API update Domain"); + String currentDomainId, + Domain domain, +) async { try { - Uri url = Uri.parse('$tenantUrl/api/domains/$currentDomainId'); - final response = await http.put(url, - body: domain.toJson(), headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/domains/$currentDomainId'); + final response = await http.put( + url, + body: domain.toJson(), + headers: getHeader(tenantToken), + ); if (response.statusCode == 200) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -231,16 +229,17 @@ Future> updateDomain( } Future> updateUser(User user) async { - print("API update Domain"); try { - Uri url = Uri.parse('$tenantUrl/api/domains/${user.id}'); - final response = await http.put(url, - body: user.toJson(), headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/domains/${user.id}'); + final response = await http.put( + url, + body: user.toJson(), + headers: getHeader(tenantToken), + ); if (response.statusCode == 200) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -249,16 +248,14 @@ Future> updateUser(User user) async { } Future, Exception>> fetchTags({http.Client? client}) async { - print("API get tags $tenantUrl"); client ??= http.Client(); try { - Uri url = Uri.parse('$tenantUrl/api/tags'); + final Uri url = Uri.parse('$tenantUrl/api/tags'); final response = await client.get(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode == 200) { - Map data = json.decode(response.body); - List tags = []; - for (var tag + final Map data = json.decode(response.body); + final List tags = []; + for (final tag in List>.from(data["data"]["objects"])) { tags.add(Tag.fromMap(tag)); } @@ -271,18 +268,17 @@ Future, Exception>> fetchTags({http.Client? client}) async { } } -Future> fetchTag(String tagId, - {http.Client? client}) async { - print("API get tag $tenantUrl"); +Future> fetchTag( + String tagId, { + http.Client? client, +}) async { client ??= http.Client(); try { - Uri url = Uri.parse('$tenantUrl/api/tags/$tagId'); + final Uri url = Uri.parse('$tenantUrl/api/tags/$tagId'); final response = await client.get(url, headers: getHeader(tenantToken)); - print(response.statusCode); if (response.statusCode == 200) { - print(response.body); - Map data = json.decode(response.body); - Tag tag = Tag.fromMap(data["data"]); + final Map data = json.decode(response.body); + final Tag tag = Tag.fromMap(data["data"]); return Success(tag); } else { return Failure(Exception('${response.statusCode}: Failed to load users')); @@ -293,16 +289,17 @@ Future> fetchTag(String tagId, } Future> createTag(Tag tag) async { - print("API create Tag"); try { - Uri url = Uri.parse('$tenantUrl/api/tags'); - final response = await http.post(url, - body: tag.toJson(), headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/tags'); + final response = await http.post( + url, + body: tag.toJson(), + headers: getHeader(tenantToken), + ); if (response.statusCode == 201) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { @@ -311,18 +308,20 @@ Future> createTag(Tag tag) async { } Future> updateTag( - String currentId, Map tagMap) async { - print("API update Tag $currentId"); - print(tagMap); + String currentId, + Map tagMap, +) async { try { - Uri url = Uri.parse('$tenantUrl/api/tags/$currentId'); - final response = await http.patch(url, - body: json.encode(tagMap), headers: getHeader(tenantToken)); - print(response.statusCode); + final Uri url = Uri.parse('$tenantUrl/api/tags/$currentId'); + final response = await http.patch( + url, + body: json.encode(tagMap), + headers: getHeader(tenantToken), + ); if (response.statusCode == 200) { return const Success(null); } else { - var data = json.decode(response.body); + final data = json.decode(response.body); return Failure(Exception("Error: ${data["message"]}")); } } on Exception catch (e) { diff --git a/APP/lib/common/appbar.dart b/APP/lib/common/appbar.dart index 28cad412c..d84f60221 100644 --- a/APP/lib/common/appbar.dart +++ b/APP/lib/common/appbar.dart @@ -1,26 +1,25 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/popup_dialog.dart'; import 'package:ogree_app/models/netbox.dart'; import 'package:ogree_app/pages/login_page.dart'; import 'package:ogree_app/pages/projects_page.dart'; import 'package:ogree_app/pages/tenant_page.dart'; -import 'package:ogree_app/widgets/login/change_password_popup.dart'; import 'package:ogree_app/widgets/common/language_toggle.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:ogree_app/widgets/login/change_password_popup.dart'; +import 'package:ogree_app/widgets/tenants/popups/create_server_popup.dart'; import 'package:ogree_app/widgets/tools/download_tool_popup.dart'; -import '../widgets/tenants/popups/create_server_popup.dart'; - AppBar myAppBar(context, userEmail, {isTenantMode = false}) { final localeMsg = AppLocalizations.of(context)!; - logout() => Navigator.of(context).push( + Future logout() => Navigator.of(context).push( MaterialPageRoute( builder: (context) => const LoginPage(), ), ); - List> entries = >[ + final List> entries = >[ PopupMenuItem( value: "change", child: Text(AppLocalizations.of(context)!.changePassword), @@ -37,32 +36,32 @@ AppBar myAppBar(context, userEmail, {isTenantMode = false}) { value: "new", child: Text(backendType == BackendType.kubernetes ? localeMsg.addKube - : localeMsg.addServer), - )); + : localeMsg.addServer,), + ),); } else { entries.insert( 0, PopupMenuItem( value: Tools.unity.name, child: Text(localeMsg.downloadUnity), - )); + ),); entries.insert( 0, PopupMenuItem( value: Tools.cli.name, child: Text(localeMsg.downloadCli), - )); + ),); if (isTenantAdmin) { entries.insert( 0, PopupMenuItem( value: "tenant", child: Text(localeMsg.tenantParameters), - )); + ),); } } - bool isSmallDisplay = MediaQuery.of(context).size.width < 600; + final bool isSmallDisplay = MediaQuery.of(context).size.width < 600; return AppBar( backgroundColor: Colors.grey.shade900, leadingWidth: 160, @@ -76,32 +75,29 @@ AppBar myAppBar(context, userEmail, {isTenantMode = false}) { style: TextStyle( fontSize: 21, fontWeight: FontWeight.w700, - color: Colors.white), + color: Colors.white,), ), onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => ProjectsPage( userEmail: isTenantMode ? "admin" : userEmail, - isTenantMode: isTenantMode), + isTenantMode: isTenantMode,), ), ), ), Badge( isLabelVisible: isTenantMode, label: const Text("ADMIN"), - ) + ), ], ), ), actions: [ - isSmallDisplay - ? Container() - : Padding( + if (isSmallDisplay) Container() else Padding( padding: const EdgeInsets.only(right: 20), child: Row( children: [ - backendType == BackendType.kubernetes - ? Padding( + if (backendType == BackendType.kubernetes) Padding( padding: const EdgeInsets.only(right: 8), child: Container( decoration: BoxDecoration( @@ -114,10 +110,9 @@ AppBar myAppBar(context, userEmail, {isTenantMode = false}) { label: const Text("KUBE"), ), ), - ) - : Container(), + ) else Container(), Text(isTenantMode ? apiUrl : tenantName, - style: const TextStyle(color: Colors.white)), + style: const TextStyle(color: Colors.white),), ], ), ), @@ -132,17 +127,17 @@ AppBar myAppBar(context, userEmail, {isTenantMode = false}) { logout(); } else if (value == "new") { showCustomPopup( - context, CreateServerPopup(parentCallback: () {})); + context, CreateServerPopup(parentCallback: () {}),); } else if (value == "tenant") { Navigator.of(context).push(MaterialPageRoute( builder: (context) => const TenantPage(userEmail: "admin"), - )); + ),); } else if (value == Tools.unity.name) { - showCustomPopup(context, DownloadToolPopup(tool: Tools.unity), - isDismissible: true); + showCustomPopup(context, const DownloadToolPopup(tool: Tools.unity), + isDismissible: true,); } else if (value == Tools.cli.name) { - showCustomPopup(context, DownloadToolPopup(tool: Tools.cli), - isDismissible: true); + showCustomPopup(context, const DownloadToolPopup(tool: Tools.cli), + isDismissible: true,); } else { showCustomPopup(context, const ChangePasswordPopup()); } @@ -155,8 +150,7 @@ AppBar myAppBar(context, userEmail, {isTenantMode = false}) { color: Colors.white, ), const SizedBox(width: 10), - isSmallDisplay - ? Tooltip( + if (isSmallDisplay) Tooltip( message: isTenantMode ? (backendType == BackendType.kubernetes ? "(KUBE) $apiUrl" @@ -166,14 +160,13 @@ AppBar myAppBar(context, userEmail, {isTenantMode = false}) { child: const Icon( Icons.info_outline_rounded, color: Colors.white, - )) - : Text( + ),) else Text( isTenantMode ? "admin" : userEmail, style: const TextStyle(color: Colors.white), ), ], - )), - const SizedBox(width: 40) + ),), + const SizedBox(width: 40), ], ); } diff --git a/APP/lib/common/csv.dart b/APP/lib/common/csv.dart new file mode 100644 index 000000000..edfed41e9 --- /dev/null +++ b/APP/lib/common/csv.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:csv/csv.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; +import 'package:ogree_app/common/snackbar.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:universal_html/html.dart' as html; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +saveCSV(String desiredFileName, List> rows, + BuildContext context) async { + // Prepare the file + final String csv = const ListToCsvConverter().convert(rows); + final bytes = utf8.encode(csv); + if (kIsWeb) { + // If web, use html to download csv + html.AnchorElement( + href: 'data:application/octet-stream;base64,${base64Encode(bytes)}', + ) + ..setAttribute("download", "report.csv") + ..click(); + } else { + // Save to local filesystem + final localeMsg = AppLocalizations.of(context)!; + final messenger = ScaffoldMessenger.of(context); + final path = (await getApplicationDocumentsDirectory()).path; + var fileName = '$path/$desiredFileName.csv'; + var file = File(fileName); + for (var i = 1; await file.exists(); i++) { + fileName = '$path/$desiredFileName ($i).csv'; + file = File(fileName); + } + file.writeAsBytes(bytes, flush: true).then( + (value) => showSnackBar( + messenger, + "${localeMsg.fileSavedTo} $fileName", + ), + ); + } +} diff --git a/APP/lib/common/definitions.dart b/APP/lib/common/definitions.dart index f6b4984a5..2b1bb969d 100644 --- a/APP/lib/common/definitions.dart +++ b/APP/lib/common/definitions.dart @@ -1,8 +1,10 @@ +// ignore_for_file: constant_identifier_names + enum Namespace { Physical, Organisational, Logical, Test } const starSymbol = "*"; -const isDemo = false; +const isDemo = true; /// Base Result class /// [S] represents the type of the success value diff --git a/APP/lib/common/popup_dialog.dart b/APP/lib/common/popup_dialog.dart index b0a0c35a1..71acbfca6 100644 --- a/APP/lib/common/popup_dialog.dart +++ b/APP/lib/common/popup_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; void showCustomPopup(BuildContext context, Widget child, - {isDismissible = false}) { + {isDismissible = false,}) { showGeneralDialog( context: context, barrierLabel: "Barrier", diff --git a/APP/lib/common/snackbar.dart b/APP/lib/common/snackbar.dart index 89a151609..722178bff 100644 --- a/APP/lib/common/snackbar.dart +++ b/APP/lib/common/snackbar.dart @@ -23,7 +23,7 @@ void showSnackBar( child: Text(message), onTap: () async { await Clipboard.setData( - ClipboardData(text: copyTextTap == "" ? message : copyTextTap)); + ClipboardData(text: copyTextTap == "" ? message : copyTextTap),); }, ), duration: duration, @@ -32,7 +32,7 @@ void showSnackBar( : SnackBarAction( label: "COPY", onPressed: () => - Clipboard.setData(ClipboardData(text: copyTextAction))), + Clipboard.setData(ClipboardData(text: copyTextAction)),), showCloseIcon: duration.inSeconds > 5, ), ); diff --git a/APP/lib/common/theme.dart b/APP/lib/common/theme.dart index b3c198189..ddad3880d 100644 --- a/APP/lib/common/theme.dart +++ b/APP/lib/common/theme.dart @@ -1,22 +1,27 @@ +// ignore_for_file: non_constant_identifier_names, constant_identifier_names + import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/definitions.dart'; -GetFormInputDecoration(isSmallDisplay, String? labelText, - {IconData? icon, - Color? iconColor, - String? prefixText, - String? suffixText, - String? hint, - EdgeInsets? contentPadding = const EdgeInsets.only( - top: 3.0, - bottom: 12.0, - left: 20.0, - right: 14.0, - ), - bool isEnabled = true, - bool isCompact = false, - Widget? iconWidget}) => +InputDecoration GetFormInputDecoration( + isSmallDisplay, + String? labelText, { + IconData? icon, + Color? iconColor, + String? prefixText, + String? suffixText, + String? hint, + EdgeInsets? contentPadding = const EdgeInsets.only( + top: 3.0, + bottom: 12.0, + left: 20.0, + right: 14.0, + ), + bool isEnabled = true, + bool isCompact = false, + Widget? iconWidget, +}) => InputDecoration( prefixIcon: iconWidget ?? (isSmallDisplay @@ -38,13 +43,15 @@ GetFormInputDecoration(isSmallDisplay, String? labelText, children: [ RichText( text: TextSpan( - text: labelText.replaceFirst(starSymbol, ""), - style: const TextStyle(color: Colors.black), - children: const [ - TextSpan( - text: starSymbol, - style: TextStyle(color: Colors.red)) - ]), + text: labelText.replaceFirst(starSymbol, ""), + style: const TextStyle(color: Colors.black), + children: const [ + TextSpan( + text: starSymbol, + style: TextStyle(color: Colors.red), + ), + ], + ), ), ], ) @@ -67,29 +74,33 @@ GetFormInputDecoration(isSmallDisplay, String? labelText, const FormInputPadding = EdgeInsets.only(left: 2, right: 10, bottom: 8, top: 2); final PopupDecoration = BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(30), - boxShadow: const [ - // Shadow for top-left corner - BoxShadow( - color: Colors.grey, - offset: Offset(10, 10), - blurRadius: 6, - spreadRadius: 1, - ), - // Shadow for bottom-right corner - BoxShadow( - color: Colors.white12, - offset: Offset(-10, -10), - blurRadius: 5, - spreadRadius: 1, - ), - ]); + color: Colors.white, + borderRadius: BorderRadius.circular(30), + boxShadow: const [ + // Shadow for top-left corner + BoxShadow( + color: Colors.grey, + offset: Offset(10, 10), + blurRadius: 6, + spreadRadius: 1, + ), + // Shadow for bottom-right corner + BoxShadow( + color: Colors.white12, + offset: Offset(-10, -10), + blurRadius: 5, + spreadRadius: 1, + ), + ], +); -IsSmallDisplay(width) => width < 550; +bool IsSmallDisplay(double width) => width < 550; -LoginInputDecoration( - {required String label, String? hint, bool isSmallDisplay = false}) => +InputDecoration LoginInputDecoration({ + required String label, + String? hint, + bool isSmallDisplay = false, +}) => InputDecoration( contentPadding: isSmallDisplay ? const EdgeInsets.symmetric(horizontal: 12, vertical: 16) @@ -103,7 +114,6 @@ LoginInputDecoration( border: const OutlineInputBorder( borderSide: BorderSide( color: Colors.grey, - width: 1, ), ), ); diff --git a/APP/lib/main.dart b/APP/lib/main.dart index d816929b8..c17ad4243 100644 --- a/APP/lib/main.dart +++ b/APP/lib/main.dart @@ -2,8 +2,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/pages/login_page.dart'; Future main() async { @@ -42,66 +42,65 @@ class MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - // debugShowCheckedModeBanner: false, - title: 'OGrEE App', - locale: _locale, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - theme: ThemeData( - useMaterial3: true, - colorSchemeSeed: Colors.blue, - fontFamily: GoogleFonts.inter().fontFamily, - scrollbarTheme: ScrollbarThemeData( - thumbVisibility: MaterialStateProperty.all(true), - ), - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( + // debugShowCheckedModeBanner: false, + title: 'OGrEE App', + locale: _locale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + theme: ThemeData( + useMaterial3: true, + colorSchemeSeed: Colors.blue, + fontFamily: GoogleFonts.inter().fontFamily, + scrollbarTheme: ScrollbarThemeData( + thumbVisibility: WidgetStateProperty.all(true), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade600, foregroundColor: Colors.white, - )), - cardTheme: const CardTheme( - elevation: 3, - surfaceTintColor: Colors.white, - color: Colors.white), - textTheme: TextTheme( - headlineLarge: GoogleFonts.inter( - fontSize: 22, - color: Colors.black, - fontWeight: FontWeight.w700, - ), - headlineMedium: GoogleFonts.inter( - fontSize: 20, - color: Colors.black, - fontWeight: FontWeight.w400, - ), - headlineSmall: GoogleFonts.inter( - fontSize: 17, - color: Colors.black, - ), ), ), - home: const LoginPage(), - onGenerateRoute: RouteGenerator.generateRoute); + cardTheme: const CardTheme( + elevation: 3, + surfaceTintColor: Colors.white, + color: Colors.white, + ), + textTheme: TextTheme( + headlineLarge: GoogleFonts.inter( + fontSize: 22, + color: Colors.black, + fontWeight: FontWeight.w700, + ), + headlineMedium: GoogleFonts.inter( + fontSize: 20, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + headlineSmall: GoogleFonts.inter( + fontSize: 17, + color: Colors.black, + ), + ), + ), + home: const LoginPage(), + onGenerateRoute: RouteGenerator.generateRoute, + ); } } class RouteGenerator { static Route generateRoute(RouteSettings settings) { - String? route; Map? queryParameters; if (settings.name != null) { - var uriData = Uri.parse(settings.name!); - route = uriData.path; + final uriData = Uri.parse(settings.name!); queryParameters = uriData.queryParameters; } - var message = - 'generateRoute: Route $route, QueryParameters $queryParameters'; - print(message); return MaterialPageRoute( builder: (context) { return LoginPage( - isPasswordReset: true, - resetToken: queryParameters!["token"].toString()); + isPasswordReset: true, + resetToken: queryParameters!["token"].toString(), + ); }, settings: settings, ); diff --git a/APP/lib/models/container.dart b/APP/lib/models/container.dart index d8e7fc856..5a4575e12 100644 --- a/APP/lib/models/container.dart +++ b/APP/lib/models/container.dart @@ -9,7 +9,7 @@ class DockerContainer { String ports; DockerContainer(this.name, this.lastStarted, this.status, this.image, - this.size, this.ports); + this.size, this.ports,); Map toMap() { return { @@ -18,7 +18,7 @@ class DockerContainer { 'State': status, 'Image': image, 'Size': size, - 'Ports': ports + 'Ports': ports, }; } diff --git a/APP/lib/models/domain.dart b/APP/lib/models/domain.dart index 21cf0bbea..c3535f0ac 100644 --- a/APP/lib/models/domain.dart +++ b/APP/lib/models/domain.dart @@ -15,7 +15,7 @@ class Domain { 'attributes': {'color': color}, 'category': 'domain', 'description': description, - 'parentId': parent + 'parentId': parent, }; } diff --git a/APP/lib/models/project.dart b/APP/lib/models/project.dart index b3e5ac2d6..0453be735 100644 --- a/APP/lib/models/project.dart +++ b/APP/lib/models/project.dart @@ -29,7 +29,7 @@ class Project { this.objects, this.permissions, {this.id, - this.isImpact = false}); + this.isImpact = false,}); Map toMap() { return { @@ -58,9 +58,9 @@ class Project { map['showAvg'] as bool, map['showSum'] as bool, map['isPublic'] as bool, - List.from((map['attributes'])), - List.from((map['objects'])), - List.from((map['permissions'])), + List.from(map['attributes']), + List.from(map['objects']), + List.from(map['permissions']), id: map['Id'].toString(), isImpact: map['isImpact'] is bool ? map['isImpact'] as bool : false, ); diff --git a/APP/lib/models/tag.dart b/APP/lib/models/tag.dart index 5236fd909..202755c49 100644 --- a/APP/lib/models/tag.dart +++ b/APP/lib/models/tag.dart @@ -27,7 +27,7 @@ class Tag { description: map['description'].toString(), slug: map['slug'].toString(), color: map['color'].toString(), - image: map['image'].toString()); + image: map['image'].toString(),); } String toJson() => json.encode(toMap()); diff --git a/APP/lib/models/tenant.dart b/APP/lib/models/tenant.dart index 77e67dfb7..699a2d597 100644 --- a/APP/lib/models/tenant.dart +++ b/APP/lib/models/tenant.dart @@ -29,7 +29,7 @@ class Tenant { this.docUrl, this.docPort, this.imageTag, - {this.status}); + {this.status,}); Map toMap() { return { @@ -59,7 +59,7 @@ class Tenant { map['hasDoc'], map['docUrl'].toString(), map['docPort'].toString(), - map['imageTag'].toString()); + map['imageTag'].toString(),); } String toJson() => json.encode(toMap()); diff --git a/APP/lib/models/user.dart b/APP/lib/models/user.dart index 43f20fa90..45dc8b164 100644 --- a/APP/lib/models/user.dart +++ b/APP/lib/models/user.dart @@ -15,7 +15,7 @@ class User { required this.email, required this.password, required this.roles, - this.id}); + this.id,}); Map toMap() { if (roles[allDomainsConvert] != null) { @@ -31,7 +31,7 @@ class User { } factory User.fromMap(Map map) { - var roles = Map.from(map['roles']); + final roles = Map.from(map['roles']); if (roles[allDomainsTag] != null) { roles[allDomainsConvert] = roles[allDomainsTag]!; roles.remove(allDomainsTag); @@ -41,7 +41,7 @@ class User { id: map['_id'].toString(), email: map['email'].toString(), password: map['password'].toString(), - roles: roles); + roles: roles,); } String toJson() => json.encode(toMap()); diff --git a/APP/lib/pages/alert_page.dart b/APP/lib/pages/alert_page.dart index 03a24deca..431134e21 100644 --- a/APP/lib/pages/alert_page.dart +++ b/APP/lib/pages/alert_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/appbar.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/models/alert.dart'; @@ -7,7 +8,6 @@ import 'package:ogree_app/pages/projects_page.dart'; import 'package:ogree_app/pages/select_page.dart'; import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class AlertPage extends StatefulWidget { final String userEmail; @@ -30,7 +30,7 @@ class AlertPageState extends State with TickerProviderStateMixin { final localeMsg = AppLocalizations.of(context)!; return Scaffold( backgroundColor: const Color.fromARGB(255, 238, 238, 241), - appBar: myAppBar(context, widget.userEmail, isTenantMode: false), + appBar: myAppBar(context, widget.userEmail), body: Padding( padding: const EdgeInsets.all(20.0), child: CustomScrollView(slivers: [ @@ -48,12 +48,12 @@ class AlertPageState extends State with TickerProviderStateMixin { Navigator.of(context).push(MaterialPageRoute( builder: (context) => ProjectsPage( userEmail: widget.userEmail, - isTenantMode: false), - )), + isTenantMode: false,), + ),), icon: Icon( Icons.arrow_back, color: Colors.blue.shade900, - )), + ),), const SizedBox(width: 5), Text( localeMsg.myAlerts, @@ -80,7 +80,7 @@ class AlertPageState extends State with TickerProviderStateMixin { Padding( padding: const EdgeInsets.only(top: 16), child: Text( - "${AppLocalizations.of(context)!.noAlerts} :)"), + "${AppLocalizations.of(context)!.noAlerts} :)",), ), ], ), @@ -90,12 +90,12 @@ class AlertPageState extends State with TickerProviderStateMixin { ), ], ), - ) - ]), - )); + ), + ],), + ),); } - alertView(AppLocalizations localeMsg) { + Column alertView(AppLocalizations localeMsg) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -148,17 +148,17 @@ class AlertPageState extends State with TickerProviderStateMixin { [], [widget.alerts[selectedIndex].id], [], - isImpact: true), + isImpact: true,), userEmail: widget.userEmail, ), ), ); }, - icon: Icon(Icons.arrow_back), - label: Text(localeMsg.goToImpact)), + icon: const Icon(Icons.arrow_back), + label: Text(localeMsg.goToImpact),), ), - ) - ]), + ), + ],), ), ), ), @@ -170,11 +170,11 @@ class AlertPageState extends State with TickerProviderStateMixin { } List getListTitles() { - List list = []; + final List list = []; int index = 0; - for (var alert in widget.alerts) { + for (final alert in widget.alerts) { list.add(getListTitle( - alert.type.toUpperCase(), Colors.amber, alert.title, index)); + alert.type.toUpperCase(), Colors.amber, alert.title, index,),); index++; } return list; @@ -202,7 +202,7 @@ class AlertPageState extends State with TickerProviderStateMixin { style: const TextStyle( fontSize: 10, fontWeight: FontWeight.bold, - color: Colors.black), + color: Colors.black,), ), ), ), @@ -220,7 +220,7 @@ class AlertPageState extends State with TickerProviderStateMixin { ); } - getAlertText(int index) { + Text getAlertText(int index) { return Text.rich( TextSpan( children: [ @@ -236,7 +236,7 @@ class AlertPageState extends State with TickerProviderStateMixin { ), TextSpan( text: - "\n${widget.alerts[index].title}. ${widget.alerts[index].subtitle}."), + "\n${widget.alerts[index].title}. ${widget.alerts[index].subtitle}.",), ], ), ], @@ -244,7 +244,7 @@ class AlertPageState extends State with TickerProviderStateMixin { ); } - getWidgetSpan(String text, String badge, MaterialColor badgeColor) { + WidgetSpan getWidgetSpan(String text, String badge, MaterialColor badgeColor) { return WidgetSpan( child: Padding( padding: const EdgeInsets.symmetric(vertical: 2.0), @@ -260,7 +260,7 @@ class AlertPageState extends State with TickerProviderStateMixin { style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, - color: badgeColor.shade900), + color: badgeColor.shade900,), ), ), ), diff --git a/APP/lib/pages/impact_page.dart b/APP/lib/pages/impact_page.dart index decf8ce49..d95400e4a 100644 --- a/APP/lib/pages/impact_page.dart +++ b/APP/lib/pages/impact_page.dart @@ -1,23 +1,22 @@ import 'dart:convert'; import 'dart:io'; + import 'package:csv/csv.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/alert.dart'; -import 'package:flutter/material.dart'; -import 'package:ogree_app/common/api_backend.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/widgets/impact/impact_view.dart'; import 'package:path_provider/path_provider.dart'; - -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:universal_html/html.dart' as html; class ImpactPage extends StatefulWidget { - List selectedObjects; + final List selectedObjects; - ImpactPage({ + const ImpactPage({ super.key, required this.selectedObjects, }); @@ -33,7 +32,6 @@ class _ImpactPageState extends State { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - bool isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return SizedBox( height: MediaQuery.of(context).size.height > 205 @@ -47,7 +45,7 @@ class _ImpactPageState extends State { index = index - 1; if (index == -1) { if (widget.selectedObjects.length == 1) { - return SizedBox(height: 6); + return const SizedBox(height: 6); } return Padding( padding: const EdgeInsets.only(top: 12.0), @@ -59,28 +57,31 @@ class _ImpactPageState extends State { Tooltip( message: localeMsg.markAllTip, child: TextButton.icon( - onPressed: () => markAll(true, localeMsg), - label: Text(localeMsg.markAllMaintenance), - icon: Icon(Icons.check_circle)), + onPressed: () => markAll(true, localeMsg), + label: Text(localeMsg.markAllMaintenance), + icon: const Icon(Icons.check_circle), + ), ), Padding( - padding: EdgeInsets.only(left: 2), + padding: const EdgeInsets.only(left: 2), child: Tooltip( message: localeMsg.unmarkAllTip, child: TextButton.icon( - onPressed: () => markAll(false, localeMsg), - label: Text(localeMsg.unmarkAll), - icon: Icon(Icons.check_circle_outline)), + onPressed: () => markAll(false, localeMsg), + label: Text(localeMsg.unmarkAll), + icon: const Icon(Icons.check_circle_outline), + ), ), ), Padding( - padding: EdgeInsets.only(left: 2), + padding: const EdgeInsets.only(left: 2), child: Tooltip( message: localeMsg.downloadAllTip, child: TextButton.icon( - onPressed: () => downloadAll(), - label: Text(localeMsg.downloadAll), - icon: Icon(Icons.download)), + onPressed: () => downloadAll(), + label: Text(localeMsg.downloadAll), + icon: const Icon(Icons.download), + ), ), ), ], @@ -105,12 +106,12 @@ class _ImpactPageState extends State { ); } - markAll(bool isMark, AppLocalizations localeMsg) async { + Future markAll(bool isMark, AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); if (!isMark) { // unmark - for (var obj in widget.selectedObjects) { - var result = await deleteObject(obj, "alert"); + for (final obj in widget.selectedObjects) { + final result = await deleteObject(obj, "alert"); switch (result) { case Success(): break; @@ -126,11 +127,15 @@ class _ImpactPageState extends State { shouldMarkAll = false; }); } else { - for (var obj in widget.selectedObjects) { - var alert = Alert( - obj, "minor", "$obj ${localeMsg.isMarked}", localeMsg.checkImpact); + for (final obj in widget.selectedObjects) { + final alert = Alert( + obj, + "minor", + "$obj ${localeMsg.isMarked}", + localeMsg.checkImpact, + ); - var result = await createAlert(alert); + final result = await createAlert(alert); switch (result) { case Success(): break; @@ -146,11 +151,12 @@ class _ImpactPageState extends State { } } - downloadAll() async { - List> rows = []; + Future downloadAll() async { + final List> rows = []; final messenger = ScaffoldMessenger.of(context); - for (var obj in widget.selectedObjects) { - List selectedCategories = []; + final localeMsg = AppLocalizations.of(context)!; + for (final obj in widget.selectedObjects) { + final List selectedCategories = []; List selectedPtypes = []; List selectedVtypes = []; if ('.'.allMatches(obj).length > 2) { @@ -159,16 +165,19 @@ class _ImpactPageState extends State { selectedVtypes = ["application", "cluster", "vm"]; } final result = await fetchObjectImpact( - obj, selectedCategories, selectedPtypes, selectedVtypes); + obj, + selectedCategories, + selectedPtypes, + selectedVtypes, + ); switch (result) { case Success(value: final value): rows.add(["target", obj]); - for (var type in ["direct", "indirect"]) { - var direct = (Map.from(value[type])).keys.toList(); + for (final type in ["direct", "indirect"]) { + final direct = Map.from(value[type]).keys.toList(); direct.insertAll(0, [type]); rows.add(direct); } - break; case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); return; @@ -176,26 +185,30 @@ class _ImpactPageState extends State { } // Prepare the file - String csv = const ListToCsvConverter().convert(rows); + final String csv = const ListToCsvConverter().convert(rows); final bytes = utf8.encode(csv); if (kIsWeb) { // If web, use html to download csv html.AnchorElement( - href: 'data:application/octet-stream;base64,${base64Encode(bytes)}') + href: 'data:application/octet-stream;base64,${base64Encode(bytes)}', + ) ..setAttribute("download", "impact-report.csv") ..click(); } else { // Save to local filesystem - var path = (await getApplicationDocumentsDirectory()).path; + final path = (await getApplicationDocumentsDirectory()).path; var fileName = '$path/impact-report.csv'; var file = File(fileName); for (var i = 1; await file.exists(); i++) { fileName = '$path/impact-report ($i).csv'; file = File(fileName); } - file.writeAsBytes(bytes, flush: true).then((value) => showSnackBar( - ScaffoldMessenger.of(context), - "${AppLocalizations.of(context)!.fileSavedTo} $fileName")); + file.writeAsBytes(bytes, flush: true).then( + (value) => showSnackBar( + messenger, + "${localeMsg.fileSavedTo} $fileName", + ), + ); } } } diff --git a/APP/lib/pages/login_page.dart b/APP/lib/pages/login_page.dart index a0908dd19..48005e427 100644 --- a/APP/lib/pages/login_page.dart +++ b/APP/lib/pages/login_page.dart @@ -7,7 +7,7 @@ class LoginPage extends StatefulWidget { final bool isPasswordReset; final String resetToken; const LoginPage( - {super.key, this.isPasswordReset = false, this.resetToken = ""}); + {super.key, this.isPasswordReset = false, this.resetToken = "",}); @override State createState() => _LoginPageState(); } @@ -28,22 +28,19 @@ class _LoginPageState extends State { hasScrollBody: false, child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ const Align( alignment: Alignment.topCenter, child: LanguageToggle(), ), const SizedBox(height: 5), - widget.isPasswordReset - ? ResetCard( + if (widget.isPasswordReset) ResetCard( token: widget.resetToken, - ) - : const LoginCard(), + ) else const LoginCard(), ], ), - ) - ]), + ), + ],), ), ); } diff --git a/APP/lib/pages/projects_page.dart b/APP/lib/pages/projects_page.dart index fd12dc0db..9d342cb6c 100644 --- a/APP/lib/pages/projects_page.dart +++ b/APP/lib/pages/projects_page.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/appbar.dart'; import 'package:ogree_app/common/definitions.dart'; @@ -11,11 +12,10 @@ import 'package:ogree_app/models/project.dart'; import 'package:ogree_app/models/tenant.dart'; import 'package:ogree_app/pages/alert_page.dart'; import 'package:ogree_app/pages/select_page.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/widgets/projects/autoproject_card.dart'; import 'package:ogree_app/widgets/projects/autounity_card.dart'; -import 'package:ogree_app/widgets/tenants/popups/create_tenant_popup.dart'; import 'package:ogree_app/widgets/projects/project_card.dart'; +import 'package:ogree_app/widgets/tenants/popups/create_tenant_popup.dart'; import 'package:ogree_app/widgets/tenants/tenant_card.dart'; import 'package:ogree_app/widgets/tools/create_netbox_popup.dart'; import 'package:ogree_app/widgets/tools/create_opendcim_popup.dart'; @@ -26,7 +26,7 @@ class ProjectsPage extends StatefulWidget { final String userEmail; final bool isTenantMode; const ProjectsPage( - {super.key, required this.userEmail, required this.isTenantMode}); + {super.key, required this.userEmail, required this.isTenantMode,}); @override State createState() => _ProjectsPageState(); @@ -50,48 +50,46 @@ class _ProjectsPageState extends State { final localeMsg = AppLocalizations.of(context)!; return Scaffold( appBar: myAppBar(context, widget.userEmail, - isTenantMode: widget.isTenantMode), + isTenantMode: widget.isTenantMode,), body: Padding( padding: EdgeInsets.symmetric( - horizontal: _isSmallDisplay ? 40 : 80.0, vertical: 20), + horizontal: _isSmallDisplay ? 40 : 80.0, vertical: 20,), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - ...getAlertDemoWidgets(localeMsg), + ...getAlertWidgets(localeMsg), + // SizedBox(height: 30), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - widget.isTenantMode - ? Row( + if (widget.isTenantMode) Row( children: [ Text(localeMsg.applications, - style: Theme.of(context).textTheme.headlineLarge), + style: Theme.of(context).textTheme.headlineLarge,), IconButton( onPressed: () => setState(() { _gotData = false; }), - icon: const Icon(Icons.refresh)) + icon: const Icon(Icons.refresh),), ], - ) - : Text(localeMsg.myprojects, - style: Theme.of(context).textTheme.headlineLarge), + ) else Text(localeMsg.myprojects, + style: Theme.of(context).textTheme.headlineLarge,), Row( children: [ - Padding( - padding: const EdgeInsets.only(right: 10.0, bottom: 10), - child: impactViewButton(), - ), + if (!widget.isTenantMode) Padding( + padding: + const EdgeInsets.only(right: 10.0, bottom: 10), + child: impactViewButton(), + ) else Container(), Padding( padding: const EdgeInsets.only(right: 10.0, bottom: 10), child: createProjectButton(), ), - widget.isTenantMode - ? Padding( + if (widget.isTenantMode) Padding( padding: const EdgeInsets.only(right: 10.0, bottom: 10), child: createToolsButton(), - ) - : Container(), + ) else Container(), ], ), ], @@ -127,7 +125,7 @@ class _ProjectsPageState extends State { return Text(localeMsg.noProjects); } } - }), + },), ], ), ), @@ -146,18 +144,18 @@ class _ProjectsPageState extends State { final result = await fetchApplications(); switch (result) { case Success(value: final value): - var (tenants, tools) = value; + final (tenants, tools) = value; _tenants = tenants; - for (var tenant in tenants) { + for (final tenant in tenants) { final result = await fetchTenantDockerInfo(tenant.name); switch (result) { case Success(value: final value): - List dockerInfo = value; + final List dockerInfo = value; if (dockerInfo.isEmpty) { tenant.status = TenantStatus.unavailable; } else { int runCount = 0; - for (var container in dockerInfo) { + for (final container in dockerInfo) { if (container.status.contains("run")) { runCount++; } @@ -212,13 +210,13 @@ class _ProjectsPageState extends State { } } - createProjectButton() { + ElevatedButton createProjectButton() { final localeMsg = AppLocalizations.of(context)!; return ElevatedButton( onPressed: () { if (widget.isTenantMode) { showCustomPopup( - context, CreateTenantPopup(parentCallback: refreshFromChildren)); + context, CreateTenantPopup(parentCallback: refreshFromChildren),); } else { Navigator.of(context).push( MaterialPageRoute( @@ -232,22 +230,20 @@ class _ProjectsPageState extends State { children: [ Padding( padding: EdgeInsets.only( - top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10), + top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10,), child: const Icon(Icons.add_to_photos), ), - _isSmallDisplay - ? Container() - : Text(widget.isTenantMode + if (_isSmallDisplay) Container() else Text(widget.isTenantMode ? "${localeMsg.create} tenant" - : localeMsg.newProject), + : localeMsg.newProject,), ], ), ); } - createToolsButton() { + ElevatedButton createToolsButton() { final localeMsg = AppLocalizations.of(context)!; - List> entries = >[ + final List> entries = >[ PopupMenuItem( value: Tools.netbox, child: Text("${localeMsg.create} Netbox"), @@ -283,44 +279,39 @@ class _ProjectsPageState extends State { case Tools.netbox: if (_hasNetbox) { showSnackBar(ScaffoldMessenger.of(context), - localeMsg.onlyOneTool("Netbox")); + localeMsg.onlyOneTool("Netbox"),); } else { showCustomPopup( context, CreateNboxPopup( parentCallback: refreshFromChildren, - tool: Tools.netbox)); + tool: Tools.netbox,),); } - break; case Tools.nautobot: if (_hasNautobot) { showSnackBar(ScaffoldMessenger.of(context), - localeMsg.onlyOneTool("Nautobot")); + localeMsg.onlyOneTool("Nautobot"),); } else { showCustomPopup( context, CreateNboxPopup( parentCallback: refreshFromChildren, - tool: Tools.nautobot)); + tool: Tools.nautobot,),); } - break; case Tools.opendcim: if (_hasOpenDcim) { showSnackBar(ScaffoldMessenger.of(context), - localeMsg.onlyOneTool("OpenDCIM")); + localeMsg.onlyOneTool("OpenDCIM"),); } else { showCustomPopup(context, - CreateOpenDcimPopup(parentCallback: refreshFromChildren)); + CreateOpenDcimPopup(parentCallback: refreshFromChildren),); } - break; case Tools.cli: - showCustomPopup(context, DownloadToolPopup(tool: Tools.cli), - isDismissible: true); - break; + showCustomPopup(context, const DownloadToolPopup(tool: Tools.cli), + isDismissible: true,); case Tools.unity: - showCustomPopup(context, DownloadToolPopup(tool: Tools.unity), - isDismissible: true); - break; + showCustomPopup(context, const DownloadToolPopup(tool: Tools.unity), + isDismissible: true,); } }, itemBuilder: (_) => entries, @@ -329,32 +320,32 @@ class _ProjectsPageState extends State { children: [ Padding( padding: EdgeInsets.only( - top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10), + top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10,), child: const Icon(Icons.timeline), ), - _isSmallDisplay ? Container() : Text(localeMsg.tools), + if (_isSmallDisplay) Container() else Text(localeMsg.tools), ], ), ), ); } - getCards(context) { - List cards = []; + List getCards(context) { + final List cards = []; if (widget.isTenantMode) { if (_tenants != null && _tenants!.isNotEmpty) { - for (var tenant in _tenants!) { + for (final tenant in _tenants!) { cards.add(TenantCard( tenant: tenant, parentCallback: refreshFromChildren, - )); + ),); } } if (_tools != null && _tools!.isNotEmpty) { _hasOpenDcim = false; _hasNetbox = false; _hasNautobot = false; - for (var tool in _tools!) { + for (final tool in _tools!) { var type = Tools.netbox; if (tool.name.contains(Tools.opendcim.name)) { type = Tools.opendcim; @@ -369,36 +360,36 @@ class _ProjectsPageState extends State { type: type, container: tool, parentCallback: refreshFromChildren, - )); + ),); } } } else { if (isDemo) { cards.add(AutoUnityProjectCard( userEmail: widget.userEmail, - )); + ),); } - for (var namespace in Namespace.values) { + for (final namespace in Namespace.values) { if (namespace != Namespace.Test) { cards.add(AutoProjectCard( namespace: namespace, userEmail: widget.userEmail, parentCallback: refreshFromChildren, - )); + ),); } } - for (var project in _projects!) { + for (final project in _projects!) { cards.add(ProjectCard( project: project, userEmail: widget.userEmail, parentCallback: refreshFromChildren, - )); + ),); } } return cards; } - impactViewButton() { + ElevatedButton impactViewButton() { final localeMsg = AppLocalizations.of(context)!; return ElevatedButton( style: ElevatedButton.styleFrom( @@ -418,22 +409,25 @@ class _ProjectsPageState extends State { children: [ Padding( padding: EdgeInsets.only( - top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10), + top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10,), child: const Icon(Icons.settings_suggest), ), - _isSmallDisplay ? Container() : Text(localeMsg.impactAnalysis), + if (_isSmallDisplay) Container() else Text(localeMsg.impactAnalysis), ], ), ); } - List getAlertDemoWidgets(AppLocalizations localeMsg) { + List getAlertWidgets(AppLocalizations localeMsg) { + if (widget.isTenantMode) { + return []; + } return [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(localeMsg.myAlerts, - style: Theme.of(context).textTheme.headlineLarge), + style: Theme.of(context).textTheme.headlineLarge,), Padding( padding: const EdgeInsets.only(right: 10.0, bottom: 10), child: alertViewButton(), @@ -479,7 +473,7 @@ class _ProjectsPageState extends State { ], ), ); - }), + },), ), const SizedBox(height: 30), ]; @@ -488,20 +482,20 @@ class _ProjectsPageState extends State { String alertsToString(AppLocalizations localeMsg) { var alertStr = ""; if (_alerts.length > 1) { - for (var alert in _alerts) { + for (final alert in _alerts) { alertStr = "$alertStr${alert.title.split(" ").first}, "; } alertStr = alertStr.substring(0, alertStr.length - 2); alertStr = "${localeMsg.areMarkedMaintenance} $alertStr."; } else { - for (var alert in _alerts) { + for (final alert in _alerts) { alertStr = "${alert.title.split(" ").first} ${localeMsg.isMarked}."; } } return alertStr; } - alertViewButton() { + ElevatedButton alertViewButton() { final localeMsg = AppLocalizations.of(context)!; return ElevatedButton( style: ElevatedButton.styleFrom( @@ -511,7 +505,7 @@ class _ProjectsPageState extends State { onPressed: () { if (widget.isTenantMode) { showCustomPopup( - context, CreateTenantPopup(parentCallback: refreshFromChildren)); + context, CreateTenantPopup(parentCallback: refreshFromChildren),); } else { Navigator.of(context).push( MaterialPageRoute( @@ -528,10 +522,10 @@ class _ProjectsPageState extends State { children: [ Padding( padding: EdgeInsets.only( - top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10), + top: 8, bottom: 8, right: _isSmallDisplay ? 0 : 10,), child: const Icon(Icons.analytics), ), - _isSmallDisplay ? Container() : Text(localeMsg.viewAlerts), + if (_isSmallDisplay) Container() else Text(localeMsg.viewAlerts), ], ), ); diff --git a/APP/lib/pages/results_page.dart b/APP/lib/pages/results_page.dart index 2786559f6..51d1f2880 100644 --- a/APP/lib/pages/results_page.dart +++ b/APP/lib/pages/results_page.dart @@ -1,36 +1,32 @@ import 'dart:collection'; -import 'dart:convert'; -import 'dart:io'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:ogree_app/common/definitions.dart'; -import 'package:ogree_app/common/snackbar.dart'; -import 'package:ogree_app/common/theme.dart'; -import 'package:universal_html/html.dart' as html; import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:ogree_app/common/api_backend.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:ogree_app/common/api_backend.dart'; +import 'package:ogree_app/common/csv.dart'; +import 'package:ogree_app/common/definitions.dart'; +import 'package:ogree_app/common/snackbar.dart'; +import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; -import 'package:csv/csv.dart'; -import 'package:path_provider/path_provider.dart'; const String extraColumn = "Add new column"; const String sumStr = "Somme()"; const String avgStr = "Moyenne()"; class ResultsPage extends StatefulWidget { - List selectedAttrs; - List selectedObjects; + final List selectedAttrs; + final List selectedObjects; final String dateRange; final String namespace; - ResultsPage( - {super.key, - required this.selectedAttrs, - required this.selectedObjects, - required this.dateRange, - required this.namespace}); + const ResultsPage({ + super.key, + required this.selectedAttrs, + required this.selectedObjects, + required this.dateRange, + required this.namespace, + }); @override State createState() => _ResultsPageState(); @@ -38,7 +34,8 @@ class ResultsPage extends StatefulWidget { class _ResultsPageState extends State { final SplayTreeSet _allAttributes = SplayTreeSet( - (a, b) => a.toLowerCase().compareTo(b.toLowerCase())); + (a, b) => a.toLowerCase().compareTo(b.toLowerCase()), + ); Map>? _data; List columnLabels = []; List selectedAttrs = []; @@ -70,87 +67,92 @@ class _ResultsPageState extends State { Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - bool isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); + final bool isSmallDisplay = + IsSmallDisplay(MediaQuery.of(context).size.width); return FutureBuilder( - future: _data == null ? getData() : null, - builder: (context, _) { - if (_data != null) { - if (_data!.isEmpty) { - return SizedBox( - height: MediaQuery.of(context).size.height > 205 - ? MediaQuery.of(context).size.height - 205 - : MediaQuery.of(context).size.height, - child: Card( - margin: const EdgeInsets.all(0.1), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.warning_rounded, - size: 50, - color: Colors.grey.shade600, - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: Text( - "${AppLocalizations.of(context)!.noObjectsFound} :("), + future: _data == null ? getData() : null, + builder: (context, _) { + if (_data != null) { + if (_data!.isEmpty) { + return SizedBox( + height: MediaQuery.of(context).size.height > 205 + ? MediaQuery.of(context).size.height - 205 + : MediaQuery.of(context).size.height, + child: Card( + margin: const EdgeInsets.all(0.1), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.warning_rounded, + size: 50, + color: Colors.grey.shade600, + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Text( + "${AppLocalizations.of(context)!.noObjectsFound} :(", ), - ], - ), - ), - ); - } - return SingleChildScrollView( - padding: EdgeInsets.zero, - child: PaginatedDataTable( - header: Text( - localeMsg.yourReport, - style: TextStyle( - fontWeight: FontWeight.w600, - fontSize: isSmallDisplay ? 16 : null), - ), - actions: [ - Padding( - padding: const EdgeInsets.only(right: 6.0), - child: IconButton( - icon: const Icon(Icons.file_download_outlined), - onPressed: () => getCSV(), ), - ), - PopupMenuButton( - tooltip: localeMsg.selectionOptions, - offset: const Offset(0, -32), - itemBuilder: (_) => attributesCheckList(selectedAttrs), - onCanceled: () => print('canceled'), - icon: const Icon(Icons.add), - ), - PopupMenuButton( - tooltip: localeMsg.mathFuncTip, - offset: const Offset(0, -32), - itemBuilder: (_) => mathFunctionsPopup(), - onCanceled: () => print('canceled'), - icon: const Icon(Icons.calculate_outlined), - ) - ], - rowsPerPage: widget.selectedObjects.length >= 15 - ? 15 - : widget.selectedObjects.length, - sortColumnIndex: sortColumnIndex > 0 ? sortColumnIndex : null, - sortAscending: sort, - columns: columnLabels, - showCheckboxColumn: false, - source: _DataSource( - context, selectedAttrs, widget.selectedObjects, _data), + ], + ), ), ); - } else { - return const Center(child: CircularProgressIndicator()); } - }); + return SingleChildScrollView( + padding: EdgeInsets.zero, + child: PaginatedDataTable( + header: Text( + localeMsg.yourReport, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: isSmallDisplay ? 16 : null, + ), + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 6.0), + child: IconButton( + icon: const Icon(Icons.file_download_outlined), + onPressed: () => getCSV(), + ), + ), + PopupMenuButton( + tooltip: localeMsg.selectionOptions, + offset: const Offset(0, -32), + itemBuilder: (_) => attributesCheckList(selectedAttrs), + icon: const Icon(Icons.add), + ), + PopupMenuButton( + tooltip: localeMsg.mathFuncTip, + offset: const Offset(0, -32), + itemBuilder: (_) => mathFunctionsPopup(), + icon: const Icon(Icons.calculate_outlined), + ), + ], + rowsPerPage: widget.selectedObjects.length >= 15 + ? 15 + : widget.selectedObjects.length, + sortColumnIndex: sortColumnIndex > 0 ? sortColumnIndex : null, + sortAscending: sort, + columns: columnLabels, + showCheckboxColumn: false, + source: _DataSource( + context, + selectedAttrs, + widget.selectedObjects, + _data, + ), + ), + ); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ); } - getData() async { - print("GET DATA"); + Future getData() async { if (widget.namespace == Namespace.Test.name) { _data = getSampleData(); } else { @@ -167,19 +169,17 @@ class _ResultsPageState extends State { } getAllAttributes(_data!); applyMathFunctions(_data!); // Calculate sum and average - print("GOT DATA"); - // print(_data); } Map> getSampleData() { - var rng = Random(); - Map> sampleData = {}; - for (var listObjs in kDataSample.values) { - for (var obj in listObjs) { + final rng = Random(); + final Map> sampleData = {}; + for (final listObjs in kDataSample.values) { + for (final obj in listObjs) { sampleData[obj] = { "height": rng.nextInt(100).toString(), "weight": "45.5", - "vendor": "test" + "vendor": "test", }; } } @@ -187,14 +187,14 @@ class _ResultsPageState extends State { } getAllAttributes(Map> data) { - for (var obj in widget.selectedObjects) { + for (final obj in widget.selectedObjects) { if (data.containsKey(obj)) { - for (var attr in data[obj]!.keys) { + for (final attr in data[obj]!.keys) { _allAttributes.add(attr); } } } - for (String attr in ["height", "size"]) { + for (final String attr in ["height", "size"]) { if (_allAttributes.contains(attr) && !selectedAttrs.contains(attr)) { selectedAttrs.add(attr); } @@ -213,55 +213,60 @@ class _ResultsPageState extends State { // First, the objects column columnLabels = [ DataColumn( - label: const Text( - "Objects", - style: TextStyle(fontWeight: FontWeight.w600), - ), - onSort: (columnIndex, ascending) { - setState(() { - sort = !sort; - sortColumnIndex = columnIndex; - }); - onsortColum(columnIndex, ascending); - }) + label: const Text( + "Objects", + style: TextStyle(fontWeight: FontWeight.w600), + ), + onSort: (columnIndex, ascending) { + setState(() { + sort = !sort; + sortColumnIndex = columnIndex; + }); + onsortColum(columnIndex, ascending); + }, + ), ]; // Then all selected attributes - for (var attr in selectedAttrs) { + for (final attr in selectedAttrs) { if (attr != extraColumn) { - columnLabels.add(DataColumn( + columnLabels.add( + DataColumn( label: Row( - children: [ - Text( - attr, - style: const TextStyle(fontWeight: FontWeight.w600), + children: [ + Text( + attr, + style: const TextStyle(fontWeight: FontWeight.w600), + ), + ], ), - ], - ))); + ), + ); } } // Finally, add new column - columnLabels.add(DataColumn( - label: PopupMenuButton( - tooltip: AppLocalizations.of(context)!.addColumnTip, - offset: const Offset(0, -32), - itemBuilder: (_) => attributesCheckList(selectedAttrs), - onCanceled: () => print('canceled'), - icon: const Icon(Icons.add), + columnLabels.add( + DataColumn( + label: PopupMenuButton( + tooltip: AppLocalizations.of(context)!.addColumnTip, + offset: const Offset(0, -32), + itemBuilder: (_) => attributesCheckList(selectedAttrs), + icon: const Icon(Icons.add), + ), ), - )); + ); } applyMathFunctions(Map> data) { - List mathFunctions = [sumStr, avgStr]; - for (var func in mathFunctions) { + final List mathFunctions = [sumStr, avgStr]; + for (final func in mathFunctions) { data[func] = {}; - for (String attr in _allAttributes) { + for (final String attr in _allAttributes) { double? sum; var count = 0; - for (var obj in widget.selectedObjects) { + for (final obj in widget.selectedObjects) { if (data.containsKey(obj) && data[obj]!.containsKey(attr)) { - var currentValue = data[obj]![attr]!.toString(); - double? value = double.tryParse(currentValue); + final currentValue = data[obj]![attr]!.toString(); + final double? value = double.tryParse(currentValue); if (value != null) { count++; if (sum == null) { @@ -278,7 +283,6 @@ class _ResultsPageState extends State { : (sum / count).toStringAsFixed(2); } } - print(data[func]); } } @@ -288,25 +292,27 @@ class _ResultsPageState extends State { padding: EdgeInsets.zero, height: 0, value: key, - child: StatefulBuilder(builder: (context, localSetState) { - return CheckboxListTile( - controlAffinity: ListTileControlAffinity.leading, - title: Text(key), - value: selectedAttrs.contains(key), - dense: true, - onChanged: (bool? value) { - setState(() { - if (value!) { - selectedAttrs.add(key); - } else { - selectedAttrs.remove(key); - } - addColumnLabels(); - }); - localSetState(() {}); - }, - ); - }), + child: StatefulBuilder( + builder: (context, localSetState) { + return CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + title: Text(key), + value: selectedAttrs.contains(key), + dense: true, + onChanged: (bool? value) { + setState(() { + if (value!) { + selectedAttrs.add(key); + } else { + selectedAttrs.remove(key); + } + addColumnLabels(); + }); + localSetState(() {}); + }, + ); + }, + ), ); }).toList(); } @@ -349,11 +355,11 @@ class _ResultsPageState extends State { // Prepare data final firstRow = ["Objects", ...selectedAttrs]; firstRow.remove(extraColumn); - List> rows = [firstRow]; - for (var obj in widget.selectedObjects) { - List row = []; + final List> rows = [firstRow]; + for (final obj in widget.selectedObjects) { + final List row = []; row.add(obj); - for (String attr in selectedAttrs) { + for (final String attr in selectedAttrs) { if (attr != extraColumn) { String value = "-"; if (_data!.containsKey(obj) && _data![obj]!.containsKey(attr)) { @@ -365,28 +371,8 @@ class _ResultsPageState extends State { rows.add(row); } - // Prepare the file - String csv = const ListToCsvConverter().convert(rows); - final bytes = utf8.encode(csv); - if (kIsWeb) { - // If web, use html to download csv - html.AnchorElement( - href: 'data:application/octet-stream;base64,${base64Encode(bytes)}') - ..setAttribute("download", "report.csv") - ..click(); - } else { - // Save to local filesystem - var path = (await getApplicationDocumentsDirectory()).path; - var fileName = '$path/report.csv'; - var file = File(fileName); - for (var i = 1; await file.exists(); i++) { - fileName = '$path/report ($i).csv'; - file = File(fileName); - } - file.writeAsBytes(bytes, flush: true).then((value) => showSnackBar( - ScaffoldMessenger.of(context), - "${AppLocalizations.of(context)!.fileSavedTo} $fileName")); - } + // Save the file + await saveCSV("report", rows, context); } } @@ -406,13 +392,17 @@ class _DataSource extends DataTableSource { Map>? data; _DataSource( - this.context, this.selectedAttrs, this.selectedObjects, this.data) { + this.context, + this.selectedAttrs, + this.selectedObjects, + this.data, + ) { _rows = getChildren(); } final BuildContext context; late List _rows; - int _selectedCount = 0; + final int _selectedCount = 0; @override DataRow? getRow(int index) { @@ -444,11 +434,11 @@ class _DataSource extends DataTableSource { int get selectedRowCount => _selectedCount; List getChildren() { - List children = []; - for (var obj in selectedObjects) { - List row = []; + final List children = []; + for (final obj in selectedObjects) { + final List row = []; row.add(label(obj, fontWeight: FontWeight.w600)); - for (String attr in selectedAttrs) { + for (final String attr in selectedAttrs) { if (attr != extraColumn) { String value = "-"; if (data!.containsKey(obj) && data![obj]!.containsKey(attr)) { @@ -471,9 +461,10 @@ class _DataSource extends DataTableSource { child: SelectableText( label, style: TextStyle( - fontSize: 14, - fontWeight: fontWeight, - color: label.contains('(') ? Colors.green : null), + fontSize: 14, + fontWeight: fontWeight, + color: label.contains('(') ? Colors.green : null, + ), ), ), ); diff --git a/APP/lib/pages/select_page.dart b/APP/lib/pages/select_page.dart index 833e1a12b..c1d3b62df 100644 --- a/APP/lib/pages/select_page.dart +++ b/APP/lib/pages/select_page.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/appbar.dart'; import 'package:ogree_app/common/definitions.dart'; -import 'package:ogree_app/pages/impact_page.dart'; -import 'package:ogree_app/widgets/projects/project_popup.dart'; import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/models/project.dart'; +import 'package:ogree_app/pages/impact_page.dart'; import 'package:ogree_app/pages/projects_page.dart'; import 'package:ogree_app/pages/results_page.dart'; -import 'package:ogree_app/widgets/select_objects/select_objects.dart'; +import 'package:ogree_app/widgets/projects/project_popup.dart'; import 'package:ogree_app/widgets/select_date.dart'; import 'package:ogree_app/widgets/select_namespace.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:ogree_app/widgets/select_objects/select_objects.dart'; enum Steps { date, namespace, objects, result } @@ -20,11 +20,12 @@ class SelectPage extends StatefulWidget { final String userEmail; Project? project; bool isImpact; - SelectPage( - {super.key, - this.project, - required this.userEmail, - this.isImpact = false}); + SelectPage({ + super.key, + this.project, + required this.userEmail, + this.isImpact = false, + }); @override State createState() => SelectPageState(); @@ -62,10 +63,11 @@ class SelectPageState extends State with TickerProviderStateMixin { // select date and namespace from project _selectedDate = widget.project!.dateRange; _selectedNamespace = Namespace.values.firstWhere( - (e) => e.toString() == 'Namespace.${widget.project!.namespace}'); + (e) => e.toString() == 'Namespace.${widget.project!.namespace}', + ); _selectedAttrs = widget.project!.attributes; // select objects - for (var obj in widget.project!.objects) { + for (final obj in widget.project!.objects) { _selectedObjects[obj] = true; } // adjust step @@ -92,101 +94,115 @@ class SelectPageState extends State with TickerProviderStateMixin { return Scaffold( appBar: myAppBar(context, widget.userEmail), body: Center( - child: Stepper( - type: MediaQuery.of(context).size.width > 650 - ? StepperType.horizontal - : StepperType.vertical, - physics: const ScrollPhysics(), - currentStep: _currentStep.index, - // onStepTapped: (step) => tapped(step), - controlsBuilder: (context, _) { - return Padding( - padding: const EdgeInsets.only(top: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextButton( - onPressed: cancel, - child: Text(_currentStep == Steps.values.first - ? localeMsg.cancel - : localeMsg.back), - ), - ElevatedButton( - onPressed: continued, - child: Text(_currentStep == Steps.values.last - ? localeMsg.save - : localeMsg.next), - ), - ], + child: Stepper( + type: MediaQuery.of(context).size.width > 650 + ? StepperType.horizontal + : StepperType.vertical, + physics: const ScrollPhysics(), + currentStep: _currentStep.index, + // onStepTapped: (step) => tapped(step), + controlsBuilder: (context, _) { + return Padding( + padding: const EdgeInsets.only(top: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextButton( + onPressed: cancel, + child: Text( + _currentStep == Steps.values.first + ? localeMsg.cancel + : localeMsg.back, + ), + ), + ElevatedButton( + onPressed: continued, + child: Text( + _currentStep == Steps.values.last + ? localeMsg.save + : localeMsg.next, + ), + ), + ], + ), + ); + }, + steps: [ + Step( + title: Text( + localeMsg.selectDate, + style: const TextStyle(fontSize: 14), + ), + subtitle: _selectedDate != "" + ? Text(_selectedDate) + : const Icon(Icons.all_inclusive, size: 15), + content: const SelectDate(), + isActive: _currentStep.index >= Steps.date.index, + state: _currentStep.index >= Steps.date.index + ? StepState.complete + : StepState.disabled, ), - ); - }, - steps: [ - Step( - title: Text(localeMsg.selectDate, - style: const TextStyle(fontSize: 14)), - subtitle: _selectedDate != "" - ? Text(_selectedDate) - : const Icon(Icons.all_inclusive, size: 15), - content: const SelectDate(), - isActive: _currentStep.index >= Steps.date.index, - state: _currentStep.index >= Steps.date.index - ? StepState.complete - : StepState.disabled, - ), - Step( - title: Text(localeMsg.selectNamespace, - style: const TextStyle(fontSize: 14)), - subtitle: _selectedNamespace == Namespace.Test - ? null - : Text(_selectedNamespace.name), - content: const SelectNamespace(), - isActive: _currentStep.index >= Steps.date.index, - state: _currentStep.index >= Steps.namespace.index - ? StepState.complete - : StepState.disabled, - ), - Step( - title: Text(localeMsg.selectObjects, - style: const TextStyle(fontSize: 14)), - subtitle: _selectedObjects.keys.isNotEmpty - ? Text(localeMsg.nObjects(_selectedObjects.keys.length)) - : null, - content: SizedBox( + Step( + title: Text( + localeMsg.selectNamespace, + style: const TextStyle(fontSize: 14), + ), + subtitle: _selectedNamespace == Namespace.Test + ? null + : Text(_selectedNamespace.name), + content: const SelectNamespace(), + isActive: _currentStep.index >= Steps.date.index, + state: _currentStep.index >= Steps.namespace.index + ? StepState.complete + : StepState.disabled, + ), + Step( + title: Text( + localeMsg.selectObjects, + style: const TextStyle(fontSize: 14), + ), + subtitle: _selectedObjects.keys.isNotEmpty + ? Text(localeMsg.nObjects(_selectedObjects.keys.length)) + : null, + content: SizedBox( height: MediaQuery.of(context).size.height > 205 ? MediaQuery.of(context).size.height - 220 : MediaQuery.of(context).size.height, child: SelectObjects( - dateRange: _selectedDate, - namespace: _selectedNamespace, - load: _loadObjects)), - isActive: _currentStep.index >= Steps.date.index, - state: _currentStep.index >= Steps.objects.index - ? StepState.complete - : StepState.disabled, - ), - Step( - title: Text(localeMsg.result, style: const TextStyle(fontSize: 14)), - content: _currentStep == Steps.result - ? (widget.isImpact - ? ImpactPage( - selectedObjects: _selectedObjects.keys.toList(), - ) - : ResultsPage( - dateRange: _selectedDate, - selectedAttrs: _selectedAttrs, - selectedObjects: _selectedObjects.keys.toList(), - namespace: _selectedNamespace.name, - )) - : const Center(child: CircularProgressIndicator()), - isActive: _currentStep.index >= Steps.date.index, - state: _currentStep.index >= Steps.result.index - ? StepState.complete - : StepState.disabled, - ), - ], - )), + dateRange: _selectedDate, + namespace: _selectedNamespace, + load: _loadObjects, + ), + ), + isActive: _currentStep.index >= Steps.date.index, + state: _currentStep.index >= Steps.objects.index + ? StepState.complete + : StepState.disabled, + ), + Step( + title: + Text(localeMsg.result, style: const TextStyle(fontSize: 14)), + content: _currentStep == Steps.result + ? (widget.isImpact + ? ImpactPage( + selectedObjects: _selectedObjects.keys.toList(), + ) + : ResultsPage( + dateRange: _selectedDate, + selectedAttrs: _selectedAttrs, + selectedObjects: _selectedObjects.keys.toList(), + namespace: _selectedNamespace.name, + )) + : const Center(child: CircularProgressIndicator()), + isActive: _currentStep.index >= Steps.date.index, + state: _currentStep.index >= Steps.result.index + ? StepState.complete + : StepState.disabled, + ), + ], + ), + ), ); } @@ -204,8 +220,10 @@ class SelectPageState extends State with TickerProviderStateMixin { if (_selectedObjects.isEmpty) { // Should select at least one OBJECT before continue showSnackBar( - ScaffoldMessenger.of(context), localeMsg.atLeastOneObject, - isError: true); + ScaffoldMessenger.of(context), + localeMsg.atLeastOneObject, + isError: true, + ); } else { _selectedAttrs = []; setState(() => _currentStep = Steps.result); @@ -225,27 +243,32 @@ class SelectPageState extends State with TickerProviderStateMixin { } else { // Saving new project project = Project( - "", - _selectedDate, - _selectedNamespace.name, - widget.userEmail, - DateFormat('dd/MM/yyyy').format(DateTime.now()), - true, - true, - false, - _selectedAttrs, - _selectedObjects.keys.toList(), - [widget.userEmail], - isImpact: widget.isImpact); + "", + _selectedDate, + _selectedNamespace.name, + widget.userEmail, + DateFormat('dd/MM/yyyy').format(DateTime.now()), + true, + true, + false, + _selectedAttrs, + _selectedObjects.keys.toList(), + [widget.userEmail], + isImpact: widget.isImpact, + ); } showProjectDialog( - context, project, localeMsg.nameProject, saveProjectCallback, - isCreate: isCreate); + context, + project, + localeMsg.nameProject, + saveProjectCallback, + isCreate: isCreate, + ); } } - cancel() { + void cancel() { _loadObjects = false; switch (_currentStep) { case (Steps.date): @@ -272,8 +295,12 @@ class SelectPageState extends State with TickerProviderStateMixin { } } - saveProjectCallback(String userInput, Project project, bool isCreate, - Function? callback) async { + saveProjectCallback( + String userInput, + Project project, + bool isCreate, + Function? callback, + ) async { Result result; project.name = userInput; final messenger = ScaffoldMessenger.of(context); diff --git a/APP/lib/pages/tenant_page.dart b/APP/lib/pages/tenant_page.dart index 4f25b6c4b..a728935c2 100644 --- a/APP/lib/pages/tenant_page.dart +++ b/APP/lib/pages/tenant_page.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/appbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/models/tenant.dart'; import 'package:ogree_app/pages/projects_page.dart'; +import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; import 'package:ogree_app/widgets/tenants/api_stats_view.dart'; +import 'package:ogree_app/widgets/tenants/docker_view.dart'; import 'package:ogree_app/widgets/tenants/domain_view.dart'; import 'package:ogree_app/widgets/tenants/locked_view.dart'; -import 'package:ogree_app/widgets/tenants/docker_view.dart'; -import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; import 'package:ogree_app/widgets/tenants/tags_view.dart'; import 'package:ogree_app/widgets/tenants/user_view.dart'; @@ -44,7 +44,7 @@ class TenantPageState extends State with TickerProviderStateMixin { return Scaffold( backgroundColor: const Color.fromARGB(255, 238, 238, 241), appBar: myAppBar(context, widget.userEmail, - isTenantMode: widget.tenant != null), + isTenantMode: widget.tenant != null,), body: Padding( padding: const EdgeInsets.all(20.0), child: CustomScrollView(slivers: [ @@ -62,12 +62,12 @@ class TenantPageState extends State with TickerProviderStateMixin { Navigator.of(context).push(MaterialPageRoute( builder: (context) => ProjectsPage( userEmail: widget.userEmail, - isTenantMode: widget.tenant != null), - )), + isTenantMode: widget.tenant != null,), + ),), icon: Icon( Icons.arrow_back, color: Colors.blue.shade900, - )), + ),), const SizedBox(width: 5), Text( "Tenant $tenantName", @@ -90,10 +90,10 @@ class TenantPageState extends State with TickerProviderStateMixin { unselectedLabelColor: Colors.grey, labelStyle: TextStyle( fontSize: 14, - fontFamily: GoogleFonts.inter().fontFamily), + fontFamily: GoogleFonts.inter().fontFamily,), unselectedLabelStyle: TextStyle( fontSize: 14, - fontFamily: GoogleFonts.inter().fontFamily), + fontFamily: GoogleFonts.inter().fontFamily,), isScrollable: true, indicatorSize: TabBarIndicatorSize.label, tabs: getTabs(localeMsg), @@ -115,13 +115,13 @@ class TenantPageState extends State with TickerProviderStateMixin { ), ], ), - ) - ]), - )); + ), + ],), + ),); } List getTabs(AppLocalizations localeMsg) { - List tabs = [ + final List tabs = [ Tab( text: localeMsg.apiStats, icon: const Icon(Icons.info), @@ -145,22 +145,16 @@ class TenantPageState extends State with TickerProviderStateMixin { Tab( text: localeMsg.deployment, icon: const Icon(Icons.dns), - )); + ),); } return tabs; } List getTabViews(AppLocalizations localeMsg) { - List views = [ - _isLocked && widget.tenant != null - ? LockedView(tenant: widget.tenant!, parentCallback: unlockView) - : const ApiStatsView(), - _isLocked && widget.tenant != null - ? LockedView(tenant: widget.tenant!, parentCallback: unlockView) - : const DomainView(), - _isLocked && widget.tenant != null - ? LockedView(tenant: widget.tenant!, parentCallback: unlockView) - : (_domainSearch.isEmpty + final List views = [ + if (_isLocked && widget.tenant != null) LockedView(tenant: widget.tenant!, parentCallback: unlockView) else const ApiStatsView(), + if (_isLocked && widget.tenant != null) LockedView(tenant: widget.tenant!, parentCallback: unlockView) else const DomainView(), + if (_isLocked && widget.tenant != null) LockedView(tenant: widget.tenant!, parentCallback: unlockView) else _domainSearch.isEmpty ? UserView() : // user view should filter with domain UserView( @@ -170,10 +164,8 @@ class TenantPageState extends State with TickerProviderStateMixin { // child calls parent to clean it once applied _domainSearch = ""; }), - )), - _isLocked && widget.tenant != null - ? LockedView(tenant: widget.tenant!, parentCallback: unlockView) - : const TagsView(), + ), + if (_isLocked && widget.tenant != null) LockedView(tenant: widget.tenant!, parentCallback: unlockView) else const TagsView(), ]; if (_domainSearch.isNotEmpty) { // switch to domain page diff --git a/APP/lib/pages/unity_page.dart b/APP/lib/pages/unity_page.dart index 9e303316f..f680bd175 100644 --- a/APP/lib/pages/unity_page.dart +++ b/APP/lib/pages/unity_page.dart @@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:ogree_app/common/appbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/models/tenant.dart'; import 'package:ogree_app/pages/projects_page.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -25,11 +24,12 @@ class UnityPageState extends State with TickerProviderStateMixin { InAppWebViewController? webViewController; InAppWebViewSettings settings = InAppWebViewSettings( - isInspectable: kDebugMode, - mediaPlaybackRequiresUserGesture: false, - allowsInlineMediaPlayback: true, - iframeAllow: "camera; microphone", - iframeAllowFullscreen: true); + isInspectable: kDebugMode, + mediaPlaybackRequiresUserGesture: false, + allowsInlineMediaPlayback: true, + iframeAllow: "camera; microphone", + iframeAllowFullscreen: true, + ); PullToRefreshController? pullToRefreshController; String url = ""; @@ -39,150 +39,134 @@ class UnityPageState extends State with TickerProviderStateMixin { @override void initState() { super.initState(); - - pullToRefreshController = true //kIsWeb - ? null - : PullToRefreshController( - settings: PullToRefreshSettings( - color: Colors.blue, - ), - onRefresh: () async { - if (defaultTargetPlatform == TargetPlatform.android) { - webViewController?.reload(); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - webViewController?.loadUrl( - urlRequest: - URLRequest(url: await webViewController?.getUrl())); - } - }, - ); } @override Widget build(BuildContext context) { - final localeMsg = AppLocalizations.of(context)!; return Scaffold( - backgroundColor: const Color.fromARGB(255, 238, 238, 241), - appBar: myAppBar(context, widget.userEmail, - isTenantMode: widget.tenant != null), - body: SafeArea( - child: Column(children: [ - // TextField( - // decoration: const InputDecoration(prefixIcon: Icon(Icons.search)), - // controller: urlController, - // keyboardType: TextInputType.url, - // onSubmitted: (value) { - // var url = WebUri(value); - // if (url.scheme.isEmpty) { - // url = WebUri("https://www.google.com/search?q=$value"); - // } - // webViewController?.loadUrl(urlRequest: URLRequest(url: url)); - // }, - // ), - Expanded( - child: Stack( - children: [ - InAppWebView( - key: webViewKey, - initialUrlRequest: URLRequest( - url: WebUri("https://unity.kube.ogree.ditrit.io")), - initialSettings: settings, - pullToRefreshController: pullToRefreshController, - onWebViewCreated: (controller) { - webViewController = controller; - }, - onLoadStart: (controller, url) { - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onPermissionRequest: (controller, request) async { - return PermissionResponse( + backgroundColor: const Color.fromARGB(255, 238, 238, 241), + appBar: myAppBar( + context, + widget.userEmail, + isTenantMode: widget.tenant != null, + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Stack( + children: [ + InAppWebView( + key: webViewKey, + initialUrlRequest: URLRequest( + url: WebUri("https://unity.kube.ogree.ditrit.io"), + ), + initialSettings: settings, + pullToRefreshController: pullToRefreshController, + onWebViewCreated: (controller) { + webViewController = controller; + }, + onLoadStart: (controller, url) { + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onPermissionRequest: (controller, request) async { + return PermissionResponse( resources: request.resources, - action: PermissionResponseAction.GRANT); - }, - shouldOverrideUrlLoading: - (controller, navigationAction) async { - var uri = navigationAction.request.url!; + action: PermissionResponseAction.GRANT, + ); + }, + shouldOverrideUrlLoading: + (controller, navigationAction) async { + final uri = navigationAction.request.url!; - if (![ - "http", - "https", - "file", - "chrome", - "data", - "javascript", - "about" - ].contains(uri.scheme)) { - if (await canLaunchUrl(uri)) { - // Launch the App - await launchUrl( - uri, - ); - // and cancel the request - return NavigationActionPolicy.CANCEL; + if (![ + "http", + "https", + "file", + "chrome", + "data", + "javascript", + "about", + ].contains(uri.scheme)) { + if (await canLaunchUrl(uri)) { + // Launch the App + await launchUrl( + uri, + ); + // and cancel the request + return NavigationActionPolicy.CANCEL; + } } - } - return NavigationActionPolicy.ALLOW; - }, - onLoadStop: (controller, url) async { - pullToRefreshController?.endRefreshing(); - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onReceivedError: (controller, request, error) { - pullToRefreshController?.endRefreshing(); - }, - onProgressChanged: (controller, progress) { - if (progress == 100) { + return NavigationActionPolicy.ALLOW; + }, + onLoadStop: (controller, url) async { pullToRefreshController?.endRefreshing(); - } - setState(() { - this.progress = progress / 100; - urlController.text = url; - }); - }, - onUpdateVisitedHistory: (controller, url, androidIsReload) { - setState(() { - this.url = url.toString(); - urlController.text = this.url; - }); - }, - onConsoleMessage: (controller, consoleMessage) { - if (kDebugMode) { - print(consoleMessage); - } + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onReceivedError: (controller, request, error) { + pullToRefreshController?.endRefreshing(); + }, + onProgressChanged: (controller, progress) { + if (progress == 100) { + pullToRefreshController?.endRefreshing(); + } + setState(() { + this.progress = progress / 100; + urlController.text = url; + }); + }, + onUpdateVisitedHistory: (controller, url, androidIsReload) { + setState(() { + this.url = url.toString(); + urlController.text = this.url; + }); + }, + onConsoleMessage: (controller, consoleMessage) { + if (kDebugMode) { + print(consoleMessage); + } + }, + ), + if (progress < 1.0) + LinearProgressIndicator(value: progress) + else + Container(), + ], + ), + ), + OverflowBar( + alignment: MainAxisAlignment.center, + spacing: 10, + children: [ + ElevatedButton( + child: const Icon(Icons.arrow_back), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ProjectsPage( + userEmail: widget.userEmail, + isTenantMode: widget.tenant != null, + ), + ), + ), + ), + ElevatedButton( + child: const Icon(Icons.refresh), + onPressed: () { + webViewController?.reload(); }, ), - progress < 1.0 - ? LinearProgressIndicator(value: progress) - : Container(), ], ), - ), - ButtonBar( - alignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - child: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).push(MaterialPageRoute( - builder: (context) => ProjectsPage( - userEmail: widget.userEmail, - isTenantMode: widget.tenant != null), - )), - ), - ElevatedButton( - child: const Icon(Icons.refresh), - onPressed: () { - webViewController?.reload(); - }, - ), - ], - ), - ]))); + ], + ), + ), + ); } } diff --git a/APP/lib/widgets/common/actionbtn_row.dart b/APP/lib/widgets/common/actionbtn_row.dart index 8c4700bae..7cdff63a3 100644 --- a/APP/lib/widgets/common/actionbtn_row.dart +++ b/APP/lib/widgets/common/actionbtn_row.dart @@ -15,7 +15,7 @@ class ActionBtnRow extends StatefulWidget { required this.submitCreate, required this.submitModify, required this.submitDelete, - this.onlyDelete = false}); + this.onlyDelete = false,}); @override State createState() => _ActionBtnRowState(); } @@ -43,10 +43,9 @@ class _ActionBtnRowState extends State { ), ), // const SizedBox(width: 15), - widget.isEdit - ? TextButton.icon( + if (widget.isEdit) TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.red.shade900), + foregroundColor: Colors.red.shade900,), onPressed: () async { setState(() { _isLoadingDelete = true; @@ -71,12 +70,9 @@ class _ActionBtnRowState extends State { Icons.delete, size: 16, ), - ) - : Container(), - _isSmallDisplay ? Container() : const SizedBox(width: 10), - widget.onlyDelete - ? Container() - : ElevatedButton.icon( + ) else Container(), + if (_isSmallDisplay) Container() else const SizedBox(width: 10), + if (widget.onlyDelete) Container() else ElevatedButton.icon( onPressed: () async { setState(() { _isLoading = true; @@ -100,7 +96,7 @@ class _ActionBtnRowState extends State { strokeWidth: 3, ), ) - : const Icon(Icons.check_circle, size: 16)) + : const Icon(Icons.check_circle, size: 16),), ], ); } diff --git a/APP/lib/widgets/common/delete_dialog_popup.dart b/APP/lib/widgets/common/delete_dialog_popup.dart index 5506522e0..ad9e00388 100644 --- a/APP/lib/widgets/common/delete_dialog_popup.dart +++ b/APP/lib/widgets/common/delete_dialog_popup.dart @@ -10,11 +10,12 @@ class DeleteDialog extends StatefulWidget { final List objName; final String objType; final Function parentCallback; - const DeleteDialog( - {super.key, - required this.objName, - required this.parentCallback, - required this.objType}); + const DeleteDialog({ + super.key, + required this.objName, + required this.parentCallback, + required this.objType, + }); @override State createState() => _DeleteDialogState(); @@ -39,8 +40,10 @@ class _DeleteDialogState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(localeMsg.areYouSure, - style: Theme.of(context).textTheme.headlineMedium), + Text( + localeMsg.areYouSure, + style: Theme.of(context).textTheme.headlineMedium, + ), Padding( padding: const EdgeInsets.symmetric(vertical: 30), child: Text(localeMsg.allWillBeLost), @@ -50,37 +53,9 @@ class _DeleteDialogState extends State { children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.red.shade900), - onPressed: () async { - setState(() => _isLoading = true); - final messenger = ScaffoldMessenger.of(context); - for (var obj in widget.objName) { - Result result; - if (widget.objType == "tenants") { - result = await deleteTenant(obj); - } else if (Tools.values - .where((tool) => tool.name == widget.objType) - .isNotEmpty) { - result = await deleteTool(widget.objType); - } else { - result = await removeObject(obj, widget.objType); - } - switch (result) { - case Success(): - break; - case Failure(exception: final exception): - showSnackBar(messenger, "Error: $exception", - isError: true, - duration: const Duration(seconds: 30)); - setState(() => _isLoading = false); - return; - } - } - setState(() => _isLoading = false); - widget.parentCallback(); - showSnackBar(messenger, localeMsg.deleteOK); - if (context.mounted) Navigator.of(context).pop(); - }, + foregroundColor: Colors.red.shade900, + ), + onPressed: () => deleteAction(localeMsg), label: Text(localeMsg.delete), icon: _isLoading ? Container( @@ -101,9 +76,9 @@ class _DeleteDialogState extends State { ElevatedButton( onPressed: () => Navigator.of(context).pop(), child: Text(localeMsg.cancel), - ) + ), ], - ) + ), ], ), ), @@ -111,4 +86,38 @@ class _DeleteDialogState extends State { ), ); } + + deleteAction(AppLocalizations localeMsg) async { + setState(() => _isLoading = true); + final messenger = ScaffoldMessenger.of(context); + for (final obj in widget.objName) { + Result result; + if (widget.objType == "tenants") { + result = await deleteTenant(obj); + } else if (Tools.values + .where((tool) => tool.name == widget.objType) + .isNotEmpty) { + result = await deleteTool(widget.objType); + } else { + result = await removeObject(obj, widget.objType); + } + switch (result) { + case Success(): + break; + case Failure(exception: final exception): + showSnackBar( + messenger, + "Error: $exception", + isError: true, + duration: const Duration(seconds: 30), + ); + setState(() => _isLoading = false); + return; + } + } + setState(() => _isLoading = false); + widget.parentCallback(); + showSnackBar(messenger, localeMsg.deleteOK); + if (mounted) Navigator.of(context).pop(); + } } diff --git a/APP/lib/widgets/common/form_field.dart b/APP/lib/widgets/common/form_field.dart index aedd5dd28..810ce6286 100644 --- a/APP/lib/widgets/common/form_field.dart +++ b/APP/lib/widgets/common/form_field.dart @@ -1,8 +1,8 @@ import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:ogree_app/common/theme.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:ogree_app/common/theme.dart'; class CustomFormField extends StatefulWidget { Function(String?) save; @@ -24,26 +24,27 @@ class CustomFormField extends StatefulWidget { TextEditingController? checkListController; List? checkListValues; - CustomFormField( - {super.key, - required this.save, - required this.label, - required this.icon, - this.formatters, - this.initialValue, - this.shouldValidate = true, - this.isColor = false, - this.colorTextController, - this.maxLength, - this.isCompact = false, - this.tipStr = "", - this.prefix, - this.suffix, - this.isObscure = false, - this.isReadOnly = false, - this.extraValidationFn, - this.checkListController, - this.checkListValues}); + CustomFormField({ + super.key, + required this.save, + required this.label, + required this.icon, + this.formatters, + this.initialValue, + this.shouldValidate = true, + this.isColor = false, + this.colorTextController, + this.maxLength, + this.isCompact = false, + this.tipStr = "", + this.prefix, + this.suffix, + this.isObscure = false, + this.isReadOnly = false, + this.extraValidationFn, + this.checkListController, + this.checkListValues, + }); @override State createState() => _CustomFormFieldState(); @@ -75,7 +76,7 @@ class _CustomFormFieldState extends State { offset: const Offset(0, -32), itemBuilder: (_) => checkListPopupItems(widget.checkListValues!), onCanceled: () { - String content = selectedCheckListItems.toString(); + final String content = selectedCheckListItems.toString(); widget.checkListController!.text = content.substring(1, content.length - 1).replaceAll(" ", ""); }, @@ -83,7 +84,6 @@ class _CustomFormFieldState extends State { ); } if (widget.isColor) { - print(widget.colorTextController!.text); if (widget.colorTextController!.text != "") { _localColor = Color(int.parse("0xFF${widget.colorTextController!.text}")); @@ -103,7 +103,9 @@ class _CustomFormFieldState extends State { // Wait for the picker to close, if dialog was dismissed, // then restore the color we had before it was opened. if (!(await colorPickerDialog( - localeMsg, widget.colorTextController!))) { + localeMsg, + widget.colorTextController!, + ))) { setState(() { if (!wasNull) { _localColor = colorBeforeDialog; @@ -166,17 +168,19 @@ class _CustomFormFieldState extends State { }, maxLength: widget.maxLength, inputFormatters: widget.isColor - ? [FilteringTextInputFormatter.allow(RegExp(r'[0-9a-fA-F]'))] + ? [FilteringTextInputFormatter.allow(RegExp('[0-9a-fA-F]'))] : widget.formatters, initialValue: widget.isColor ? null : widget.initialValue, decoration: GetFormInputDecoration( - isSmallDisplay || widget.isCompact, widget.label, - icon: widget.icon, - iconColor: widget.isColor ? _localColor : null, - iconWidget: iconWidget, - prefixText: widget.prefix, - suffixText: widget.suffix, - isCompact: widget.isCompact), + isSmallDisplay || widget.isCompact, + widget.label, + icon: widget.icon, + iconColor: widget.isColor ? _localColor : null, + iconWidget: iconWidget, + prefixText: widget.prefix, + suffixText: widget.suffix, + isCompact: widget.isCompact, + ), cursorWidth: 1.3, style: const TextStyle(fontSize: 14), ), @@ -184,16 +188,16 @@ class _CustomFormFieldState extends State { ); } - Future colorPickerDialog(AppLocalizations localeMsg, - TextEditingController colorTextController) async { + Future colorPickerDialog( + AppLocalizations localeMsg, + TextEditingController colorTextController, + ) async { return ColorPicker( color: _localColor ?? _defaultColor, onColorChanged: (Color color) => setState(() { colorTextController.text = color.toString().substring(10, 16); _localColor = color; }), - width: 40, - height: 40, borderRadius: 4, spacing: 5, runSpacing: 5, @@ -204,8 +208,6 @@ class _CustomFormFieldState extends State { localeMsg.selectColor, style: Theme.of(context).textTheme.titleSmall, ), - showMaterialName: false, - showColorName: false, showColorCode: true, colorCodeHasColor: true, colorCodePrefixStyle: @@ -229,8 +231,12 @@ class _CustomFormFieldState extends State { context, backgroundColor: Colors.white, actionsPadding: const EdgeInsets.only(bottom: 10, right: 22), - transitionBuilder: (BuildContext context, Animation a1, - Animation a2, Widget widget) { + transitionBuilder: ( + BuildContext context, + Animation a1, + Animation a2, + Widget widget, + ) { final double curvedValue = Curves.easeInOutBack.transform(a1.value) - 1.0; return Transform( @@ -253,24 +259,26 @@ class _CustomFormFieldState extends State { padding: EdgeInsets.zero, height: 0, value: key, - child: StatefulBuilder(builder: (context, localSetState) { - return CheckboxListTile( - controlAffinity: ListTileControlAffinity.leading, - title: Text(key), - value: selectedCheckListItems.contains(key), - dense: true, - onChanged: (bool? value) { - setState(() { - if (value!) { - selectedCheckListItems.add(key); - } else { - selectedCheckListItems.remove(key); - } - }); - localSetState(() {}); - }, - ); - }), + child: StatefulBuilder( + builder: (context, localSetState) { + return CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + title: Text(key), + value: selectedCheckListItems.contains(key), + dense: true, + onChanged: (bool? value) { + setState(() { + if (value!) { + selectedCheckListItems.add(key); + } else { + selectedCheckListItems.remove(key); + } + }); + localSetState(() {}); + }, + ); + }, + ), ); }).toList(); } diff --git a/APP/lib/widgets/common/language_toggle.dart b/APP/lib/widgets/common/language_toggle.dart index 11a5ca808..2be5c7aea 100644 --- a/APP/lib/widgets/common/language_toggle.dart +++ b/APP/lib/widgets/common/language_toggle.dart @@ -1,7 +1,7 @@ import 'package:flag/flag.dart'; import 'package:flutter/material.dart'; -import '../../main.dart'; +import 'package:ogree_app/main.dart'; class LanguageOption { Flag flag; @@ -9,7 +9,7 @@ class LanguageOption { Locale locale; LanguageOption( - {required this.flag, required this.name, required this.locale}); + {required this.flag, required this.name, required this.locale,}); } class LanguageToggle extends StatefulWidget { @@ -32,9 +32,8 @@ class _LanguageToggleState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), border: Border.all( - width: 1, color: Colors.white, - )), + ),), child: Padding(padding: const EdgeInsets.all(0.5), child: flag), ); late List languages; @@ -47,13 +46,13 @@ class _LanguageToggleState extends State { LanguageOption( flag: getFlag("FR"), name: 'Français', - locale: const Locale('fr', 'FR')), + locale: const Locale('fr', 'FR'),), LanguageOption( - flag: getFlag("GB"), name: 'English', locale: const Locale('en')), + flag: getFlag("GB"), name: 'English', locale: const Locale('en'),), LanguageOption( - flag: getFlag("ES"), name: 'Español', locale: const Locale('es')), + flag: getFlag("ES"), name: 'Español', locale: const Locale('es'),), LanguageOption( - flag: getFlag("BR"), name: 'Português', locale: const Locale('pt')), + flag: getFlag("BR"), name: 'Português', locale: const Locale('pt'),), ]; _selectedLanguage = languages.first; } @@ -63,7 +62,7 @@ class _LanguageToggleState extends State { if (MyApp.of(context) != null && MyApp.of(context)!.getLocale() != _selectedLanguage.locale) { _selectedLanguage = languages.firstWhere( - (language) => language.locale == MyApp.of(context)!.getLocale()); + (language) => language.locale == MyApp.of(context)!.getLocale(),); } return PopupMenuButton( child: flagWrapper(_selectedLanguage.flag), diff --git a/APP/lib/widgets/impact/impact_graph_view.dart b/APP/lib/widgets/impact/impact_graph_view.dart index 9960e4cc4..cd54ea6e6 100644 --- a/APP/lib/widgets/impact/impact_graph_view.dart +++ b/APP/lib/widgets/impact/impact_graph_view.dart @@ -1,20 +1,15 @@ import 'package:flutter/material.dart'; import 'package:graphview/GraphView.dart'; -import 'package:ogree_app/common/api_backend.dart'; -import 'package:ogree_app/common/definitions.dart'; -import 'package:ogree_app/common/snackbar.dart'; -import 'package:ogree_app/common/theme.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ImpactGraphView extends StatefulWidget { - String rootId; - Map data; - ImpactGraphView(this.rootId, this.data); + final String rootId; + final Map data; + const ImpactGraphView(this.rootId, this.data, {super.key}); @override - _ImpactGraphViewState createState() => _ImpactGraphViewState(); + ImpactGraphViewState createState() => ImpactGraphViewState(); } -class _ImpactGraphViewState extends State { +class ImpactGraphViewState extends State { bool loaded = false; Map idCategory = {}; @@ -46,33 +41,32 @@ class _ImpactGraphViewState extends State { constraints: const BoxConstraints(maxHeight: 400, maxWidth: 1000), decoration: BoxDecoration( border: Border.all(color: Colors.lightBlue.shade100), - borderRadius: BorderRadius.all(Radius.circular(15.0)), + borderRadius: const BorderRadius.all(Radius.circular(15.0)), ), child: InteractiveViewer( - alignment: Alignment.center, - constrained: true, - boundaryMargin: const EdgeInsets.all(double.infinity), - minScale: 0.0001, - maxScale: 10.6, - child: OverflowBox( - alignment: Alignment.center, - minWidth: 0.0, - minHeight: 0.0, - maxWidth: double.infinity, - maxHeight: double.infinity, - child: GraphView( - graph: graph, - algorithm: SugiyamaAlgorithm(builder), - paint: Paint() - ..color = Colors.blue - ..strokeWidth = 1 - ..style = PaintingStyle.stroke, - builder: (Node node) { - var a = node.key!.value as String?; - return rectangleWidget(a!); - }, - ), - )), + alignment: Alignment.center, + boundaryMargin: const EdgeInsets.all(double.infinity), + minScale: 0.0001, + maxScale: 10.6, + child: OverflowBox( + minWidth: 0.0, + minHeight: 0.0, + maxWidth: double.infinity, + maxHeight: double.infinity, + child: GraphView( + graph: graph, + algorithm: SugiyamaAlgorithm(builder), + paint: Paint() + ..color = Colors.blue + ..strokeWidth = 1 + ..style = PaintingStyle.stroke, + builder: (Node node) { + final a = node.key!.value as String?; + return rectangleWidget(a!); + }, + ), + ), + ), ), ), ); @@ -82,18 +76,20 @@ class _ImpactGraphViewState extends State { return Tooltip( message: a, child: Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: idCategory[a] == "virtual_obj" - ? Colors.purple[100]! - : Colors.blue[100]!, - spreadRadius: 1), - ], - ), - child: Text('${a.split(".").last}')), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + boxShadow: [ + BoxShadow( + color: idCategory[a] == "virtual_obj" + ? Colors.purple[100]! + : Colors.blue[100]!, + spreadRadius: 1, + ), + ], + ), + child: Text(a.split(".").last), + ), ); } @@ -105,8 +101,8 @@ class _ImpactGraphViewState extends State { while (key.contains(".") && key != widget.rootId && !graph.hasPredecessor(node)) { - var predecessorId = key.substring(0, key.lastIndexOf(".")); - var predecessor = Node.Id(predecessorId); + final predecessorId = key.substring(0, key.lastIndexOf(".")); + final predecessor = Node.Id(predecessorId); graph.addEdge(predecessor, node); node = predecessor; key = predecessorId; @@ -116,15 +112,18 @@ class _ImpactGraphViewState extends State { } addIndirectRelationsToGraph(Map value) { - for (var key in value.keys) { + for (final key in value.keys) { final node = Node.Id(key); if (!graph.contains(node: node)) { graph.addNode(node); } - for (var childId in value[key]) { - graph.addEdge(Node.Id(childId), node, - paint: Paint()..color = Colors.purple); + for (final childId in value[key]) { + graph.addEdge( + Node.Id(childId), + node, + paint: Paint()..color = Colors.purple, + ); } } } diff --git a/APP/lib/widgets/impact/impact_popup.dart b/APP/lib/widgets/impact/impact_popup.dart index 0ba3250a5..1a69f69f5 100644 --- a/APP/lib/widgets/impact/impact_popup.dart +++ b/APP/lib/widgets/impact/impact_popup.dart @@ -7,21 +7,24 @@ class ImpactOptionsPopup extends StatefulWidget { List selectedCategories; List selectedPtypes; List selectedVtypes; - final Function(List selectedCategories, List selectedPtypes, - List selectedVtypes) parentCallback; - ImpactOptionsPopup( - {super.key, - required this.selectedCategories, - required this.selectedPtypes, - required this.selectedVtypes, - required this.parentCallback}); + final Function( + List selectedCategories, + List selectedPtypes, + List selectedVtypes, + ) parentCallback; + ImpactOptionsPopup({ + super.key, + required this.selectedCategories, + required this.selectedPtypes, + required this.selectedVtypes, + required this.parentCallback, + }); @override State createState() => _ImpactOptionsPopupState(); } class _ImpactOptionsPopupState extends State { - bool _isSmallDisplay = false; List ptypes = ["blade", "chassis", "disk", "processor"]; List vtypes = ["application", "cluster", "storage", "vm"]; late List selectedCategories; @@ -39,8 +42,6 @@ class _ImpactOptionsPopupState extends State { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - _isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); - return Center( child: Container( height: 330, @@ -51,69 +52,73 @@ class _ImpactOptionsPopupState extends State { child: Padding( padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: Column( - children: [ - Center( - child: Text( - localeMsg.indirectOptions, - style: Theme.of(context).textTheme.headlineMedium, + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: Column( + children: [ + Center( + child: Text( + localeMsg.indirectOptions, + style: Theme.of(context).textTheme.headlineMedium, + ), ), - ), - const SizedBox(height: 15), - const SizedBox(height: 10), - SizedBox( - height: 200, - child: Wrap( - children: [ - Column( - children: [ - const Text( - "Category", - ), - SizedBox( - height: 200, - width: 200, - child: ListView.builder( + const SizedBox(height: 15), + const SizedBox(height: 10), + SizedBox( + height: 200, + child: Wrap( + children: [ + Column( + children: [ + const Text( + "Category", + ), + SizedBox( + height: 200, + width: 200, + child: ListView.builder( itemCount: PhyCategories.values.length, itemBuilder: (BuildContext context, int index) { - var name = PhyCategories.values[index].name; + final name = + PhyCategories.values[index].name; return CheckboxListTile( controlAffinity: ListTileControlAffinity.leading, dense: true, value: selectedCategories.contains( - PhyCategories.values[index].name), + PhyCategories.values[index].name, + ), onChanged: (bool? selected) { setState(() { if (selectedCategories.contains( - PhyCategories - .values[index].name)) { + PhyCategories.values[index].name, + )) { selectedCategories.remove( - PhyCategories - .values[index].name); + PhyCategories.values[index].name, + ); } else { - selectedCategories.add(PhyCategories - .values[index].name); + selectedCategories.add( + PhyCategories.values[index].name, + ); } }); }, title: Text(name), ); - }), - ), - ], - ), - Column( - children: [ - const Text("Physical Type"), - SizedBox( - height: 200, - width: 200, - child: ListView.builder( + }, + ), + ), + ], + ), + Column( + children: [ + const Text("Physical Type"), + SizedBox( + height: 200, + width: 200, + child: ListView.builder( itemCount: ptypes.length, itemBuilder: (BuildContext context, int index) { @@ -134,17 +139,18 @@ class _ImpactOptionsPopupState extends State { }, title: Text(ptypes[index]), ); - }), - ), - ], - ), - Column( - children: [ - const Text("Virtual Type"), - SizedBox( - height: 200, - width: 200, - child: ListView.builder( + }, + ), + ), + ], + ), + Column( + children: [ + const Text("Virtual Type"), + SizedBox( + height: 200, + width: 200, + child: ListView.builder( itemCount: vtypes.length, itemBuilder: (BuildContext context, int index) { @@ -165,31 +171,37 @@ class _ImpactOptionsPopupState extends State { }, title: Text(vtypes[index]), ); - }), - ), - ], - ) - ], + }, + ), + ), + ], + ), + ], + ), ), - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ElevatedButton.icon( + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton.icon( onPressed: () { - widget.parentCallback(selectedCategories, - selectedPtypes, selectedVtypes); + widget.parentCallback( + selectedCategories, + selectedPtypes, + selectedVtypes, + ); Navigator.of(context).pop(); }, label: const Text("OK"), - icon: const Icon(Icons.thumb_up, size: 16)) - ], - ) - ], + icon: const Icon(Icons.thumb_up, size: 16), + ), + ], + ), + ], + ), ), ), - )), + ), ), ), ); diff --git a/APP/lib/widgets/impact/impact_view.dart b/APP/lib/widgets/impact/impact_view.dart index b96240c56..44ef68116 100644 --- a/APP/lib/widgets/impact/impact_view.dart +++ b/APP/lib/widgets/impact/impact_view.dart @@ -1,26 +1,18 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:csv/csv.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; +import 'package:ogree_app/common/csv.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/alert.dart'; import 'package:ogree_app/widgets/impact/impact_graph_view.dart'; import 'package:ogree_app/widgets/impact/impact_popup.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:path_provider/path_provider.dart'; - -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:universal_html/html.dart' as html; class ImpactView extends StatefulWidget { String rootId; bool? receivedMarkAll; - ImpactView({required this.rootId, required this.receivedMarkAll}); + ImpactView({super.key, required this.rootId, required this.receivedMarkAll}); @override State createState() => _ImpactViewState(); @@ -50,8 +42,8 @@ class _ImpactViewState extends State @override Widget build(BuildContext context) { + super.build(context); final localeMsg = AppLocalizations.of(context)!; - bool isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); if (lastReceivedMarkAll != widget.receivedMarkAll) { isMarkedForMaintenance = widget.receivedMarkAll!; @@ -59,27 +51,32 @@ class _ImpactViewState extends State } return FutureBuilder( - future: _data.isEmpty ? getData() : null, - builder: (context, _) { - if (_data.isEmpty) { - return SizedBox( - height: MediaQuery.of(context).size.height > 205 - ? MediaQuery.of(context).size.height - 220 - : MediaQuery.of(context).size.height, - child: const Card( - margin: EdgeInsets.all(0.1), - child: Center(child: CircularProgressIndicator()), - ), - ); - } - return objectImpactView(widget.rootId, localeMsg); - }); + future: _data.isEmpty ? getData() : null, + builder: (context, _) { + if (_data.isEmpty) { + return SizedBox( + height: MediaQuery.of(context).size.height > 205 + ? MediaQuery.of(context).size.height - 220 + : MediaQuery.of(context).size.height, + child: const Card( + margin: EdgeInsets.all(0.1), + child: Center(child: CircularProgressIndicator()), + ), + ); + } + return objectImpactView(widget.rootId, localeMsg); + }, + ); } - getData() async { + Future getData() async { final messenger = ScaffoldMessenger.of(context); final result = await fetchObjectImpact( - widget.rootId, selectedCategories, selectedPtypes, selectedVtypes); + widget.rootId, + selectedCategories, + selectedPtypes, + selectedVtypes, + ); switch (result) { case Success(value: final value): _data = value; @@ -98,53 +95,57 @@ class _ImpactViewState extends State } } - objectImpactView(String rootId, AppLocalizations localeMsg) { - print(widget.receivedMarkAll); + Column objectImpactView(String rootId, AppLocalizations localeMsg) { return Column( children: [ const SizedBox(height: 10), Row( children: [ Padding( - padding: EdgeInsets.only(left: 4), + padding: const EdgeInsets.only(left: 4), child: SizedBox( width: 230, child: TextButton.icon( - onPressed: () => markForMaintenance(localeMsg), - label: Text(isMarkedForMaintenance + onPressed: () => markForMaintenance(localeMsg), + label: Text( + isMarkedForMaintenance ? localeMsg.markedMaintenance - : localeMsg.markMaintenance), - icon: isMarkedForMaintenance - ? Icon(Icons.check_circle) - : Icon(Icons.check_circle_outline)), + : localeMsg.markMaintenance, + ), + icon: isMarkedForMaintenance + ? const Icon(Icons.check_circle) + : const Icon(Icons.check_circle_outline), + ), ), ), Expanded( child: Padding( - padding: EdgeInsets.only(right: 150), + padding: const EdgeInsets.only(right: 150), child: getWidgetSpan(rootId, "target", size: 18), ), ), - IconButton(onPressed: () => getCSV(), icon: Icon(Icons.download)), + IconButton( + onPressed: () => getCSV(), icon: const Icon(Icons.download)), Padding( - padding: EdgeInsets.only(right: 10), + padding: const EdgeInsets.only(right: 10), child: IconButton( - onPressed: () => showCustomPopup( - context, - ImpactOptionsPopup( - selectedCategories: selectedCategories, - selectedPtypes: selectedPtypes, - selectedVtypes: selectedVtypes, - parentCallback: changeImpactFilters, - ), - isDismissible: true), - icon: Icon(Icons.edit)), + onPressed: () => showCustomPopup( + context, + ImpactOptionsPopup( + selectedCategories: selectedCategories, + selectedPtypes: selectedPtypes, + selectedVtypes: selectedVtypes, + parentCallback: changeImpactFilters, + ), + isDismissible: true, + ), + icon: const Icon(Icons.edit), + ), ), ], ), Align( - alignment: Alignment.center, child: Padding( padding: const EdgeInsets.only(top: 16), child: Text( @@ -164,126 +165,112 @@ class _ImpactViewState extends State children: [ Text( localeMsg.directly.toUpperCase(), - style: TextStyle( - fontWeight: FontWeight.w900, fontSize: 17), + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 17, + ), ), Padding( - padding: EdgeInsets.only(left: 6), + padding: const EdgeInsets.only(left: 6), child: Tooltip( message: localeMsg.directTip, verticalOffset: 13, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.all(Radius.circular(12)), ), - textStyle: TextStyle( + textStyle: const TextStyle( fontSize: 13, color: Colors.white, ), - padding: EdgeInsets.all(13), - child: Icon(Icons.info_outline_rounded, - color: Colors.blueAccent), + padding: const EdgeInsets.all(13), + child: const Icon( + Icons.info_outline_rounded, + color: Colors.blueAccent, + ), ), ), ], ), - SizedBox(height: 15), + const SizedBox(height: 15), ...listImpacted(_data["direct"]), ], ), Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, children: [ Row( children: [ Text( localeMsg.indirectly.toUpperCase(), - style: TextStyle( - fontWeight: FontWeight.w900, fontSize: 17), + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 17, + ), ), Padding( - padding: EdgeInsets.only(left: 6), + padding: const EdgeInsets.only(left: 6), child: Tooltip( message: localeMsg.indirectTip, verticalOffset: 13, - decoration: BoxDecoration( + decoration: const BoxDecoration( color: Colors.blueAccent, borderRadius: BorderRadius.all(Radius.circular(12)), ), - textStyle: TextStyle( + textStyle: const TextStyle( fontSize: 13, color: Colors.white, ), - padding: EdgeInsets.all(13), - child: Icon(Icons.info_outline_rounded, - color: Colors.blueAccent), + padding: const EdgeInsets.all(13), + child: const Icon( + Icons.info_outline_rounded, + color: Colors.blueAccent, + ), ), ), ], ), - SizedBox(height: 15), + const SizedBox(height: 15), ...listImpacted(_data["indirect"]), ], - ) + ), ], ), ), - SizedBox(height: 15), + const SizedBox(height: 15), Center( child: Text( localeMsg.graphView, style: Theme.of(context).textTheme.headlineMedium, ), ), - SizedBox(height: 15), + const SizedBox(height: 15), // ImpactGraphView("BASIC.A.R1.A01.chT"), ImpactGraphView(rootId, _data), - SizedBox(height: 10), + const SizedBox(height: 10), ], ); } getCSV() async { // Prepare data - List> rows = [ - ["target", widget.rootId] + final List> rows = [ + ["target", widget.rootId], ]; - for (var type in ["direct", "indirect"]) { - var direct = (Map.from(_data[type])).keys.toList(); + for (final type in ["direct", "indirect"]) { + final direct = Map.from(_data[type]).keys.toList(); direct.insertAll(0, [type]); rows.add(direct); } - // Prepare the file - String csv = const ListToCsvConverter().convert(rows); - final bytes = utf8.encode(csv); - if (kIsWeb) { - // If web, use html to download csv - html.AnchorElement( - href: 'data:application/octet-stream;base64,${base64Encode(bytes)}') - ..setAttribute("download", "impact-report.csv") - ..click(); - } else { - // Save to local filesystem - var path = (await getApplicationDocumentsDirectory()).path; - var fileName = '$path/impact-report.csv'; - var file = File(fileName); - for (var i = 1; await file.exists(); i++) { - fileName = '$path/impact-report ($i).csv'; - file = File(fileName); - } - file.writeAsBytes(bytes, flush: true).then((value) => showSnackBar( - ScaffoldMessenger.of(context), - "${AppLocalizations.of(context)!.fileSavedTo} $fileName")); - } + // Save the file + await saveCSV("impact-report", rows, context); } markForMaintenance(AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); if (isMarkedForMaintenance) { // unmark - var result = await deleteObject(widget.rootId, "alert"); + final result = await deleteObject(widget.rootId, "alert"); switch (result) { case Success(): showSnackBar( @@ -297,14 +284,21 @@ class _ImpactViewState extends State showSnackBar(messenger, exception.toString(), isError: true); } } else { - var alert = Alert(widget.rootId, "minor", - "${widget.rootId} ${localeMsg.isMarked}", localeMsg.checkImpact); + final alert = Alert( + widget.rootId, + "minor", + "${widget.rootId} ${localeMsg.isMarked}", + localeMsg.checkImpact, + ); - var result = await createAlert(alert); + final result = await createAlert(alert); switch (result) { case Success(): - showSnackBar(messenger, "${widget.rootId} ${localeMsg.successMarked}", - isSuccess: true); + showSnackBar( + messenger, + "${widget.rootId} ${localeMsg.successMarked}", + isSuccess: true, + ); setState(() { isMarkedForMaintenance = true; }); @@ -314,7 +308,7 @@ class _ImpactViewState extends State } } - getWidgetSpan(String text, String category, {double size = 14}) { + Padding getWidgetSpan(String text, String category, {double size = 14}) { MaterialColor badgeColor = Colors.blue; if (category == "device") { badgeColor = Colors.teal; @@ -324,7 +318,6 @@ class _ImpactViewState extends State return Padding( padding: const EdgeInsets.symmetric(vertical: 2.0), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( @@ -337,9 +330,10 @@ class _ImpactViewState extends State label: Text( " $text ", style: TextStyle( - fontSize: size, - fontWeight: FontWeight.bold, - color: badgeColor.shade900), + fontSize: size, + fontWeight: FontWeight.bold, + color: badgeColor.shade900, + ), ), ), ), @@ -351,15 +345,18 @@ class _ImpactViewState extends State } List listImpacted(Map objects) { - List listWidgets = []; - for (var objId in objects.keys) { + final List listWidgets = []; + for (final objId in objects.keys) { listWidgets.add(getWidgetSpan(objId, objects[objId]["category"])); } return listWidgets; } changeImpactFilters( - List categories, List ptypes, List vtypes) async { + List categories, + List ptypes, + List vtypes, + ) async { selectedCategories = categories; selectedPtypes = ptypes; selectedVtypes = vtypes; diff --git a/APP/lib/widgets/login/change_password_popup.dart b/APP/lib/widgets/login/change_password_popup.dart index e626d453e..be8edbc00 100644 --- a/APP/lib/widgets/login/change_password_popup.dart +++ b/APP/lib/widgets/login/change_password_popup.dart @@ -19,12 +19,10 @@ class _ChangePasswordPopupState extends State { String? _oldPassword; String? _newPassword; String? _confirmPass; - bool _isSmallDisplay = false; @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - _isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return Center( child: Container( // height: 240, @@ -38,7 +36,6 @@ class _ChangePasswordPopupState extends State { child: Form( key: _formKey, child: Column( - mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( @@ -47,24 +44,28 @@ class _ChangePasswordPopupState extends State { ), const SizedBox(height: 20), CustomFormField( - save: (newValue) => _oldPassword = newValue, - label: localeMsg.currentPassword, - icon: Icons.lock_open_rounded), + save: (newValue) => _oldPassword = newValue, + label: localeMsg.currentPassword, + icon: Icons.lock_open_rounded, + ), CustomFormField( - save: (newValue) => _newPassword = newValue, - label: localeMsg.newPassword, - icon: Icons.lock_outline_rounded), + save: (newValue) => _newPassword = newValue, + label: localeMsg.newPassword, + icon: Icons.lock_outline_rounded, + ), CustomFormField( - save: (newValue) => _confirmPass = newValue, - label: localeMsg.confirmPassword, - icon: Icons.lock_outline_rounded), + save: (newValue) => _confirmPass = newValue, + label: localeMsg.confirmPassword, + icon: Icons.lock_outline_rounded, + ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900, + ), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -74,58 +75,22 @@ class _ChangePasswordPopupState extends State { ), const SizedBox(width: 15), ElevatedButton.icon( - onPressed: () async { - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - if (_newPassword != _confirmPass) { - showSnackBar(ScaffoldMessenger.of(context), - localeMsg.passwordNoMatch, - isError: true); - return; - } - final messenger = ScaffoldMessenger.of(context); - try { - setState(() { - _isLoading = true; - }); - final response = await changeUserPassword( - _oldPassword!, _newPassword!); - switch (response) { - case Success(): - showSnackBar(messenger, localeMsg.modifyOK, - isSuccess: true); - if (context.mounted) { - Navigator.of(context).pop(); - } - case Failure(exception: final exception): - setState(() { - _isLoading = false; - }); - showSnackBar( - messenger, exception.toString(), - isError: true); - } - } catch (e) { - showSnackBar(messenger, e.toString(), - isError: true); - return; - } - } - }, - label: Text(localeMsg.modify), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, size: 16)) + onPressed: () => passwordAction(localeMsg), + label: Text(localeMsg.modify), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.check_circle, size: 16), + ), ], - ) + ), ], ), ), @@ -134,4 +99,55 @@ class _ChangePasswordPopupState extends State { ), ); } + + passwordAction(AppLocalizations localeMsg) async { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + if (_newPassword != _confirmPass) { + showSnackBar( + ScaffoldMessenger.of(context), + localeMsg.passwordNoMatch, + isError: true, + ); + return; + } + final messenger = ScaffoldMessenger.of(context); + try { + setState(() { + _isLoading = true; + }); + final response = await changeUserPassword( + _oldPassword!, + _newPassword, + ); + switch (response) { + case Success(): + showSnackBar( + messenger, + localeMsg.modifyOK, + isSuccess: true, + ); + if (mounted) { + Navigator.of(context).pop(); + } + case Failure(exception: final exception): + setState(() { + _isLoading = false; + }); + showSnackBar( + messenger, + exception.toString(), + isError: true, + ); + } + } catch (e) { + showSnackBar( + messenger, + e.toString(), + isError: true, + ); + return; + } + } + } } diff --git a/APP/lib/widgets/login/login_card.dart b/APP/lib/widgets/login/login_card.dart index 768d313c4..671539bce 100644 --- a/APP/lib/widgets/login/login_card.dart +++ b/APP/lib/widgets/login/login_card.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/pages/login_page.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/pages/projects_page.dart'; class LoginCard extends StatefulWidget { @@ -28,78 +28,90 @@ class _LoginCardState extends State { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - bool isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); + final bool isSmallDisplay = + IsSmallDisplay(MediaQuery.of(context).size.width); return Card( child: Form( key: _formKey, child: Container( constraints: const BoxConstraints(maxWidth: 550, maxHeight: 520), padding: EdgeInsets.only( - right: isSmallDisplay ? 45 : 100, - left: isSmallDisplay ? 45 : 100, - top: 50, - bottom: 30), + right: isSmallDisplay ? 45 : 100, + left: isSmallDisplay ? 45 : 100, + top: 50, + bottom: 30, + ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - showForgotView - ? Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - IconButton( - constraints: const BoxConstraints(), - onPressed: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const LoginPage())), - icon: Icon( - Icons.arrow_back, - color: Colors.blue.shade900, - )), - SizedBox(width: isSmallDisplay ? 0 : 5), - Text( - localeMsg.resetPassword, - style: Theme.of(context).textTheme.headlineMedium, + if (showForgotView) + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + IconButton( + constraints: const BoxConstraints(), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LoginPage(), ), - ], - ) - : Center( - child: Text(localeMsg.welcome, - style: Theme.of(context).textTheme.headlineLarge)), - const SizedBox(height: 8), - showForgotView - ? const SizedBox(height: 10) - : Center( - child: Text( - localeMsg.welcomeConnect, - style: Theme.of(context).textTheme.headlineSmall, ), - ), - showForgotView ? Container() : const SizedBox(height: 20), - dotenv.env['ALLOW_SET_BACK'] == "true" - ? BackendInput( - parentCallback: (newValue) => _apiUrl = newValue, - ) - : Center( - child: Image.asset( - "assets/custom/logo.png", - height: 40, + icon: Icon( + Icons.arrow_back, + color: Colors.blue.shade900, ), ), - dotenv.env['ALLOW_SET_BACK'] == "true" - ? Align( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Badge( - backgroundColor: Colors.white, - label: Text( - getBackendTypeText(), - style: const TextStyle(color: Colors.black), - ), - ), + SizedBox(width: isSmallDisplay ? 0 : 5), + Text( + localeMsg.resetPassword, + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ) + else + Center( + child: Text( + localeMsg.welcome, + style: Theme.of(context).textTheme.headlineLarge, + ), + ), + const SizedBox(height: 8), + if (showForgotView) + const SizedBox(height: 10) + else + Center( + child: Text( + localeMsg.welcomeConnect, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + if (showForgotView) Container() else const SizedBox(height: 20), + if (dotenv.env['ALLOW_SET_BACK'] == "true") + BackendInput( + parentCallback: (newValue) => _apiUrl = newValue, + ) + else + Center( + child: Image.asset( + "assets/custom/logo.png", + height: 40, + ), + ), + if (dotenv.env['ALLOW_SET_BACK'] == "true") + Align( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Badge( + backgroundColor: Colors.white, + label: Text( + getBackendTypeText(), + style: const TextStyle(color: Colors.black), ), - ) - : const SizedBox(height: 30), + ), + ), + ) + else + const SizedBox(height: 30), TextFormField( onSaved: (newValue) => _email = newValue, validator: (text) { @@ -109,104 +121,112 @@ class _LoginCardState extends State { return null; }, decoration: LoginInputDecoration( - label: 'E-mail', - hint: 'abc@example.com', - isSmallDisplay: isSmallDisplay), + label: 'E-mail', + hint: 'abc@example.com', + isSmallDisplay: isSmallDisplay, + ), ), SizedBox(height: isSmallDisplay ? 10 : 20), - showForgotView - ? Container() - : TextFormField( - obscureText: true, - onSaved: (newValue) => _password = newValue, - onEditingComplete: () => - tryLogin(localeMsg, ScaffoldMessenger.of(context)), - validator: (text) { - if (!showForgotView && - (text == null || text.isEmpty)) { - return localeMsg.mandatoryField; - } - return null; - }, - decoration: LoginInputDecoration( - label: localeMsg.password, - hint: '********', - isSmallDisplay: isSmallDisplay), + if (showForgotView) + Container() + else + TextFormField( + obscureText: true, + onSaved: (newValue) => _password = newValue, + onEditingComplete: () => + tryLogin(localeMsg, ScaffoldMessenger.of(context)), + validator: (text) { + if (!showForgotView && (text == null || text.isEmpty)) { + return localeMsg.mandatoryField; + } + return null; + }, + decoration: LoginInputDecoration( + label: localeMsg.password, + hint: '********', + isSmallDisplay: isSmallDisplay, + ), + ), + if (!showForgotView) + SizedBox(height: isSmallDisplay ? 15 : 25) + else + Container(), + if (showForgotView) + TextButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LoginPage( + isPasswordReset: true, + ), ), - !showForgotView - ? SizedBox(height: isSmallDisplay ? 15 : 25) - : Container(), - showForgotView - ? TextButton( - onPressed: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const LoginPage( - isPasswordReset: true, - resetToken: '', + ), + child: Text( + localeMsg.haveResetToken, + style: const TextStyle( + fontSize: 14, + color: Color.fromARGB(255, 0, 84, 152), + ), + ), + ) + else + Wrap( + alignment: WrapAlignment.spaceBetween, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (!isSmallDisplay) + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + SizedBox( + height: 24, + width: 24, + child: StatefulBuilder( + builder: (context, localSetState) { + return Checkbox( + value: _stayLoggedIn, + onChanged: (bool? value) => localSetState( + () => _stayLoggedIn = value!, + ), + ); + }, + ), ), - ), - ), + const SizedBox(width: 8), + Text( + localeMsg.stayLogged, + style: const TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + ], + ) + else + Container(), + TextButton( + onPressed: () => setState(() { + showForgotView = !showForgotView; + }), child: Text( - localeMsg.haveResetToken, + localeMsg.forgotPassword, style: const TextStyle( fontSize: 14, color: Color.fromARGB(255, 0, 84, 152), ), ), - ) - : Wrap( - alignment: WrapAlignment.spaceBetween, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - !isSmallDisplay - ? Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - SizedBox( - height: 24, - width: 24, - child: StatefulBuilder( - builder: (context, localSetState) { - return Checkbox( - value: _stayLoggedIn, - onChanged: (bool? value) => - localSetState( - () => _stayLoggedIn = value!), - ); - }), - ), - const SizedBox(width: 8), - Text( - localeMsg.stayLogged, - style: const TextStyle( - fontSize: 14, - color: Colors.black, - ), - ), - ], - ) - : Container(), - TextButton( - onPressed: () => setState(() { - showForgotView = !showForgotView; - }), - child: Text( - localeMsg.forgotPassword, - style: const TextStyle( - fontSize: 14, - color: Color.fromARGB(255, 0, 84, 152), - ), - ), - ), - ], ), + ], + ), SizedBox( - height: showForgotView ? 20 : (isSmallDisplay ? 15 : 30)), + height: showForgotView ? 20 : (isSmallDisplay ? 15 : 30), + ), Align( child: ElevatedButton( onPressed: () => showForgotView ? resetPassword( - localeMsg, ScaffoldMessenger.of(context)) + localeMsg, + ScaffoldMessenger.of(context), + ) : tryLogin(localeMsg, ScaffoldMessenger.of(context)), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric( @@ -233,7 +253,9 @@ class _LoginCardState extends State { } resetPassword( - AppLocalizations localeMsg, ScaffoldMessengerState messenger) async { + AppLocalizations localeMsg, + ScaffoldMessengerState messenger, + ) async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); final result = await userForgotPassword(_email!, userUrl: _apiUrl); @@ -249,14 +271,18 @@ class _LoginCardState extends State { tryLogin(AppLocalizations localeMsg, ScaffoldMessengerState messenger) async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); - final result = await loginAPI(_email!, _password!, - userUrl: _apiUrl, stayLoggedIn: _stayLoggedIn); + final result = await loginAPI( + _email!, + _password!, + userUrl: _apiUrl, + stayLoggedIn: _stayLoggedIn, + ); switch (result) { case Success(value: final loginData): if (apiType == BackendType.tenant) { await fetchApiVersion(_apiUrl); } - if (context.mounted) { + if (mounted) { Navigator.of(context).push( MaterialPageRoute( builder: (context) => ProjectsPage( @@ -267,7 +293,7 @@ class _LoginCardState extends State { ); } case Failure(exception: final exception): - String errorMsg = exception.toString() == "Exception" + final String errorMsg = exception.toString() == "Exception" ? localeMsg.invalidLogin : exception.toString(); showSnackBar(messenger, errorMsg, isError: true); @@ -290,7 +316,7 @@ class _LoginCardState extends State { } } - getBackendTypeText() { + String getBackendTypeText() { if (apiType == null) { return ""; } else if (apiType == BackendType.unavailable) { @@ -318,10 +344,12 @@ class BackendInput extends StatelessWidget { return option.contains(textEditingValue.text); }); }, - fieldViewBuilder: (BuildContext context, - TextEditingController textEditingController, - FocusNode focusNode, - VoidCallback onFieldSubmitted) { + fieldViewBuilder: ( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { textEditingController.text = options.first; return TextFormField( controller: textEditingController, @@ -334,13 +362,17 @@ class BackendInput extends StatelessWidget { return null; }, decoration: InputDecoration( - isDense: true, - labelText: localeMsg.selectServer, - labelStyle: const TextStyle(fontSize: 14)), + isDense: true, + labelText: localeMsg.selectServer, + labelStyle: const TextStyle(fontSize: 14), + ), ); }, - optionsViewBuilder: (BuildContext context, - AutocompleteOnSelected onSelected, Iterable options) { + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { return Align( alignment: Alignment.topLeft, child: Material( diff --git a/APP/lib/widgets/login/reset_card.dart b/APP/lib/widgets/login/reset_card.dart index 6499132a0..f5c072879 100644 --- a/APP/lib/widgets/login/reset_card.dart +++ b/APP/lib/widgets/login/reset_card.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/pages/login_page.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/widgets/login/login_card.dart'; class ResetCard extends StatelessWidget { @@ -21,7 +21,8 @@ class ResetCard extends StatelessWidget { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - bool isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); + final bool isSmallDisplay = + IsSmallDisplay(MediaQuery.of(context).size.width); return Card( child: Form( key: _formKey, @@ -36,13 +37,16 @@ class ResetCard extends StatelessWidget { Row( children: [ IconButton( - onPressed: () => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const LoginPage())), - icon: Icon( - Icons.arrow_back, - color: Colors.blue.shade900, - )), + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LoginPage(), + ), + ), + icon: Icon( + Icons.arrow_back, + color: Colors.blue.shade900, + ), + ), const SizedBox(width: 5), Text( localeMsg.resetPassword, @@ -51,16 +55,17 @@ class ResetCard extends StatelessWidget { ], ), const SizedBox(height: 25), - dotenv.env['ALLOW_SET_BACK'] == "true" - ? BackendInput( - parentCallback: (newValue) => _apiUrl = newValue, - ) - : Center( - child: Image.asset( - "assets/custom/logo.png", - height: 30, - ), - ), + if (dotenv.env['ALLOW_SET_BACK'] == "true") + BackendInput( + parentCallback: (newValue) => _apiUrl = newValue, + ) + else + Center( + child: Image.asset( + "assets/custom/logo.png", + height: 30, + ), + ), const SizedBox(height: 32), TextFormField( initialValue: token, @@ -73,7 +78,9 @@ class ResetCard extends StatelessWidget { return null; }, decoration: LoginInputDecoration( - label: 'Reset Token', isSmallDisplay: isSmallDisplay), + label: 'Reset Token', + isSmallDisplay: isSmallDisplay, + ), ), const SizedBox(height: 20), TextFormField( @@ -86,9 +93,10 @@ class ResetCard extends StatelessWidget { return null; }, decoration: LoginInputDecoration( - label: localeMsg.newPassword, - hint: '********', - isSmallDisplay: isSmallDisplay), + label: localeMsg.newPassword, + hint: '********', + isSmallDisplay: isSmallDisplay, + ), ), const SizedBox(height: 20), TextFormField( @@ -102,9 +110,10 @@ class ResetCard extends StatelessWidget { return null; }, decoration: LoginInputDecoration( - label: localeMsg.confirmPassword, - hint: '********', - isSmallDisplay: isSmallDisplay), + label: localeMsg.confirmPassword, + hint: '********', + isSmallDisplay: isSmallDisplay, + ), ), const SizedBox(height: 25), Align( @@ -134,12 +143,16 @@ class ResetCard extends StatelessWidget { ); } - resetPassword(AppLocalizations localeMsg, BuildContext context) async { + Future resetPassword( + AppLocalizations localeMsg, BuildContext context) async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); if (_password != _confirmPassword) { - showSnackBar(ScaffoldMessenger.of(context), localeMsg.passwordNoMatch, - isError: true); + showSnackBar( + ScaffoldMessenger.of(context), + localeMsg.passwordNoMatch, + isError: true, + ); return; } final messenger = ScaffoldMessenger.of(context); @@ -147,17 +160,19 @@ class ResetCard extends StatelessWidget { await userResetPassword(_password!, _token!, userUrl: _apiUrl); switch (result) { case Success(): - resetSucces(localeMsg, context); + resetSuccess(localeMsg, context); case Failure(exception: final exception): - print(exception); showSnackBar(messenger, exception.toString().trim(), isError: true); } } } - resetSucces(AppLocalizations localeMsg, BuildContext context) { - showSnackBar(ScaffoldMessenger.of(context), localeMsg.modifyOK, - isSuccess: true); + resetSuccess(AppLocalizations localeMsg, BuildContext context) { + showSnackBar( + ScaffoldMessenger.of(context), + localeMsg.modifyOK, + isSuccess: true, + ); Navigator.of(context).push( MaterialPageRoute( builder: (context) => const LoginPage(), diff --git a/APP/lib/widgets/object_graph_view.dart b/APP/lib/widgets/object_graph_view.dart index 7e1cc54cb..8dcb189b0 100644 --- a/APP/lib/widgets/object_graph_view.dart +++ b/APP/lib/widgets/object_graph_view.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:graphview/GraphView.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/common/theme.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ObjectGraphView extends StatefulWidget { - String rootId; - ObjectGraphView(this.rootId); + final String rootId; + const ObjectGraphView(this.rootId, {super.key}); @override - _ObjectGraphViewState createState() => _ObjectGraphViewState(); + ObjectGraphViewState createState() => ObjectGraphViewState(); } -class _ObjectGraphViewState extends State { +class ObjectGraphViewState extends State { bool loaded = false; Map idCategory = {}; @@ -35,102 +35,102 @@ class _ObjectGraphViewState extends State { @override Widget build(BuildContext context) { return FutureBuilder( - future: !loaded ? getObject() : null, - builder: (context, _) { - if (!loaded) { - return const Center(child: CircularProgressIndicator()); - } - return Center( - child: Container( - constraints: const BoxConstraints(maxHeight: 520, maxWidth: 800), - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), - decoration: PopupDecoration, - child: Padding( - padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), - child: Scaffold( - backgroundColor: Colors.white, - body: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Center( - child: Text( - AppLocalizations.of(context)!.viewGraph, - style: Theme.of(context).textTheme.headlineMedium, - ), + future: !loaded ? getObject() : null, + builder: (context, _) { + if (!loaded) { + return const Center(child: CircularProgressIndicator()); + } + return Center( + child: Container( + constraints: const BoxConstraints(maxHeight: 520, maxWidth: 800), + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + decoration: PopupDecoration, + child: Padding( + padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), + child: Scaffold( + backgroundColor: Colors.white, + body: Column( + children: [ + Center( + child: Text( + AppLocalizations.of(context)!.viewGraph, + style: Theme.of(context).textTheme.headlineMedium, ), - Expanded( - child: InteractiveViewer( - alignment: Alignment.center, - constrained: true, - boundaryMargin: - const EdgeInsets.all(double.infinity), - minScale: 0.0001, - maxScale: 10.6, - child: OverflowBox( - alignment: Alignment.center, - minWidth: 0.0, - minHeight: 0.0, - maxWidth: double.infinity, - maxHeight: double.infinity, - child: GraphView( - graph: graph, - algorithm: SugiyamaAlgorithm(builder), - paint: Paint() - ..color = Colors.blue - ..strokeWidth = 1 - ..style = PaintingStyle.stroke, - builder: (Node node) { - var a = node.key!.value as String?; - return rectangleWidget(a!); - }, - ), - )), + ), + Expanded( + child: InteractiveViewer( + alignment: Alignment.center, + boundaryMargin: const EdgeInsets.all(double.infinity), + minScale: 0.0001, + maxScale: 10.6, + child: OverflowBox( + minWidth: 0.0, + minHeight: 0.0, + maxWidth: double.infinity, + maxHeight: double.infinity, + child: GraphView( + graph: graph, + algorithm: SugiyamaAlgorithm(builder), + paint: Paint() + ..color = Colors.blue + ..strokeWidth = 1 + ..style = PaintingStyle.stroke, + builder: (Node node) { + final a = node.key!.value as String?; + return rectangleWidget(a!); + }, + ), + ), ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ElevatedButton.icon( - onPressed: () { - Navigator.of(context).pop(); - }, - label: const Text("OK"), - icon: const Icon(Icons.thumb_up, size: 16)) - ], - ) - ], - ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton.icon( + onPressed: () { + Navigator.of(context).pop(); + }, + label: const Text("OK"), + icon: const Icon(Icons.thumb_up, size: 16), + ), + ], + ), + ], ), ), ), - ); - }); + ), + ); + }, + ); } Widget rectangleWidget(String a) { return Tooltip( message: a, child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - boxShadow: [ - BoxShadow( - color: idCategory[a] == "virtual_obj" - ? Colors.purple[100]! - : Colors.blue[100]!, - spreadRadius: 1), - ], - ), - child: Text('${a.split(".").last}')), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + boxShadow: [ + BoxShadow( + color: idCategory[a] == "virtual_obj" + ? Colors.purple[100]! + : Colors.blue[100]!, + spreadRadius: 1, + ), + ], + ), + child: Text(a.split(".").last), + ), ); } - getObject() async { + Future getObject() async { final messenger = ScaffoldMessenger.of(context); - var result = await fetchObjectChildren(widget.rootId); + final result = await fetchObjectChildren(widget.rootId); switch (result) { case Success(value: final value): - print(value); addToGraph(value); loaded = true; return; @@ -139,19 +139,22 @@ class _ObjectGraphViewState extends State { } } - addToGraph(Map value) { + Node addToGraph(Map value) { final node = Node.Id(value["id"]); graph.addNode(node); idCategory[value["id"]] = value["category"]; if (value["attributes"] != null && value["attributes"]["vlinks"] != null) { - for (var vlink in List.from(value["attributes"]["vlinks"])) { - graph.addEdge(node, Node.Id(vlink), - paint: Paint()..color = Colors.purple); + for (final vlink in List.from(value["attributes"]["vlinks"])) { + graph.addEdge( + node, + Node.Id(vlink), + paint: Paint()..color = Colors.purple, + ); } } if (value["children"] != null) { - for (var child in List>.from(value["children"])) { - var childNode = addToGraph(child); + for (final child in List>.from(value["children"])) { + final childNode = addToGraph(child); graph.addEdge(node, childNode); } } diff --git a/APP/lib/widgets/projects/autoproject_card.dart b/APP/lib/widgets/projects/autoproject_card.dart index ac60225d6..6df0f0670 100644 --- a/APP/lib/widgets/projects/autoproject_card.dart +++ b/APP/lib/widgets/projects/autoproject_card.dart @@ -10,12 +10,11 @@ class AutoProjectCard extends StatelessWidget { final String userEmail; final Function parentCallback; const AutoProjectCard( - {Key? key, + {super.key, // required this.project, required this.namespace, required this.userEmail, - required this.parentCallback}) - : super(key: key); + required this.parentCallback,}); @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; @@ -48,7 +47,7 @@ class AutoProjectCard extends StatelessWidget { style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: color.shade900), + color: color.shade900,), ), ), ), @@ -93,15 +92,15 @@ class AutoProjectCard extends StatelessWidget { "AUTO", false, false, - false, [], [], []), + false, [], [], [],), userEmail: userEmail, ), ), ); }, icon: const Icon(Icons.play_circle), - label: Text(localeMsg.launch)), - ) + label: Text(localeMsg.launch),), + ), ], ), ), diff --git a/APP/lib/widgets/projects/autounity_card.dart b/APP/lib/widgets/projects/autounity_card.dart index 612fe3db8..0276ea554 100644 --- a/APP/lib/widgets/projects/autounity_card.dart +++ b/APP/lib/widgets/projects/autounity_card.dart @@ -4,12 +4,11 @@ import 'package:ogree_app/pages/unity_page.dart'; class AutoUnityProjectCard extends StatelessWidget { final String userEmail; - const AutoUnityProjectCard({Key? key, required this.userEmail}) - : super(key: key); + const AutoUnityProjectCard({super.key, required this.userEmail}); @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - var color = Colors.blue; + const color = Colors.blue; return SizedBox( width: 265, @@ -33,7 +32,7 @@ class AutoUnityProjectCard extends StatelessWidget { style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, - color: color.shade900), + color: color.shade900,), ), ), ), @@ -74,8 +73,8 @@ class AutoUnityProjectCard extends StatelessWidget { ); }, icon: const Icon(Icons.play_circle), - label: Text(localeMsg.launch)), - ) + label: Text(localeMsg.launch),), + ), ], ), ), diff --git a/APP/lib/widgets/projects/project_card.dart b/APP/lib/widgets/projects/project_card.dart index af51b5cbc..7382a5db8 100644 --- a/APP/lib/widgets/projects/project_card.dart +++ b/APP/lib/widgets/projects/project_card.dart @@ -2,36 +2,40 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; -import 'package:ogree_app/widgets/projects/project_popup.dart'; import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/models/project.dart'; import 'package:ogree_app/pages/select_page.dart'; +import 'package:ogree_app/widgets/projects/project_popup.dart'; class ProjectCard extends StatelessWidget { final Project project; final String userEmail; final Function parentCallback; - const ProjectCard( - {Key? key, - required this.project, - required this.userEmail, - required this.parentCallback}) - : super(key: key); + const ProjectCard({ + super.key, + required this.project, + required this.userEmail, + required this.parentCallback, + }); @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - modifyProjectCallback(String userInput, Project project, bool isCreate, - Function? parentCallback) async { + modifyProjectCallback( + String userInput, + Project project, + bool isCreate, + Function? parentCallback, + ) async { if (userInput == project.name) { Navigator.pop(context); } else { project.name = userInput; final messenger = ScaffoldMessenger.of(context); - var result = await modifyProject(project); + final result = await modifyProject(project); switch (result) { case Success(): parentCallback!(); - if (context.mounted) Navigator.pop(context); + Navigator.pop(context); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); } @@ -40,11 +44,11 @@ class ProjectCard extends StatelessWidget { deleteProjectCallback(String projectId, Function? parentCallback) async { final messenger = ScaffoldMessenger.of(context); - var result = await deleteObject(projectId, "project"); + final result = await deleteObject(projectId, "project"); switch (result) { case Success(): parentCallback!(); - if (context.mounted) Navigator.pop(context); + Navigator.pop(context); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); } @@ -66,43 +70,50 @@ class ProjectCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - project.isImpact - ? Padding( - padding: const EdgeInsets.only(right: 4), - child: Tooltip( - message: localeMsg.impactAnalysis, - child: Icon( - Icons.settings_suggest, - size: 14, - color: Colors.black, - ), - ), - ) - : Container(), + if (project.isImpact) + Padding( + padding: const EdgeInsets.only(right: 4), + child: Tooltip( + message: localeMsg.impactAnalysis, + child: const Icon( + Icons.settings_suggest, + size: 14, + color: Colors.black, + ), + ), + ) + else + Container(), SizedBox( width: 160, - child: Text(project.name, - overflow: TextOverflow.clip, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 16)), + child: Text( + project.name, + overflow: TextOverflow.clip, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), ), CircleAvatar( radius: 13, child: IconButton( - splashRadius: 18, - iconSize: 13, - padding: const EdgeInsets.all(2), - onPressed: () => showProjectDialog( - context, - project, - localeMsg.editProject, - deleteCallback: deleteProjectCallback, - modifyProjectCallback, - parentCallback: parentCallback), - icon: const Icon( - Icons.mode_edit_outline_rounded, - )), - ) + splashRadius: 18, + iconSize: 13, + padding: const EdgeInsets.all(2), + onPressed: () => showProjectDialog( + context, + project, + localeMsg.editProject, + deleteCallback: deleteProjectCallback, + modifyProjectCallback, + parentCallback: parentCallback, + ), + icon: const Icon( + Icons.mode_edit_outline_rounded, + ), + ), + ), ], ), Column( @@ -113,7 +124,7 @@ class ProjectCard extends StatelessWidget { child: Text(localeMsg.author), ), Text( - "${project.authorLastUpdate}", + project.authorLastUpdate, style: TextStyle(backgroundColor: Colors.grey.shade200), ), ], @@ -126,7 +137,7 @@ class ProjectCard extends StatelessWidget { child: Text(localeMsg.lastUpdate), ), Text( - "${project.lastUpdate}", + project.lastUpdate, style: TextStyle(backgroundColor: Colors.grey.shade200), ), ], @@ -134,19 +145,20 @@ class ProjectCard extends StatelessWidget { Align( alignment: Alignment.bottomRight, child: TextButton.icon( - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => SelectPage( - project: project, - userEmail: userEmail, - ), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SelectPage( + project: project, + userEmail: userEmail, ), - ); - }, - icon: const Icon(Icons.play_circle), - label: Text(localeMsg.launch)), - ) + ), + ); + }, + icon: const Icon(Icons.play_circle), + label: Text(localeMsg.launch), + ), + ), ], ), ), diff --git a/APP/lib/widgets/projects/project_popup.dart b/APP/lib/widgets/projects/project_popup.dart index 8654bdebf..24e846f09 100644 --- a/APP/lib/widgets/projects/project_popup.dart +++ b/APP/lib/widgets/projects/project_popup.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/snackbar.dart'; +import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/project.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import '../../common/theme.dart'; void showProjectDialog( BuildContext context, @@ -38,7 +37,7 @@ void showProjectDialog( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(title, - style: Theme.of(context).textTheme.headlineMedium), + style: Theme.of(context).textTheme.headlineMedium,), Padding( padding: const EdgeInsets.symmetric(vertical: 25), child: TextFormField( @@ -46,7 +45,7 @@ void showProjectDialog( onChanged: (value) => editInput = value, decoration: GetFormInputDecoration( false, localeMsg.projectName, - icon: Icons.edit_outlined), + icon: Icons.edit_outlined,), cursorWidth: 1.3, style: const TextStyle(fontSize: 14), ), @@ -59,7 +58,7 @@ void showProjectDialog( padding: isSmallDisplay ? const EdgeInsets.symmetric(horizontal: 8) : null, - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900,), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -67,39 +66,37 @@ void showProjectDialog( size: 16, ), ), - deleteCallback != null - ? (isSmallDisplay + if (deleteCallback != null) isSmallDisplay ? IconButton( iconSize: 16, onPressed: () => deleteCallback( - project.id, parentCallback), + project.id, parentCallback,), icon: Icon( Icons.delete, color: Colors.red.shade900, - )) + ),) : TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.red.shade900), + foregroundColor: Colors.red.shade900,), onPressed: () => deleteCallback( - project.id, parentCallback), + project.id, parentCallback,), label: Text( - isSmallDisplay ? "" : localeMsg.delete), + isSmallDisplay ? "" : localeMsg.delete,), icon: const Icon( Icons.delete, size: 16, ), - )) - : Container(), + ) else Container(), SizedBox(width: isSmallDisplay ? 0 : 10), ElevatedButton( onPressed: () async { if (editInput == "") { showSnackBar(ScaffoldMessenger.of(context), localeMsg.mandatoryProjectName, - isError: true); + isError: true,); } else { saveCallback( - editInput, project, isCreate, parentCallback); + editInput, project, isCreate, parentCallback,); } }, style: isSmallDisplay @@ -109,9 +106,9 @@ void showProjectDialog( ) : null, child: Text(localeMsg.save), - ) + ), ], - ) + ), ], ), ), diff --git a/APP/lib/widgets/select_date.dart b/APP/lib/widgets/select_date.dart index 393090d69..093ed272c 100644 --- a/APP/lib/widgets/select_date.dart +++ b/APP/lib/widgets/select_date.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/pages/select_page.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SelectDate extends StatefulWidget { const SelectDate({super.key}); @@ -16,12 +16,11 @@ class SelectDate extends StatefulWidget { const List datasetOptions = [ '19/12/2022 - Jeu ABCDEF', '18/12/2022 - Jeu JKLMNO', - '17/12/2022 - Jeu UVWXYZ' + '17/12/2022 - Jeu UVWXYZ', ]; class _SelectDateState extends State with TickerProviderStateMixin { late TabController _tabController; - final String _dataset = datasetOptions.first; late FocusNode myFocusNode; @override @@ -41,7 +40,8 @@ class _SelectDateState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - bool isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); + final bool isSmallDisplay = + IsSmallDisplay(MediaQuery.of(context).size.width); return Column( children: [ Text( @@ -51,129 +51,133 @@ class _SelectDateState extends State with TickerProviderStateMixin { const SizedBox(height: 20), Card( child: Container( - alignment: Alignment.center, - child: Column( - children: [ - Align( - alignment: Alignment.center, - child: TabBar( - controller: _tabController, - labelPadding: const EdgeInsets.only(left: 20, right: 20), - labelColor: Colors.black, - unselectedLabelColor: Colors.grey, - labelStyle: TextStyle( - fontSize: 14, - fontFamily: GoogleFonts.inter().fontFamily), - unselectedLabelStyle: TextStyle( - fontSize: 14, - fontFamily: GoogleFonts.inter().fontFamily), - isScrollable: true, - indicatorSize: TabBarIndicatorSize.label, - tabs: [ - Tab( - text: localeMsg.allData, - icon: const Icon(Icons.all_inclusive), - ), - Tab( - text: localeMsg.pickDate, - icon: const Icon(Icons.calendar_month), - ), - // Tab( - // text: localeMsg.openLastDataset, - // icon: Icon(Icons.timelapse), - // ), - // Tab( - // text: localeMsg.openSavedDataser, - // icon: Icon(Icons.calendar_view_day), - // ), - ], + alignment: Alignment.center, + child: Column( + children: [ + Align( + child: TabBar( + controller: _tabController, + labelPadding: const EdgeInsets.only(left: 20, right: 20), + labelColor: Colors.black, + unselectedLabelColor: Colors.grey, + labelStyle: TextStyle( + fontSize: 14, + fontFamily: GoogleFonts.inter().fontFamily, ), + unselectedLabelStyle: TextStyle( + fontSize: 14, + fontFamily: GoogleFonts.inter().fontFamily, + ), + isScrollable: true, + indicatorSize: TabBarIndicatorSize.label, + tabs: [ + Tab( + text: localeMsg.allData, + icon: const Icon(Icons.all_inclusive), + ), + Tab( + text: localeMsg.pickDate, + icon: const Icon(Icons.calendar_month), + ), + // Tab( + // text: localeMsg.openLastDataset, + // icon: Icon(Icons.timelapse), + // ), + // Tab( + // text: localeMsg.openSavedDataser, + // icon: Icon(Icons.calendar_view_day), + // ), + ], ), - Container( - padding: const EdgeInsets.only(left: 20, right: 20), - height: 350, - width: double.maxFinite, - child: TabBarView( - controller: _tabController, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 500.0, - height: 70.0, - child: OutlinedButton( - style: OutlinedButton.styleFrom( - side: BorderSide( - width: 0.3, - color: Colors.blue.shade900)), - onPressed: () { - SelectPage.of(context)!.selectedDate = ""; - myFocusNode.requestFocus(); - }, - autofocus: true, - focusNode: myFocusNode, - child: Text( - localeMsg.allDataBase, - style: GoogleFonts.inter( - fontSize: isSmallDisplay ? 14 : 17, - ), - textAlign: TextAlign.center, + ), + Container( + padding: const EdgeInsets.only(left: 20, right: 20), + height: 350, + width: double.maxFinite, + child: TabBarView( + controller: _tabController, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 500.0, + height: 70.0, + child: OutlinedButton( + style: OutlinedButton.styleFrom( + side: BorderSide( + width: 0.3, + color: Colors.blue.shade900, ), ), - ) - ], - ), - const DatePicker(), - // Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Text( - // localeMsg.useLastDataSet, - // style: Theme.of(context).textTheme.headlineSmall, - // ), - // const SizedBox(height: 32), - // SizedBox( - // width: 500.0, - // height: 70.0, - // child: OutlinedButton( - // onPressed: () {}, - // autofocus: true, - // child: Text( - // 'Données mises à jour le 19/12/2022 à 19h45', - // style: GoogleFonts.inter( - // fontSize: 17, - // ), - // ), - // ), - // ) - // ], - // ), - // Center( - // child: SizedBox( - // width: 500, - // child: Column( - // mainAxisAlignment: MainAxisAlignment.center, - // children: datasetOptions - // .map((dataset) => RadioListTile( - // title: Text(dataset), - // value: dataset, - // groupValue: _dataset, - // onChanged: (String? value) { - // setState(() { - // _dataset = value; - // }); - // }, - // )) - // .toList(), - // ), - // ), - // ), - ], - ), + onPressed: () { + SelectPage.of(context)!.selectedDate = ""; + myFocusNode.requestFocus(); + }, + autofocus: true, + focusNode: myFocusNode, + child: Text( + localeMsg.allDataBase, + style: GoogleFonts.inter( + fontSize: isSmallDisplay ? 14 : 17, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + const DatePicker(), + // Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // localeMsg.useLastDataSet, + // style: Theme.of(context).textTheme.headlineSmall, + // ), + // const SizedBox(height: 32), + // SizedBox( + // width: 500.0, + // height: 70.0, + // child: OutlinedButton( + // onPressed: () {}, + // autofocus: true, + // child: Text( + // 'Données mises à jour le 19/12/2022 à 19h45', + // style: GoogleFonts.inter( + // fontSize: 17, + // ), + // ), + // ), + // ) + // ], + // ), + // Center( + // child: SizedBox( + // width: 500, + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: datasetOptions + // .map((dataset) => RadioListTile( + // title: Text(dataset), + // value: dataset, + // groupValue: _dataset, + // onChanged: (String? value) { + // setState(() { + // _dataset = value; + // }); + // }, + // )) + // .toList(), + // ), + // ), + // ), + ], ), - ], - )), + ), + ], + ), + ), ), ], ); @@ -182,8 +186,8 @@ class _SelectDateState extends State with TickerProviderStateMixin { class DatePicker extends StatefulWidget { const DatePicker({ - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _DatePickerState(); @@ -230,21 +234,21 @@ class _DatePickerState extends State { @override Widget build(BuildContext context) { - print(DateTime.now()); return Center( - child: SizedBox( - width: 700, - height: 700, - child: Container( - padding: const EdgeInsets.fromLTRB(5, 30, 5, 5), - child: SfDateRangePicker( - onSelectionChanged: _onSelectionChanged, - selectionMode: DateRangePickerSelectionMode.range, - enableMultiView: MediaQuery.of(context).size.width > 700, - headerStyle: - const DateRangePickerHeaderStyle(textAlign: TextAlign.center), + child: SizedBox( + width: 700, + height: 700, + child: Container( + padding: const EdgeInsets.fromLTRB(5, 30, 5, 5), + child: SfDateRangePicker( + onSelectionChanged: _onSelectionChanged, + selectionMode: DateRangePickerSelectionMode.range, + enableMultiView: MediaQuery.of(context).size.width > 700, + headerStyle: + const DateRangePickerHeaderStyle(textAlign: TextAlign.center), + ), ), ), - )); + ); } } diff --git a/APP/lib/widgets/select_namespace.dart b/APP/lib/widgets/select_namespace.dart index 8dd7654ca..9994990e9 100644 --- a/APP/lib/widgets/select_namespace.dart +++ b/APP/lib/widgets/select_namespace.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/pages/select_page.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SelectNamespace extends StatefulWidget { const SelectNamespace({super.key}); @@ -14,7 +14,7 @@ class _SelectNamespaceState extends State { Map namespaces = { Namespace.Physical.name: "site.building.room", Namespace.Organisational.name: "domains", - Namespace.Logical.name: "templates&groups" + Namespace.Logical.name: "templates&groups", }; Namespace _selection = Namespace.Physical; @@ -44,15 +44,15 @@ class _SelectNamespaceState extends State { crossAxisAlignment: WrapCrossAlignment.center, children: namespaces.keys.map((label) => nameSpaceButton(label)).toList(), - )), + ),), ), ], ); } Widget nameSpaceButton(String label) { - var isBigScreen = MediaQuery.of(context).size.width > 800; - Namespace thisNamespace = + final isBigScreen = MediaQuery.of(context).size.width > 800; + final Namespace thisNamespace = Namespace.values.firstWhere((e) => e.toString() == 'Namespace.$label'); return Container( margin: const EdgeInsets.only(top: 30, bottom: 30), diff --git a/APP/lib/widgets/select_objects/object_popup.dart b/APP/lib/widgets/select_objects/object_popup.dart index 15005a3b7..a897fec9a 100644 --- a/APP/lib/widgets/select_objects/object_popup.dart +++ b/APP/lib/widgets/select_objects/object_popup.dart @@ -1,11 +1,13 @@ +// ignore_for_file: constant_identifier_names + import 'dart:convert'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/tag.dart'; import 'package:ogree_app/widgets/common/actionbtn_row.dart'; @@ -13,16 +15,17 @@ import 'package:ogree_app/widgets/common/form_field.dart'; import 'package:ogree_app/widgets/tenants/popups/tags_popup.dart'; class ObjectPopup extends StatefulWidget { - Function() parentCallback; - String? objId; - Namespace namespace; - String? parentId; - ObjectPopup( - {super.key, - required this.parentCallback, - required this.namespace, - this.objId, - this.parentId}); + final Function() parentCallback; + final String? objId; + final Namespace namespace; + final String? parentId; + const ObjectPopup({ + super.key, + required this.parentCallback, + required this.namespace, + this.objId, + this.parentId, + }); @override State createState() => _ObjectPopupState(); @@ -59,7 +62,6 @@ Map> objsByNamespace = { class _ObjectPopupState extends State { final _formKey = GlobalKey(); - bool _isSmallDisplay = false; String _objCategory = LogCategories.group.name; String _objId = ""; List customAttributesRows = []; @@ -116,10 +118,8 @@ class _ObjectPopupState extends State { switch (widget.namespace) { case Namespace.Logical: _objCategory = LogCategories.group.name; - break; case Namespace.Organisational: _objCategory = OrgCategories.domain.name; - break; default: _objCategory = PhyCategories.site.name; } @@ -134,130 +134,137 @@ class _ObjectPopupState extends State { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - _isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return FutureBuilder( - future: categoryAttrs.isEmpty ? getExternalAssets() : null, - builder: (context, _) { - if (categoryAttrs.isEmpty) { - return const Center(child: CircularProgressIndicator()); - } + future: categoryAttrs.isEmpty ? getExternalAssets() : null, + builder: (context, _) { + if (categoryAttrs.isEmpty) { + return const Center(child: CircularProgressIndicator()); + } - return Center( - child: Container( - width: 500, - constraints: - BoxConstraints(maxHeight: getPopupHeightByCategory()), - margin: const EdgeInsets.symmetric(horizontal: 20), - decoration: PopupDecoration, - child: Padding( - padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), - child: Form( - key: _formKey, - child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: SingleChildScrollView( - child: Column( - children: [ - Center( - child: Text( - _isEdit - ? (_objCategory.contains("template") - ? localeMsg.viewTemplate - : localeMsg.modifyObj) - : localeMsg.createObj, - style: Theme.of(context) - .textTheme - .headlineMedium, - ), + return Center( + child: Container( + width: 500, + constraints: BoxConstraints(maxHeight: getPopupHeightByCategory()), + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: PopupDecoration, + child: Padding( + padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), + child: Form( + key: _formKey, + child: ScaffoldMessenger( + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: SingleChildScrollView( + child: Column( + children: [ + Center( + child: Text( + _isEdit + ? (_objCategory.contains("template") + ? localeMsg.viewTemplate + : localeMsg.modifyObj) + : localeMsg.createObj, + style: + Theme.of(context).textTheme.headlineMedium, ), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(localeMsg.objType), - const SizedBox(width: 20), - SizedBox( - height: 35, - width: 147, - child: DropdownButtonFormField( - isExpanded: true, - borderRadius: BorderRadius.circular(12.0), - decoration: GetFormInputDecoration( - false, - null, - icon: Icons.bookmark, - ), - value: _objCategory, - items: getCategoryMenuItems(), - onChanged: _isEdit - ? null - : (String? value) { - if (_objCategory != value) { - // clean the whole form - _formKey.currentState?.reset(); - setState(() { - _objCategory = value!; - colorTextControllers.values - .toList() - .forEach((element) { - element.clear(); - }); - colorTextControllers = {}; - }); - } - }, + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(localeMsg.objType), + const SizedBox(width: 20), + SizedBox( + height: 35, + width: 147, + child: DropdownButtonFormField( + isExpanded: true, + borderRadius: BorderRadius.circular(12.0), + decoration: GetFormInputDecoration( + false, + null, + icon: Icons.bookmark, ), + value: _objCategory, + items: getCategoryMenuItems(), + onChanged: _isEdit + ? null + : (String? value) { + if (_objCategory != value) { + // clean the whole form + _formKey.currentState?.reset(); + setState(() { + _objCategory = value!; + colorTextControllers.values + .toList() + .forEach((element) { + element.clear(); + }); + colorTextControllers = {}; + }); + } + }, ), - ], + ), + ], + ), + const SizedBox(height: 10), + SizedBox( + height: getFormHeightByCategory(), + child: getFormByCategory( + _objCategory, + localeMsg, ), - const SizedBox(height: 10), - SizedBox( - height: getFormHeightByCategory(), - child: getFormByCategory( - _objCategory, localeMsg)), - const SizedBox(height: 12), - ActionBtnRow( - isEdit: _isEdit, - onlyDelete: _isEdit && - _objCategory.contains("template"), - submitCreate: () { - if (_objCategory == LogCategories.tag.name) { - return submitActionTag(localeMsg); - } else if (_objCategory - .contains("template")) { - return submitCreateTemplate( - localeMsg, context); - } else { - return submitCreateObject( - localeMsg, context); - } - }, - submitModify: () { - if (_objCategory == LogCategories.tag.name) { - return submitActionTag(localeMsg); - } else if (_objCategory - .contains("template")) { - return; - } else { - return submitModifyObject( - localeMsg, context); - } - }, - submitDelete: () => - submitDeleteAny(localeMsg, context), - ) - ], - ), + ), + const SizedBox(height: 12), + ActionBtnRow( + isEdit: _isEdit, + onlyDelete: + _isEdit && _objCategory.contains("template"), + submitCreate: () { + if (_objCategory == LogCategories.tag.name) { + return submitActionTag(localeMsg); + } else if (_objCategory.contains("template")) { + return submitCreateTemplate( + localeMsg, + context, + ); + } else { + return submitCreateObject( + localeMsg, + context, + ); + } + }, + submitModify: () { + if (_objCategory == LogCategories.tag.name) { + return submitActionTag(localeMsg); + } else if (_objCategory.contains("template")) { + return; + } else { + return submitModifyObject( + localeMsg, + context, + ); + } + }, + submitDelete: () => + submitDeleteAny(localeMsg, context), + ), + ], ), ), - ))), + ), + ), + ), ), ), - ); - }); + ), + ); + }, + ); } double getPopupHeightByCategory() { @@ -298,14 +305,14 @@ class _ObjectPopupState extends State { categories = [ PhyCategories.rack.name, PhyCategories.corridor.name, - PhyCategories.group.name + PhyCategories.group.name, ]; case 3: categories = [PhyCategories.device.name, PhyCategories.group.name]; default: categories = [ PhyCategories.device.name, - PhyCategories.virtual_obj.name + PhyCategories.virtual_obj.name, ]; } } @@ -335,8 +342,8 @@ class _ObjectPopupState extends State { //its a rack searchCategories = ["device"]; } - for (var category in searchCategories) { - var response = await getGroupContent(widget.parentId!, category); + for (final category in searchCategories) { + final response = await getGroupContent(widget.parentId!, category); if (response != null) { groupCheckListContent.addAll(response); } @@ -344,24 +351,24 @@ class _ObjectPopupState extends State { } } - readJsonAssets() async { + Future readJsonAssets() async { final localeMsg = AppLocalizations.of(context)!; - var language = AppLocalizations.of(context)!.localeName; + final language = AppLocalizations.of(context)!.localeName; final messenger = ScaffoldMessenger.of(context); // Get JSON refs/types Map defs; - var result = await fetchSchema("types.json"); + final result = await fetchSchema("types.json"); switch (result) { case Success(value: final value): defs = value["definitions"]; case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); - if (context.mounted) Navigator.pop(context); + if (mounted) Navigator.pop(context); return; } - Map types = {}; - for (var def in defs.keys.toList()) { + final Map types = {}; + for (final def in defs.keys.toList()) { if (defs[def]["descriptions"] != null) { types["refs/types.json#/definitions/$def"] = "${defs[def]["descriptions"][language]}"; @@ -374,7 +381,7 @@ class _ObjectPopupState extends State { // Define JSON schemas to read according to namespace List objects = [ LogCategories.group.name, - LogCategories.virtual_obj.name + LogCategories.virtual_obj.name, ]; if (widget.namespace == Namespace.Physical) { objects = objsByNamespace[widget.namespace]!; @@ -382,32 +389,34 @@ class _ObjectPopupState extends State { objects = objsByNamespace[widget.namespace]!; } - for (var obj in objects) { + for (final obj in objects) { // Read JSON schema Map jsonResult; - var result = await fetchSchema("${obj}_schema.json"); + final result = await fetchSchema("${obj}_schema.json"); switch (result) { case Success(value: final value): jsonResult = value; case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); - if (context.mounted) Navigator.pop(context); + if (mounted) Navigator.pop(context); return; } if (jsonResult["properties"]["attributes"] != null && jsonResult["properties"]["attributes"]["properties"] != null) { // Get all properties - var attrs = Map.from( - jsonResult["properties"]["attributes"]["properties"]); + final attrs = Map.from( + jsonResult["properties"]["attributes"]["properties"], + ); categoryAttrs[obj] = attrs.keys.toList(); categoryAttrs[obj]!.remove("virtual_config"); if (jsonResult["properties"]["attributes"]["required"] != null) { // Get required ones - var requiredAttrs = List.from( - jsonResult["properties"]["attributes"]["required"]); + final requiredAttrs = List.from( + jsonResult["properties"]["attributes"]["required"], + ); for (var i = 0; i < categoryAttrs[obj]!.length; i++) { - var attr = categoryAttrs[obj]![i]; + final attr = categoryAttrs[obj]![i]; if (requiredAttrs.contains(attr)) { categoryAttrs[obj]![i] = "$starSymbol$attr"; } @@ -416,14 +425,15 @@ class _ObjectPopupState extends State { categoryAttrs[obj]!.sort((a, b) => a.compareTo(b)); // Get examples - var examples = List>.from(jsonResult["examples"]); + final examples = + List>.from(jsonResult["examples"]); examplesAttrs[obj] = Map.from(examples[0]["attributes"]); for (var attr in categoryAttrs[obj]!) { attr = attr.replaceFirst(starSymbol, ""); // use original name if (attrs[attr]["\$ref"] != null) { if (types[attrs[attr]["\$ref"]] != null) { - examplesAttrs[obj]![attr] = types[attrs[attr]["\$ref"]]!; + examplesAttrs[obj]![attr] = types[attrs[attr]["\$ref"]]; } } else if (attrs[attr]["enum"] != null) { examplesAttrs[obj]![attr] = @@ -442,27 +452,30 @@ class _ObjectPopupState extends State { } } - getDomains() async { + Future getDomains() async { // Get domains option for dropdown menu of physical final messenger = ScaffoldMessenger.of(context); - var result = await fetchObjectsTree( - namespace: Namespace.Organisational, isTenantMode: false); + final result = await fetchObjectsTree( + namespace: Namespace.Organisational, + ); switch (result) { case Success(value: final listValue): domainList = listValue[0] .values .reduce((value, element) => List.from(value + element)); - print(domainList); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); - if (context.mounted) Navigator.pop(context); + if (mounted) Navigator.pop(context); return; } } Future?> getGroupContent(String parentId, targetCategory) async { - var result = await fetchGroupContent( - parentId, targetCategory, AppLocalizations.of(context)!); + final result = await fetchGroupContent( + parentId, + targetCategory, + AppLocalizations.of(context)!, + ); switch (result) { case Success(value: final value): return value; @@ -471,14 +484,17 @@ class _ObjectPopupState extends State { } } - getObject() async { + Future getObject() async { // Get object info for edit popup final messenger = ScaffoldMessenger.of(context); var errMsg = ""; // Try both id and slug since we dont know the obj's category - for (var keyId in ["id", "slug", "name"]) { - var result = await fetchObject(_objId, AppLocalizations.of(context)!, - idKey: keyId); + for (final keyId in ["id", "slug", "name"]) { + final result = await fetchObject( + _objId, + AppLocalizations.of(context)!, + idKey: keyId, + ); switch (result) { case Success(value: final value): if (widget.namespace == Namespace.Logical) { @@ -488,14 +504,12 @@ class _ObjectPopupState extends State { switch (value["category"]) { case "room": _objCategory = LogCategories.room_template.name; - break; case "building": _objCategory = LogCategories.bldg_template.name; - break; default: _objCategory = LogCategories.obj_template.name; } - var encoder = const JsonEncoder.withIndent(" "); + const encoder = JsonEncoder.withIndent(" "); _loadFileResult = encoder.convert(value); } else { if (value["applicability"] != null) { @@ -527,16 +541,19 @@ class _ObjectPopupState extends State { } _objCategory = value["category"]; if (_objCategory == LogCategories.virtual_obj.name) { - for (var attr in objDataAttrs.entries) { + for (final attr in objDataAttrs.entries) { if (!categoryAttrs[_objCategory]!.contains(attr.key) && !categoryAttrs[_objCategory]! .contains(starSymbol + attr.key) && attr.key != "virtual_config") { // add custom attribute - customAttributesRows.add(CustomAttrRow( + customAttributesRows.add( + CustomAttrRow( customAttributesRows.length, givenAttrName: attr.key, - givenAttrValue: attr.value.toString())); + givenAttrValue: attr.value.toString(), + ), + ); } } } @@ -547,16 +564,19 @@ class _ObjectPopupState extends State { objData = value; objDataAttrs = Map.from(objData["attributes"]); _objCategory = value["category"]; - for (var attr in objDataAttrs.entries) { + for (final attr in objDataAttrs.entries) { if (!categoryAttrs[_objCategory]!.contains(attr.key) && !categoryAttrs[_objCategory]! .contains(starSymbol + attr.key) && attr.key != "virtual_config") { // add custom attribute - customAttributesRows.add(CustomAttrRow( + customAttributesRows.add( + CustomAttrRow( customAttributesRows.length, givenAttrName: attr.key, - givenAttrValue: attr.value)); + givenAttrValue: attr.value, + ), + ); } } } @@ -567,10 +587,10 @@ class _ObjectPopupState extends State { } } showSnackBar(messenger, errMsg, isError: true); - if (context.mounted) Navigator.pop(context); + if (mounted) Navigator.pop(context); } - domainAutoFillField() { + Padding domainAutoFillField() { return Padding( padding: const EdgeInsets.only(right: 10, left: 1, bottom: 6), child: RawAutocomplete( @@ -579,10 +599,12 @@ class _ObjectPopupState extends State { return option.contains(textEditingValue.text); }); }, - fieldViewBuilder: (BuildContext context, - TextEditingController textEditingController, - FocusNode focusNode, - VoidCallback onFieldSubmitted) { + fieldViewBuilder: ( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { if (objData["domain"] != null) { textEditingController.text = objData["domain"]; } @@ -591,8 +613,10 @@ class _ObjectPopupState extends State { focusNode: focusNode, style: const TextStyle(fontSize: 14), decoration: GetFormInputDecoration( - false, "$starSymbol${AppLocalizations.of(context)!.domain}", - icon: Icons.edit), + false, + "$starSymbol${AppLocalizations.of(context)!.domain}", + icon: Icons.edit, + ), onFieldSubmitted: (String value) { objData["domain"] = value; onFieldSubmitted(); @@ -606,9 +630,11 @@ class _ObjectPopupState extends State { }, ); }, - optionsViewBuilder: (BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options) { + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { return Align( alignment: Alignment.topLeft, child: Material( @@ -641,73 +667,79 @@ class _ObjectPopupState extends State { ); } - CustomAttrRow(int rowIdx, - {bool useDefaultValue = true, - String? givenAttrName, - String? givenAttrValue}) { - UniqueKey key = UniqueKey(); + StatefulBuilder CustomAttrRow( + int rowIdx, { + bool useDefaultValue = true, + String? givenAttrName, + String? givenAttrValue, + }) { + final UniqueKey key = UniqueKey(); return StatefulBuilder( - key: key, - builder: (context, localSetState) { - String? attrName; - if (givenAttrName != null) { - attrName = givenAttrName; - } - return Padding( - padding: const EdgeInsets.only(top: 2.0), - child: SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Flexible( - flex: 5, - child: CustomFormField( - save: (newValue) => attrName = newValue, - label: AppLocalizations.of(context)!.attribute, - icon: Icons.tag_sharp, - isCompact: true, - initialValue: givenAttrName), + key: key, + builder: (context, localSetState) { + String? attrName; + if (givenAttrName != null) { + attrName = givenAttrName; + } + return Padding( + padding: const EdgeInsets.only(top: 2.0), + child: SizedBox( + height: 60, + child: Row( + children: [ + Flexible( + flex: 5, + child: CustomFormField( + save: (newValue) => attrName = newValue, + label: AppLocalizations.of(context)!.attribute, + icon: Icons.tag_sharp, + isCompact: true, + initialValue: givenAttrName, ), - Padding( - padding: const EdgeInsets.only(right: 6), - child: Icon( - Icons.arrow_forward, - color: Colors.blue.shade600, - ), + ), + Padding( + padding: const EdgeInsets.only(right: 6), + child: Icon( + Icons.arrow_forward, + color: Colors.blue.shade600, ), - Flexible( - flex: 4, - child: CustomFormField( - save: (newValue) => objDataAttrs[attrName!] = newValue!, - label: "Value", - icon: Icons.tag_sharp, - isCompact: true, - initialValue: givenAttrValue), + ), + Flexible( + flex: 4, + child: CustomFormField( + save: (newValue) => objDataAttrs[attrName!] = newValue, + label: "Value", + icon: Icons.tag_sharp, + isCompact: true, + initialValue: givenAttrValue, ), - IconButton( - padding: const EdgeInsets.only(bottom: 6, right: 3), - constraints: const BoxConstraints(), - iconSize: 18, - onPressed: () { - setState( - () => customAttributesRows.removeWhere((element) { - return element.key == key; - })); - objDataAttrs.remove(attrName); - }, - icon: Icon( - Icons.delete, - color: Colors.red.shade400, - )), - ], - ), + ), + IconButton( + padding: const EdgeInsets.only(bottom: 6, right: 3), + constraints: const BoxConstraints(), + iconSize: 18, + onPressed: () { + setState( + () => customAttributesRows.removeWhere((element) { + return element.key == key; + }), + ); + objDataAttrs.remove(attrName); + }, + icon: Icon( + Icons.delete, + color: Colors.red.shade400, + ), + ), + ], ), - ); - }); + ), + ); + }, + ); } - getFormByCategory(String category, AppLocalizations localeMsg) { + dynamic getFormByCategory(String category, AppLocalizations localeMsg) { if (widget.namespace == Namespace.Physical || widget.namespace == Namespace.Organisational || category == LogCategories.group.name || @@ -723,13 +755,13 @@ class _ObjectPopupState extends State { } } - getObjectForm() { - List attributes = categoryAttrs[_objCategory]!; + ListView getObjectForm() { + final List attributes = categoryAttrs[_objCategory]!; final localeMsg = AppLocalizations.of(context)!; - for (var str in attributes) { + for (final str in attributes) { if (str.toLowerCase().contains("color")) { - var textEditingController = TextEditingController(); + final textEditingController = TextEditingController(); colorTextControllers.putIfAbsent(str, () => textEditingController); } } @@ -737,58 +769,66 @@ class _ObjectPopupState extends State { return ListView( padding: EdgeInsets.zero, children: [ - _objCategory != PhyCategories.site.name - ? CustomFormField( - save: (newValue) { - if (newValue != null && newValue.isNotEmpty) { - objData["parentId"] = newValue; - } - }, - label: - "${_objCategory == LogCategories.virtual_obj.name ? "" : starSymbol}Parent ID", - icon: Icons.family_restroom, - initialValue: objData["parentId"], - tipStr: localeMsg.parentIdTip, - shouldValidate: widget.namespace != Namespace.Organisational && - _objCategory != LogCategories.virtual_obj.name) - : Container(), + if (_objCategory != PhyCategories.site.name) + CustomFormField( + save: (newValue) { + if (newValue != null && newValue.isNotEmpty) { + objData["parentId"] = newValue; + } + }, + label: + "${_objCategory == LogCategories.virtual_obj.name ? "" : starSymbol}Parent ID", + icon: Icons.family_restroom, + initialValue: objData["parentId"], + tipStr: localeMsg.parentIdTip, + shouldValidate: widget.namespace != Namespace.Organisational && + _objCategory != LogCategories.virtual_obj.name, + ) + else + Container(), CustomFormField( - save: (newValue) => objData["name"] = newValue, - label: "$starSymbol${localeMsg.name}", - icon: Icons.edit, - tipStr: localeMsg.nameTip, - initialValue: objData["name"]), - _objCategory != OrgCategories.domain.name - ? (domainList.isEmpty - ? CustomFormField( - save: (newValue) => objData["domain"] = newValue, - label: "$starSymbol${localeMsg.domain}", - icon: Icons.edit, - initialValue: objData["domain"]) - : domainAutoFillField()) - : Container(), + save: (newValue) => objData["name"] = newValue, + label: "$starSymbol${localeMsg.name}", + icon: Icons.edit, + tipStr: localeMsg.nameTip, + initialValue: objData["name"], + ), + if (_objCategory != OrgCategories.domain.name) + domainList.isEmpty + ? CustomFormField( + save: (newValue) => objData["domain"] = newValue, + label: "$starSymbol${localeMsg.domain}", + icon: Icons.edit, + initialValue: objData["domain"], + ) + : domainAutoFillField() + else + Container(), CustomFormField( - save: (newValue) => objData["description"] = newValue, - label: localeMsg.description, - icon: Icons.edit, + save: (newValue) => objData["description"] = newValue, + label: localeMsg.description, + icon: Icons.edit, + shouldValidate: false, + initialValue: objData["description"], + ), + if (_objCategory != OrgCategories.domain.name) + CustomFormField( + save: (newValue) { + final tags = newValue!.replaceAll(" ", "").split(","); + if (!(tags.length == 1 && tags.first == "")) { + objData["tags"] = tags; + } + }, + label: "Tags", + icon: Icons.tag_sharp, shouldValidate: false, - initialValue: objData["description"]), - _objCategory != OrgCategories.domain.name - ? CustomFormField( - save: (newValue) { - var tags = newValue!.replaceAll(" ", "").split(","); - if (!(tags.length == 1 && tags.first == "")) { - objData["tags"] = tags; - } - }, - label: "Tags", - icon: Icons.tag_sharp, - shouldValidate: false, - tipStr: localeMsg.tagTip, - initialValue: objData["tags"] - ?.toString() - .substring(1, objData["tags"].toString().length - 1)) - : Container(), + tipStr: localeMsg.tagTip, + initialValue: objData["tags"] + ?.toString() + .substring(1, objData["tags"].toString().length - 1), + ) + else + Container(), Padding( padding: const EdgeInsets.only(top: 4.0, left: 6, bottom: 6), child: Text(localeMsg.attributes), @@ -804,60 +844,59 @@ class _ObjectPopupState extends State { crossAxisCount: 2, children: List.generate(attributes.length, (index) { return CustomFormField( - tipStr: examplesAttrs[_objCategory] - ?[attributes[index].replaceFirst(starSymbol, "")] ?? - "", - save: (newValue) { - var attrKey = - attributes[index].replaceFirst(starSymbol, ""); - if (newValue != null && newValue.isNotEmpty) { - // check type - var numValue = num.tryParse(newValue); - if (attrKey == "virtual_config") { - objDataAttrs[attrKey] = json.decode(newValue); - } else if (numValue != null) { - // is number - objDataAttrs[attrKey] = numValue.toDouble(); - } else if (newValue.length >= 2 && - newValue[0] == "[" && - newValue[newValue.length - 1] == "]") { - // is array - var arrStr = newValue - .substring(1, newValue.length - 1) - .split(","); - try { - List arrNum = - arrStr.map(double.parse).toList(); - objDataAttrs[attrKey] = arrNum; - } on Exception catch (_) { - objDataAttrs[attrKey] = arrStr; - } - } else if (newValue == "true") { - objDataAttrs[attrKey] = true; - } else if (newValue == "false") { - objDataAttrs[attrKey] = false; - } else { - // is string - objDataAttrs[attrKey] = newValue; + tipStr: examplesAttrs[_objCategory] + ?[attributes[index].replaceFirst(starSymbol, "")] ?? + "", + save: (newValue) { + final attrKey = + attributes[index].replaceFirst(starSymbol, ""); + if (newValue != null && newValue.isNotEmpty) { + // check type + final numValue = num.tryParse(newValue); + if (attrKey == "virtual_config") { + objDataAttrs[attrKey] = json.decode(newValue); + } else if (numValue != null) { + // is number + objDataAttrs[attrKey] = numValue.toDouble(); + } else if (newValue.length >= 2 && + newValue[0] == "[" && + newValue[newValue.length - 1] == "]") { + // is array + final arrStr = + newValue.substring(1, newValue.length - 1).split(","); + try { + final List arrNum = + arrStr.map(double.parse).toList(); + objDataAttrs[attrKey] = arrNum; + } on Exception catch (_) { + objDataAttrs[attrKey] = arrStr; } + } else if (newValue == "true") { + objDataAttrs[attrKey] = true; + } else if (newValue == "false") { + objDataAttrs[attrKey] = false; + } else { + // is string + objDataAttrs[attrKey] = newValue; } - }, - label: attributes[index], - icon: Icons.tag_sharp, - isCompact: true, - shouldValidate: attributes[index].contains(starSymbol), - isColor: colorTextControllers[attributes[index]] != null, - colorTextController: colorTextControllers[attributes[index]], - checkListController: - _objCategory == PhyCategories.group.name && - attributes[index] == "${starSymbol}content" && - groupCheckListContent.isNotEmpty - ? checkListController - : null, - checkListValues: groupCheckListContent, - initialValue: objDataAttrs[ - attributes[index].replaceFirst(starSymbol, "")] - ?.toString()); + } + }, + label: attributes[index], + icon: Icons.tag_sharp, + isCompact: true, + shouldValidate: attributes[index].contains(starSymbol), + isColor: colorTextControllers[attributes[index]] != null, + colorTextController: colorTextControllers[attributes[index]], + checkListController: _objCategory == PhyCategories.group.name && + attributes[index] == "${starSymbol}content" && + groupCheckListContent.isNotEmpty + ? checkListController + : null, + checkListValues: groupCheckListContent, + initialValue: + objDataAttrs[attributes[index].replaceFirst(starSymbol, "")] + ?.toString(), + ); }), ), ), @@ -870,12 +909,13 @@ class _ObjectPopupState extends State { child: Align( alignment: Alignment.bottomLeft, child: TextButton.icon( - onPressed: () => setState(() { - customAttributesRows - .add(CustomAttrRow(customAttributesRows.length)); - }), - icon: const Icon(Icons.add), - label: Text(localeMsg.attribute)), + onPressed: () => setState(() { + customAttributesRows + .add(CustomAttrRow(customAttributesRows.length)); + }), + icon: const Icon(Icons.add), + label: Text(localeMsg.attribute), + ), ), ), virtualConfigInput(), @@ -883,16 +923,16 @@ class _ObjectPopupState extends State { ); } - virtualConfigInput() { + Widget virtualConfigInput() { if (_objCategory != "virtual_obj" && _objCategory != "device") { return Container(); } - Map virtualAttrs = { + final Map virtualAttrs = { "clusterId": "string (e.g. kube-cluster)", "type": "string (e.g. node)", - "role": "string (e.g. master)" + "role": "string (e.g. master)", }; - List virtualAttrsKeys = virtualAttrs.keys.toList(); + final List virtualAttrsKeys = virtualAttrs.keys.toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -911,21 +951,22 @@ class _ObjectPopupState extends State { crossAxisCount: 2, children: List.generate(virtualAttrsKeys.length, (index) { return CustomFormField( - tipStr: virtualAttrs[virtualAttrsKeys[index]] ?? "string", - save: (newValue) { - if (objDataAttrs["virtual_config"] == null) { - objDataAttrs["virtual_config"] = {}; - } - objDataAttrs["virtual_config"][virtualAttrsKeys[index]] = - newValue; - }, - label: virtualAttrsKeys[index], - icon: Icons.tag_sharp, - isCompact: true, - shouldValidate: false, - initialValue: objDataAttrs["virtual_config"] - ?[virtualAttrsKeys[index]] - ?.toString()); + tipStr: virtualAttrs[virtualAttrsKeys[index]] ?? "string", + save: (newValue) { + if (objDataAttrs["virtual_config"] == null) { + objDataAttrs["virtual_config"] = {}; + } + objDataAttrs["virtual_config"][virtualAttrsKeys[index]] = + newValue; + }, + label: virtualAttrsKeys[index], + icon: Icons.tag_sharp, + isCompact: true, + shouldValidate: false, + initialValue: objDataAttrs["virtual_config"] + ?[virtualAttrsKeys[index]] + ?.toString(), + ); }), ), ), @@ -933,9 +974,9 @@ class _ObjectPopupState extends State { ); } - getLayerForm() { + ListView getLayerForm() { final localeMsg = AppLocalizations.of(context)!; - checkBoxWrapper(Checkbox checkbox, String text) => Wrap( + Wrap checkBoxWrapper(Checkbox checkbox, String text) => Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ SizedBox(height: 24, width: 24, child: checkbox), @@ -949,112 +990,130 @@ class _ObjectPopupState extends State { ), ], ); - return ListView(padding: EdgeInsets.zero, children: [ - CustomFormField( + return ListView( + padding: EdgeInsets.zero, + children: [ + CustomFormField( save: (newValue) => objData["slug"] = newValue, label: localeMsg.name, icon: Icons.edit, - initialValue: objData["slug"]), - CustomFormField( + initialValue: objData["slug"], + ), + CustomFormField( save: (newValue) => objData["applicability"] = newValue, label: localeMsg.applicability, icon: Icons.edit, tipStr: localeMsg.applicabilityTooltip, - initialValue: objData["applicability"]), - const SizedBox(height: 3), - Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Text( - localeMsg.applyAlso, - style: const TextStyle( - fontSize: 14, - color: Colors.black, - ), + initialValue: objData["applicability"], ), - checkBoxWrapper( - Checkbox( - value: _applyDirectChild, - onChanged: _applyAllChild - ? null - : (bool? value) => setState(() => _applyDirectChild = value!), - ), - localeMsg.directChildren, - ), - checkBoxWrapper( - Checkbox( - value: _applyAllChild, - onChanged: (bool? value) => setState(() { - _applyAllChild = value!; - _applyDirectChild = value; - }), - ), - localeMsg.allChildren, + const SizedBox(height: 3), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + localeMsg.applyAlso, + style: const TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + checkBoxWrapper( + Checkbox( + value: _applyDirectChild, + onChanged: _applyAllChild + ? null + : (bool? value) => + setState(() => _applyDirectChild = value!), + ), + localeMsg.directChildren, + ), + checkBoxWrapper( + Checkbox( + value: _applyAllChild, + onChanged: (bool? value) => setState(() { + _applyAllChild = value!; + _applyDirectChild = value; + }), + ), + localeMsg.allChildren, + ), + ], ), - ]), - const SizedBox(height: 10), - CustomFormField( + const SizedBox(height: 10), + CustomFormField( save: (newValue) => objData["filter"] = newValue, label: localeMsg.filter, icon: Icons.filter_alt, tipStr: localeMsg.filterLayerTooltip, - initialValue: objData["filter"]), - ]); + initialValue: objData["filter"], + ), + ], + ); } - getTemplatesForm(AppLocalizations localeMsg) { + Center getTemplatesForm(AppLocalizations localeMsg) { return Center( - child: ListView(shrinkWrap: true, children: [ - _loadFileResult == null - ? Align( - child: ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = await FilePicker.platform - .pickFiles( - type: FileType.custom, - allowedExtensions: ["json"], - withData: true); - if (result != null) { - setState(() { - _loadedFile = result.files.single; - }); - } - }, - icon: const Icon(Icons.download), - label: Text(localeMsg.selectJSON)), - ) - : Container(), - _loadedFile != null - ? Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), - child: Align( - child: Text(localeMsg.fileLoaded(_loadedFile!.name)), - ), - ) - : Container(), - _loadFileResult != null - ? Container( - color: Colors.black, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - _loadFileResult!, - style: const TextStyle(color: Colors.white), - ), + child: ListView( + shrinkWrap: true, + children: [ + if (_loadFileResult == null) + Align( + child: ElevatedButton.icon( + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ["json"], + withData: true, + ); + if (result != null) { + setState(() { + _loadedFile = result.files.single; + }); + } + }, + icon: const Icon(Icons.download), + label: Text(localeMsg.selectJSON), + ), + ) + else + Container(), + if (_loadedFile != null) + Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), + child: Align( + child: Text(localeMsg.fileLoaded(_loadedFile!.name)), + ), + ) + else + Container(), + if (_loadFileResult != null) + Container( + color: Colors.black, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + _loadFileResult!, + style: const TextStyle(color: Colors.white), ), - ) - : Container(), - ]), + ), + ) + else + Container(), + ], + ), ); } - submitActionTag(AppLocalizations localeMsg) async { + Future submitActionTag(AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); - Tag? newTag = _tagFormKey.currentState!.onActionBtnPressed(); + final Tag? newTag = _tagFormKey.currentState!.onActionBtnPressed(); if (newTag == null) { return; } Result result; if (_isEdit) { - var newTagMap = newTag.toMap(); + final newTagMap = newTag.toMap(); if (newTag.image == "" && tag!.image != "") { newTagMap.remove("image"); // patch and keep old one } @@ -1065,28 +1124,32 @@ class _ObjectPopupState extends State { switch (result) { case Success(): widget.parentCallback(); - showSnackBar(messenger, - "${_isEdit ? localeMsg.modifyOK : localeMsg.createOK} 🥳", - isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + showSnackBar( + messenger, + "${_isEdit ? localeMsg.modifyOK : localeMsg.createOK} 🥳", + isSuccess: true, + ); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); } } submitCreateTemplate( - AppLocalizations localeMsg, BuildContext popupContext) async { + AppLocalizations localeMsg, + BuildContext popupContext, + ) async { final messenger = ScaffoldMessenger.of(context); final errorMessenger = ScaffoldMessenger.of(popupContext); if (_loadedFile == null) { showSnackBar(messenger, localeMsg.mustSelectJSON); } else { - var result = await createTemplate(_loadedFile!.bytes!, _objCategory); + final result = await createTemplate(_loadedFile!.bytes!, _objCategory); switch (result) { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.createOK, isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): showSnackBar(errorMessenger, exception.toString(), isError: true); } @@ -1094,7 +1157,9 @@ class _ObjectPopupState extends State { } submitCreateObject( - AppLocalizations localeMsg, BuildContext popupContext) async { + AppLocalizations localeMsg, + BuildContext popupContext, + ) async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); @@ -1116,18 +1181,23 @@ class _ObjectPopupState extends State { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.createOK, isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): - showSnackBar(errorMessenger, exception.toString(), - isError: true, - copyTextTap: exception.toString(), - duration: const Duration(seconds: 30)); + showSnackBar( + errorMessenger, + exception.toString(), + isError: true, + copyTextTap: exception.toString(), + duration: const Duration(seconds: 30), + ); } } } submitModifyObject( - AppLocalizations localeMsg, BuildContext popupContext) async { + AppLocalizations localeMsg, + BuildContext popupContext, + ) async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); @@ -1154,7 +1224,7 @@ class _ObjectPopupState extends State { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.modifyOK, isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): showSnackBar(errorMessenger, exception.toString(), isError: true); } @@ -1164,14 +1234,12 @@ class _ObjectPopupState extends State { submitDeleteAny(AppLocalizations localeMsg, BuildContext popupContext) async { final messenger = ScaffoldMessenger.of(context); final errorMessenger = ScaffoldMessenger.of(popupContext); - var result = await deleteObject(_objId, _objCategory); + final result = await deleteObject(_objId, _objCategory); switch (result) { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.deleteOK); - if (context.mounted) { - Navigator.of(context).pop(); - } + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): showSnackBar(errorMessenger, exception.toString(), isError: true); } diff --git a/APP/lib/widgets/select_objects/select_objects.dart b/APP/lib/widgets/select_objects/select_objects.dart index 781ea629f..94fe8d4fd 100644 --- a/APP/lib/widgets/select_objects/select_objects.dart +++ b/APP/lib/widgets/select_objects/select_objects.dart @@ -1,25 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/pages/select_page.dart'; import 'package:ogree_app/widgets/select_objects/object_popup.dart'; +import 'package:ogree_app/widgets/select_objects/settings_view/settings_view.dart'; +import 'package:ogree_app/widgets/select_objects/tree_view/custom_tree_view.dart'; import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; import 'package:ogree_app/widgets/tenants/popups/domain_popup.dart'; -import 'settings_view/settings_view.dart'; -import 'tree_view/custom_tree_view.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SelectObjects extends StatefulWidget { final String dateRange; final Namespace namespace; bool load; - SelectObjects( - {super.key, - required this.dateRange, - required this.namespace, - required this.load}); + SelectObjects({ + super.key, + required this.dateRange, + required this.namespace, + required this.load, + }); @override State createState() => _SelectObjectsState(); } @@ -39,46 +40,51 @@ class _SelectObjectsState extends State { : SelectPage.of(context)!.selectedObjects, dateRange: widget.dateRange, reload: widget.load, - argNamespace: widget.namespace) + argNamespace: widget.namespace, + ) : null, builder: (_, __) { - print(widget.load); if (appController.isInitialized && widget.load) { return _Unfocus( child: Card( margin: const EdgeInsets.all(0.1), child: appController.treeController.roots.isEmpty - ? Stack(children: [ - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.warning_rounded, - size: 50, - color: Colors.grey.shade600, - ), - Padding( - padding: const EdgeInsets.only(top: 16), - child: Text( - "${AppLocalizations.of(context)!.noObjectsFound} :("), - ), - ], + ? Stack( + children: [ + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.warning_rounded, + size: 50, + color: Colors.grey.shade600, + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Text( + "${AppLocalizations.of(context)!.noObjectsFound} :(", + ), + ), + ], + ), ), - ), - addObjectButton( + addObjectButton( context, widget.namespace, () => setState(() { - widget.load = true; - })), - ]) + widget.load = true; + }), + ), + ], + ) : _ResponsiveBody( namespace: widget.namespace, controller: appController, callback: () => setState(() { - widget.load = true; - })), + widget.load = true; + }), + ), ), ); } @@ -90,7 +96,7 @@ class _SelectObjectsState extends State { } class _Unfocus extends StatelessWidget { - const _Unfocus({Key? key, required this.child}) : super(key: key); + const _Unfocus({required this.child}); final Widget child; @@ -108,12 +114,11 @@ class _ResponsiveBody extends StatelessWidget { final Namespace namespace; final TreeAppController controller; final Function() callback; - const _ResponsiveBody( - {Key? key, - required this.namespace, - required this.controller, - required this.callback}) - : super(key: key); + const _ResponsiveBody({ + required this.namespace, + required this.controller, + required this.callback, + }); @override Widget build(BuildContext context) { @@ -129,12 +134,13 @@ class _ResponsiveBody extends StatelessWidget { padding: const EdgeInsets.only(bottom: 20, right: 20), child: ElevatedButton.icon( onPressed: () => showCustomPopup( - context, - SettingsViewPopup( - controller: controller, - namespace: namespace, - ), - isDismissible: true), + context, + SettingsViewPopup( + controller: controller, + namespace: namespace, + ), + isDismissible: true, + ), icon: const Icon(Icons.filter_alt_outlined), label: Text(AppLocalizations.of(context)!.filters), ), @@ -148,28 +154,33 @@ class _ResponsiveBody extends StatelessWidget { child: Row( children: [ Flexible( - flex: 2, - child: Stack( - children: [ - CustomTreeView(isTenantMode: false), - addObjectButton(context, namespace, callback), - ], - )), + flex: 2, + child: Stack( + children: [ + const CustomTreeView(isTenantMode: false), + addObjectButton(context, namespace, callback), + ], + ), + ), const VerticalDivider( width: 1, thickness: 1, color: Colors.black26, ), Expanded( - child: SettingsView(isTenantMode: false, namespace: namespace)), + child: SettingsView(isTenantMode: false, namespace: namespace), + ), ], ), ); } } -addObjectButton( - BuildContext context, Namespace namespace, Function() callback) { +Padding addObjectButton( + BuildContext context, + Namespace namespace, + Function() callback, +) { return Padding( padding: const EdgeInsets.only(right: 12, bottom: 6), child: Align( @@ -178,20 +189,21 @@ addObjectButton( height: 34, width: 34, child: IconButton( - padding: EdgeInsets.all(0.0), + padding: const EdgeInsets.all(0.0), iconSize: 24, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade600, foregroundColor: Colors.white, ), onPressed: () => showCustomPopup( - context, - namespace == Namespace.Organisational - ? DomainPopup( - parentCallback: callback, - ) - : ObjectPopup(parentCallback: callback, namespace: namespace), - isDismissible: true), + context, + namespace == Namespace.Organisational + ? DomainPopup( + parentCallback: callback, + ) + : ObjectPopup(parentCallback: callback, namespace: namespace), + isDismissible: true, + ), icon: const Icon(Icons.add), ), ), @@ -203,8 +215,11 @@ class SettingsViewPopup extends StatelessWidget { final TreeAppController controller; final Namespace namespace; - const SettingsViewPopup( - {super.key, required this.controller, required this.namespace}); + const SettingsViewPopup({ + super.key, + required this.controller, + required this.namespace, + }); @override Widget build(BuildContext context) { @@ -212,41 +227,45 @@ class SettingsViewPopup extends StatelessWidget { child: SizedBox( height: 500, child: TreeAppControllerScope( - controller: controller, - child: Container( - width: 500, - constraints: const BoxConstraints(maxHeight: 625), - margin: const EdgeInsets.symmetric(horizontal: 20), - decoration: PopupDecoration, - child: Padding( - padding: const EdgeInsets.fromLTRB(20, 20, 30, 15), - child: Material( - color: Colors.white, - child: ListView( - padding: EdgeInsets.zero, - shrinkWrap: true, - children: [ - SizedBox( - height: 420, - child: SettingsView( - isTenantMode: false, - namespace: namespace, - ), - ), - const SizedBox(height: 10), - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(AppLocalizations.of(context)!.close), - icon: const Icon( - Icons.cancel_outlined, - size: 16, - ), - ), - ], - )), - ))), + controller: controller, + child: Container( + width: 500, + constraints: const BoxConstraints(maxHeight: 625), + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: PopupDecoration, + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 20, 30, 15), + child: Material( + color: Colors.white, + child: ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + SizedBox( + height: 420, + child: SettingsView( + isTenantMode: false, + namespace: namespace, + ), + ), + const SizedBox(height: 10), + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, + ), + onPressed: () => Navigator.pop(context), + label: Text(AppLocalizations.of(context)!.close), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + ], + ), + ), + ), + ), + ), ), ); } diff --git a/APP/lib/widgets/select_objects/settings_view/_actions.dart b/APP/lib/widgets/select_objects/settings_view/_actions.dart index d68e70b85..2d3acb7e0 100644 --- a/APP/lib/widgets/select_objects/settings_view/_actions.dart +++ b/APP/lib/widgets/select_objects/settings_view/_actions.dart @@ -2,7 +2,7 @@ part of 'settings_view.dart'; class _Actions extends StatelessWidget { final bool isTenantMode; - const _Actions({Key? key, required this.isTenantMode}) : super(key: key); + const _Actions({required this.isTenantMode}); @override Widget build(BuildContext context) { @@ -21,11 +21,11 @@ class _Actions extends StatelessWidget { actionsRow.add(_Action( label: Text(localeMsg.selectAll), onPressed: TreeAppController.of(context).selectAll, - )); + ),); actionsRow.add(_Action( label: Text(localeMsg.deselectAll), onPressed: () => TreeAppController.of(context).selectAll(false), - )); + ),); } return Wrap( spacing: 10, @@ -37,10 +37,9 @@ class _Actions extends StatelessWidget { class _Action extends StatelessWidget { const _Action({ - Key? key, required this.label, this.onPressed, - }) : super(key: key); + }); final Widget label; final VoidCallback? onPressed; diff --git a/APP/lib/widgets/select_objects/settings_view/_advanced_find_field.dart b/APP/lib/widgets/select_objects/settings_view/_advanced_find_field.dart index 5e9114aa5..081308b80 100644 --- a/APP/lib/widgets/select_objects/settings_view/_advanced_find_field.dart +++ b/APP/lib/widgets/select_objects/settings_view/_advanced_find_field.dart @@ -23,7 +23,6 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { return TextField( controller: controller, - autofocus: false, style: const TextStyle(fontSize: 14), decoration: GetFormInputDecoration( false, @@ -44,8 +43,10 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { color: Colors.white, ), padding: const EdgeInsets.all(13), - child: const Icon(Icons.info_outline_rounded, - color: Colors.blueAccent), + child: const Icon( + Icons.info_outline_rounded, + color: Colors.blueAccent, + ), ), ), ), @@ -71,11 +72,13 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { return; } - var result = await fetchWithComplexFilter( - searchExpression, widget.namespace, localeMsg); + final result = await fetchWithComplexFilter( + searchExpression, + widget.namespace, + localeMsg, + ); switch (result) { case Success(value: final foundObjs): - print(foundObjs); ids = getIdsFromObjects(foundObjs); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); @@ -99,8 +102,8 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { } List getIdsFromObjects(List> foundObjs) { - List ids = []; - for (var obj in foundObjs) { + final List ids = []; + for (final obj in foundObjs) { ids.add(obj["id"] as String); } return ids; @@ -113,11 +116,13 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { final messenger = ScaffoldMessenger.of(context); List nodes; - var result = await fetchWithComplexFilter( - searchExpression, widget.namespace, localeMsg); + final result = await fetchWithComplexFilter( + searchExpression, + widget.namespace, + localeMsg, + ); switch (result) { case Success(value: final foundObjs): - print(foundObjs); nodes = getTreeNodesFromObjects(foundObjs, appController); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); @@ -140,7 +145,7 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { if (!appController.treeController.areAllRootsCollapsed) { appController.treeController.collapseAll(); } - for (var node in nodes) { + for (final node in nodes) { appController.treeController.expandAncestors(node); appController.scrollTo(node); appController.selectNode(node.id); @@ -150,12 +155,14 @@ class _AdvancedFindFieldState extends State<_AdvancedFindField> { } List getTreeNodesFromObjects( - List> foundObjs, TreeAppController appController) { - List nodes = []; - for (var obj in foundObjs) { - var id = obj["id"] as String; + List> foundObjs, + TreeAppController appController, + ) { + final List nodes = []; + for (final obj in foundObjs) { + final id = obj["id"] as String; // search for this obj on root node or in its children - for (var root in appController.treeController.roots) { + for (final root in appController.treeController.roots) { TreeNode? node; if (root.id.toLowerCase().contains(id.toLowerCase())) { node = root; diff --git a/APP/lib/widgets/select_objects/settings_view/_find_node_field.dart b/APP/lib/widgets/select_objects/settings_view/_find_node_field.dart index 78190bf12..4ac2855e7 100644 --- a/APP/lib/widgets/select_objects/settings_view/_find_node_field.dart +++ b/APP/lib/widgets/select_objects/settings_view/_find_node_field.dart @@ -20,7 +20,6 @@ class __FindNodeFieldState extends State<_FindNodeField> { Widget build(BuildContext context) { return TextField( controller: controller, - autofocus: false, style: const TextStyle(fontSize: 14), decoration: GetFormInputDecoration( false, @@ -37,7 +36,7 @@ class __FindNodeFieldState extends State<_FindNodeField> { final localeMsg = AppLocalizations.of(context)!; TreeNode? node; - for (var root in appController.treeController.roots) { + for (final root in appController.treeController.roots) { if (root.id.toLowerCase().contains(id.toLowerCase())) { node = root; break; diff --git a/APP/lib/widgets/select_objects/settings_view/_header.dart b/APP/lib/widgets/select_objects/settings_view/_header.dart index 175344a00..7edc70320 100644 --- a/APP/lib/widgets/select_objects/settings_view/_header.dart +++ b/APP/lib/widgets/select_objects/settings_view/_header.dart @@ -1,7 +1,7 @@ part of 'settings_view.dart'; class SettingsHeader extends StatelessWidget { - const SettingsHeader({Key? key, required this.text}) : super(key: key); + const SettingsHeader({super.key, required this.text}); final String text; diff --git a/APP/lib/widgets/select_objects/settings_view/_selected_chips.dart b/APP/lib/widgets/select_objects/settings_view/_selected_chips.dart index 0933f7ccd..729db411c 100644 --- a/APP/lib/widgets/select_objects/settings_view/_selected_chips.dart +++ b/APP/lib/widgets/select_objects/settings_view/_selected_chips.dart @@ -1,7 +1,7 @@ part of 'settings_view.dart'; class SelectedChips extends StatefulWidget { - const SelectedChips({Key? key}) : super(key: key); + const SelectedChips({super.key}); @override State createState() => _SelectedChipsState(); @@ -16,7 +16,7 @@ class _SelectedChipsState extends State { final appController = TreeAppController.of(context); if (shouldGroupBy.isEmpty && appController.fetchedCategories["room"] != null) { - for (var room in appController.fetchedCategories["room"]!) { + for (final room in appController.fetchedCategories["room"]!) { shouldGroupBy[room] = true; } } @@ -29,16 +29,16 @@ class _SelectedChipsState extends State { children: getChips(Map.from(appController.selectedNodes), appController), ); - }); + },); } List getChips(Map nodes, appController) { - List chips = []; - Map> groups = {}; // group name: [group nodes] + final List chips = []; + final Map> groups = {}; // group name: [group nodes] // Group by, create groups - for (var key in nodes.keys) { - for (var group in shouldGroupBy.keys) { + for (final key in nodes.keys) { + for (final group in shouldGroupBy.keys) { if (key.contains(group)) { if (!groups.containsKey(group)) { groups[group] = [key]; @@ -76,9 +76,9 @@ class _SelectedChipsState extends State { size: 20, color: Colors.blue.shade900, ), - )); + ),); } else { - for (var element in value) { + for (final element in value) { nodes[element] = true; } } @@ -105,7 +105,7 @@ class _SelectedChipsState extends State { size: 20, color: Colors.green.shade900, ), - )); + ),); } }); return chips; diff --git a/APP/lib/widgets/select_objects/settings_view/settings_view.dart b/APP/lib/widgets/select_objects/settings_view/settings_view.dart index e40815179..3ee7a23c5 100644 --- a/APP/lib/widgets/select_objects/settings_view/settings_view.dart +++ b/APP/lib/widgets/select_objects/settings_view/settings_view.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; - -import '../treeapp_controller.dart'; -import '../tree_view/tree_node.dart'; -import 'tree_filter.dart'; +import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; +import 'package:ogree_app/widgets/select_objects/tree_view/tree_node.dart'; +import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; part '_actions.dart'; +part '_advanced_find_field.dart'; part '_find_node_field.dart'; part '_header.dart'; part '_selected_chips.dart'; -part '_advanced_find_field.dart'; const Duration kAnimationDuration = Duration(milliseconds: 300); @@ -24,7 +23,7 @@ class SettingsView extends StatelessWidget { final bool isTenantMode; final Namespace namespace; const SettingsView( - {super.key, required this.isTenantMode, required this.namespace}); + {super.key, required this.isTenantMode, required this.namespace,}); @override Widget build(BuildContext context) { @@ -65,7 +64,7 @@ class SettingsView extends StatelessWidget { namespace: namespace, ), const SizedBox(height: 8), - namespace != Namespace.Physical ? Container() : const TreeFilter(), + if (namespace != Namespace.Physical) Container() else const TreeFilter(), ], ), ); diff --git a/APP/lib/widgets/select_objects/settings_view/tree_filter.dart b/APP/lib/widgets/select_objects/settings_view/tree_filter.dart index 586127da7..2eeed3a09 100644 --- a/APP/lib/widgets/select_objects/settings_view/tree_filter.dart +++ b/APP/lib/widgets/select_objects/settings_view/tree_filter.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/theme.dart'; +import 'package:ogree_app/widgets/select_objects/settings_view/settings_view.dart'; import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'settings_view.dart'; const lastLevel = 3; @@ -18,13 +17,13 @@ class TreeFilter extends StatefulWidget { const TreeFilter({super.key}); @override - State createState() => _TreeFilterState(); + State createState() => TreeFilterState(); - static _TreeFilterState? of(BuildContext context) => - context.findAncestorStateOfType<_TreeFilterState>(); + static TreeFilterState? of(BuildContext context) => + context.findAncestorStateOfType(); } -class _TreeFilterState extends State { +class TreeFilterState extends State { Map> _filterLevels = {0: [], 1: [], 2: [], 3: []}; Map> get filterLevels => _filterLevels; @@ -37,7 +36,7 @@ class _TreeFilterState extends State { // Get which fields to filter and their list of suggestions int idx = 0; if (TreeAppController.of(context).fetchedCategories["KeysOrder"] != null) { - for (String key + for (final String key in TreeAppController.of(context).fetchedCategories["KeysOrder"]!) { objectsPerCategory[key.capitalize()] = TreeAppController.of(context).fetchedCategories[key] ?? @@ -48,47 +47,48 @@ class _TreeFilterState extends State { } return Column( - children: objectsPerCategory.keys.map((key) { - // Input enabled only if child of selected filter or if last level - var enabled = enumParams[key]! > getMaxFilterLevel() || - enumParams[key]! == lastLevel; - List options = objectsPerCategory[key]!; + children: objectsPerCategory.keys.map((key) { + // Input enabled only if child of selected filter or if last level + final enabled = enumParams[key]! > getMaxFilterLevel() || + enumParams[key]! == lastLevel; + List options = objectsPerCategory[key]!; - // Update suggestions according to last selected level - if (enabled && !isFilterEmpty(topLevel: lastLevel - 1)) { - var lastLevelFilters = - _filterLevels[getMaxFilterLevel(topLevel: lastLevel - 1)]!; - options = options.where((obj) { - for (var filter in lastLevelFilters) { - if (obj.contains(filter)) return true; - } - return false; - }).toList(); - } + // Update suggestions according to last selected level + if (enabled && !isFilterEmpty(topLevel: lastLevel - 1)) { + final lastLevelFilters = + _filterLevels[getMaxFilterLevel(topLevel: lastLevel - 1)]!; + options = options.where((obj) { + for (final filter in lastLevelFilters) { + if (obj.contains(filter)) return true; + } + return false; + }).toList(); + } - // Special filter for last level with multiple selection - if (enumParams[key]! == lastLevel && - _filterLevels[lastLevel]!.isNotEmpty) { - options = options - .where((obj) => !_filterLevels[lastLevel]!.contains(obj)) - .toList(); - } + // Special filter for last level with multiple selection + if (enumParams[key]! == lastLevel && + _filterLevels[lastLevel]!.isNotEmpty) { + options = options + .where((obj) => !_filterLevels[lastLevel]!.contains(obj)) + .toList(); + } - return AutocompleteFilter( - enabled: enabled, - param: key, - paramLevel: enumParams[key]!, - options: options, - notifyParent: NotifyChildSelection, - showClearFilter: enumParams[key] == 0 ? !isFilterEmpty() : false, - ); - }).toList()); + return AutocompleteFilter( + enabled: enabled, + param: key, + paramLevel: enumParams[key]!, + options: options, + notifyParent: notifyChildSelection, + showClearFilter: enumParams[key] == 0 ? !isFilterEmpty() : false, + ); + }).toList(), + ); } // Callback for child to update parent state - void NotifyChildSelection({bool isClearAll = false}) { + void notifyChildSelection({bool isClearAll = false}) { if (isClearAll) { - for (var level in _filterLevels.keys) { + for (final level in _filterLevels.keys) { _filterLevels[level] = []; } TreeAppController.of(context).filterTree("", -1); @@ -127,14 +127,15 @@ class AutocompleteFilter extends StatefulWidget { final Function({bool isClearAll}) notifyParent; final bool showClearFilter; - const AutocompleteFilter( - {super.key, - required this.enabled, - required this.param, - required this.paramLevel, - required this.options, - required this.notifyParent, - required this.showClearFilter}); + const AutocompleteFilter({ + super.key, + required this.enabled, + required this.param, + required this.paramLevel, + required this.options, + required this.notifyParent, + required this.showClearFilter, + }); @override State createState() => _AutocompleteFilterState(); @@ -153,50 +154,55 @@ class _AutocompleteFilterState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - widget.paramLevel == 0 - ? Wrap( - children: [ - SettingsHeader(text: localeMsg.categoryFilters), - widget.showClearFilter - ? OutlinedButton( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.orange.shade700, - backgroundColor: Colors.orange.shade100, - padding: const EdgeInsets.all(8), - side: const BorderSide(style: BorderStyle.none), - shape: const RoundedRectangleBorder( - borderRadius: - BorderRadius.all(Radius.circular(12)), - ), - textStyle: const TextStyle( - color: kDarkBlue, - fontWeight: FontWeight.w600, - ), - ), - onPressed: () => - widget.notifyParent(isClearAll: true), - child: Text(localeMsg.clearAllFilters), - ) - : Container(), - ], - ) - : Container(), + if (widget.paramLevel == 0) + Wrap( + children: [ + SettingsHeader(text: localeMsg.categoryFilters), + if (widget.showClearFilter) + OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.orange.shade700, + backgroundColor: Colors.orange.shade100, + padding: const EdgeInsets.all(8), + side: const BorderSide(style: BorderStyle.none), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + textStyle: const TextStyle( + color: kDarkBlue, + fontWeight: FontWeight.w600, + ), + ), + onPressed: () => widget.notifyParent(isClearAll: true), + child: Text(localeMsg.clearAllFilters), + ) + else + Container(), + ], + ) + else + Container(), RawAutocomplete( optionsBuilder: (TextEditingValue textEditingValue) { return widget.options.where((String option) { return option.contains(textEditingValue.text); }); }, - fieldViewBuilder: (BuildContext context, - TextEditingController textEditingController, - FocusNode focusNode, - VoidCallback onFieldSubmitted) { + fieldViewBuilder: ( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { return TextFormField( controller: textEditingController, focusNode: focusNode, style: const TextStyle(fontSize: 14), - decoration: GetFormInputDecoration(true, widget.param, - isEnabled: widget.enabled), + decoration: GetFormInputDecoration( + true, + widget.param, + isEnabled: widget.enabled, + ), onFieldSubmitted: (String value) { if (widget.options.contains(value)) { setState(() { @@ -216,9 +222,11 @@ class _AutocompleteFilterState extends State { }, ); }, - optionsViewBuilder: (BuildContext context, - AutocompleteOnSelected onSelected, - Iterable options) { + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { return Align( alignment: Alignment.topLeft, child: Material( @@ -235,8 +243,10 @@ class _AutocompleteFilterState extends State { onSelected(option); }, child: ListTile( - title: Text(option, - style: const TextStyle(fontSize: 14)), + title: Text( + option, + style: const TextStyle(fontSize: 14), + ), ), ); }, @@ -258,33 +268,35 @@ class _AutocompleteFilterState extends State { // One chip per selected filter List getChips(List nodes, BuildContext context) { - List chips = []; - for (var value in nodes) { - chips.add(RawChip( - onPressed: () { - TreeAppController.of(context).filterTree(value, widget.paramLevel); - setState(() { - // _selectedOptions.removeWhere((opt) => opt == value); - widget.notifyParent(); - }); - }, - backgroundColor: ColorChip[widget.param]!.shade100, - side: const BorderSide(style: BorderStyle.none), - label: Text( - value, - style: TextStyle( - fontSize: 13.5, - fontFamily: GoogleFonts.inter().fontFamily, + final List chips = []; + for (final value in nodes) { + chips.add( + RawChip( + onPressed: () { + TreeAppController.of(context).filterTree(value, widget.paramLevel); + setState(() { + // _selectedOptions.removeWhere((opt) => opt == value); + widget.notifyParent(); + }); + }, + backgroundColor: ColorChip[widget.param]!.shade100, + side: const BorderSide(style: BorderStyle.none), + label: Text( + value, + style: TextStyle( + fontSize: 13.5, + fontFamily: GoogleFonts.inter().fontFamily, + color: ColorChip[widget.param], + fontWeight: FontWeight.w600, + ), + ), + avatar: Icon( + Icons.cancel, + size: 20, color: ColorChip[widget.param], - fontWeight: FontWeight.w600, ), ), - avatar: Icon( - Icons.cancel, - size: 20, - color: ColorChip[widget.param], - ), - )); + ); } return chips; } diff --git a/APP/lib/widgets/select_objects/tree_view/_node_chip.dart b/APP/lib/widgets/select_objects/tree_view/_node_chip.dart index 384c0b458..f4ba1269b 100644 --- a/APP/lib/widgets/select_objects/tree_view/_node_chip.dart +++ b/APP/lib/widgets/select_objects/tree_view/_node_chip.dart @@ -5,11 +5,9 @@ class _NodeActionsChip extends StatefulWidget { final bool isVirtual; final bool isTemplate; const _NodeActionsChip( - {Key? key, - required this.node, + {required this.node, this.isTemplate = false, - this.isVirtual = false}) - : super(key: key); + this.isVirtual = false,}); @override State<_NodeActionsChip> createState() => _NodeActionsChipState(); @@ -22,8 +20,8 @@ class _NodeActionsChipState extends State<_NodeActionsChip> { @override Widget build(BuildContext context) { - var namespace = TreeAppController.of(context).namespace; - var menuEntries = >[ + final namespace = TreeAppController.of(context).namespace; + final menuEntries = >[ PopupMenuItem( value: 1, child: ListTile( @@ -32,7 +30,7 @@ class _NodeActionsChipState extends State<_NodeActionsChip> { contentPadding: const EdgeInsets.symmetric(horizontal: 4), leading: const Icon(Icons.account_tree_rounded, color: _kDarkBlue), ), - ) + ), ]; if (namespace != Namespace.Logical || widget.node.id[0] != starSymbol) { menuEntries.add( @@ -93,7 +91,7 @@ class _NodeActionsChipState extends State<_NodeActionsChip> { {}, argNamespace: Namespace.Organisational, reload: true, - isTenantMode: true), + isTenantMode: true,), domainId: widget.node.id, ) : ObjectPopup( @@ -101,24 +99,23 @@ class _NodeActionsChipState extends State<_NodeActionsChip> { parentCallback: () => TreeAppController.of(context).init( {}, argNamespace: namespace, - reload: true, - isTenantMode: false), + reload: true,), objId: widget.node.id, ), - isDismissible: true); + isDismissible: true,); } else if (selected == 3) { showCustomPopup(context, ViewObjectPopup(namespace: namespace, objId: widget.node.id), - isDismissible: true); + isDismissible: true,); } else { showCustomPopup(context, ObjectGraphView(widget.node.id), - isDismissible: true); + isDismissible: true,); } }, child: RawChip( onPressed: () => _menu?.showButtonMenu(), backgroundColor: - widget.isVirtual ? Colors.deepPurple.shade100 : Color(0x331565c0), + widget.isVirtual ? Colors.deepPurple.shade100 : const Color(0x331565c0), side: const BorderSide(style: BorderStyle.none), label: Text( adaptLabel(widget.node.label), diff --git a/APP/lib/widgets/select_objects/tree_view/_node_selector.dart b/APP/lib/widgets/select_objects/tree_view/_node_selector.dart index ca8495b49..5546a10d3 100644 --- a/APP/lib/widgets/select_objects/tree_view/_node_selector.dart +++ b/APP/lib/widgets/select_objects/tree_view/_node_selector.dart @@ -2,7 +2,7 @@ part of 'tree_node_tile.dart'; class _NodeSelector extends StatelessWidget { final String id; - const _NodeSelector({Key? key, required this.id}) : super(key: key); + const _NodeSelector({required this.id}); @override Widget build(BuildContext context) { diff --git a/APP/lib/widgets/select_objects/tree_view/custom_tree_view.dart b/APP/lib/widgets/select_objects/tree_view/custom_tree_view.dart index b68c6a3dd..51c043b5f 100644 --- a/APP/lib/widgets/select_objects/tree_view/custom_tree_view.dart +++ b/APP/lib/widgets/select_objects/tree_view/custom_tree_view.dart @@ -1,20 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'; - -import '../treeapp_controller.dart'; -import 'tree_node.dart'; -import 'tree_node_tile.dart'; +import 'package:ogree_app/widgets/select_objects/tree_view/tree_node.dart'; +import 'package:ogree_app/widgets/select_objects/tree_view/tree_node_tile.dart'; +import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; class CustomTreeView extends StatefulWidget { final bool isTenantMode; - const CustomTreeView({Key? key, required this.isTenantMode}) - : super(key: key); + const CustomTreeView({super.key, required this.isTenantMode}); @override - _CustomTreeViewState createState() => _CustomTreeViewState(); + CustomTreeViewState createState() => CustomTreeViewState(); } -class _CustomTreeViewState extends State { +class CustomTreeViewState extends State { @override Widget build(BuildContext context) { final appController = TreeAppController.of(context); diff --git a/APP/lib/widgets/select_objects/tree_view/tree_node_tile.dart b/APP/lib/widgets/select_objects/tree_view/tree_node_tile.dart index b14ff854d..493845221 100644 --- a/APP/lib/widgets/select_objects/tree_view/tree_node_tile.dart +++ b/APP/lib/widgets/select_objects/tree_view/tree_node_tile.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:ogree_app/widgets/object_graph_view.dart'; import 'package:ogree_app/pages/tenant_page.dart'; -import 'package:ogree_app/widgets/select_objects/view_object_popup.dart'; +import 'package:ogree_app/widgets/object_graph_view.dart'; import 'package:ogree_app/widgets/select_objects/object_popup.dart'; import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; +import 'package:ogree_app/widgets/select_objects/tree_view/tree_node.dart'; +import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; +import 'package:ogree_app/widgets/select_objects/view_object_popup.dart'; import 'package:ogree_app/widgets/tenants/popups/domain_popup.dart'; -import '../treeapp_controller.dart'; -import 'tree_node.dart'; - part '_node_chip.dart'; part '_node_selector.dart'; @@ -28,15 +27,15 @@ class TreeNodeTile extends StatefulWidget { final bool isTenantMode; final TreeEntry entry; final Function() onTap; - const TreeNodeTile( - {Key? key, - required this.isTenantMode, - required this.entry, - required this.onTap}) - : super(key: key); + const TreeNodeTile({ + super.key, + required this.isTenantMode, + required this.entry, + required this.onTap, + }); @override - _TreeNodeTileState createState() => _TreeNodeTileState(); + State createState() => _TreeNodeTileState(); } class _TreeNodeTileState extends State { @@ -68,16 +67,16 @@ class _TreeNodeTileState extends State { children: [ FolderButton( closedIcon: isVirtual - ? Icon(Icons.cloud) - : Icon(Icons.auto_awesome_mosaic), + ? const Icon(Icons.cloud) + : const Icon(Icons.auto_awesome_mosaic), openedIcon: isVirtual - ? Icon(Icons.cloud_outlined) - : Icon(Icons.auto_awesome_mosaic_outlined), + ? const Icon(Icons.cloud_outlined) + : const Icon(Icons.auto_awesome_mosaic_outlined), icon: widget.isTenantMode ? const Icon(Icons.dns) : isVirtual - ? Icon(Icons.cloud) - : Icon(Icons.auto_awesome_mosaic), + ? const Icon(Icons.cloud) + : const Icon(Icons.auto_awesome_mosaic), isOpen: widget.entry.hasChildren ? widget.entry.isExpanded : null, onPressed: widget.entry.hasChildren ? widget.onTap : null, @@ -87,106 +86,111 @@ class _TreeNodeTileState extends State { isTemplate: isTemplate, isVirtual: isVirtual, ), - widget.isTenantMode - ? Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: CircleAvatar( - radius: 13, - child: IconButton( - splashRadius: 18, - iconSize: 14, - padding: const EdgeInsets.all(2), - onPressed: () => showCustomPopup( - context, - DomainPopup( - parentCallback: () => appController.init( - {}, - argNamespace: - Namespace.Organisational, - reload: true, - isTenantMode: true), - domainId: widget.entry.node.id, - )), - icon: const Icon( - Icons.edit, - color: Colors.black, - )), + if (widget.isTenantMode) + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CircleAvatar( + radius: 13, + child: IconButton( + splashRadius: 18, + iconSize: 14, + padding: const EdgeInsets.all(2), + onPressed: () => showCustomPopup( + context, + DomainPopup( + parentCallback: () => appController.init( + {}, + argNamespace: Namespace.Organisational, + reload: true, + isTenantMode: true, + ), + domainId: widget.entry.node.id, + ), + ), + icon: const Icon( + Icons.edit, + color: Colors.black, ), ), - CircleAvatar( - radius: 13, - child: IconButton( - splashRadius: 18, - iconSize: 14, - padding: const EdgeInsets.all(2), - onPressed: () => TenantPage.of(context)! - .changeToUserView(widget.entry.node.id), - icon: const Icon( - Icons.people, - color: Colors.black, - )), + ), + ), + CircleAvatar( + radius: 13, + child: IconButton( + splashRadius: 18, + iconSize: 14, + padding: const EdgeInsets.all(2), + onPressed: () => TenantPage.of(context)! + .changeToUserView(widget.entry.node.id), + icon: const Icon( + Icons.people, + color: Colors.black, ), - ], - ) - : Row( - children: [ - widget.entry.node.id[0] == starSymbol - ? Container() - : Padding( - padding: - const EdgeInsets.symmetric(horizontal: 4.0), - child: _NodeSelector(id: widget.entry.node.id), - ), - TreeAppController.of(context).namespace != - Namespace.Logical - ? CircleAvatar( - radius: 10, - child: IconButton( - splashRadius: 18, - iconSize: 14, - padding: const EdgeInsets.all(2), - onPressed: () => showCustomPopup( - context, - TreeAppController.of(context) - .namespace == - Namespace.Organisational - ? DomainPopup( - parentCallback: () => - TreeAppController.of( - context) - .init( - {}, - argNamespace: Namespace - .Organisational, - reload: true, - isTenantMode: true), - parentId: widget.entry.node.id, - ) - : ObjectPopup( - namespace: TreeAppController.of( - context) - .namespace, - parentCallback: () => - appController.init({}, - argNamespace: - TreeAppController - .of(context) - .namespace, - reload: true, - isTenantMode: true), - parentId: widget.entry.node.id, - ), - isDismissible: true), - icon: const Icon( - Icons.add, - color: Colors.black, - )), - ) - : Container(), - ], + ), ), + ], + ) + else + Row( + children: [ + if (widget.entry.node.id[0] == starSymbol) + Container() + else + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: _NodeSelector(id: widget.entry.node.id), + ), + if (TreeAppController.of(context).namespace != + Namespace.Logical) + CircleAvatar( + radius: 10, + child: IconButton( + splashRadius: 18, + iconSize: 14, + padding: const EdgeInsets.all(2), + onPressed: () => showCustomPopup( + context, + TreeAppController.of(context).namespace == + Namespace.Organisational + ? DomainPopup( + parentCallback: () => TreeAppController.of( + context, + ).init( + {}, + argNamespace: Namespace.Organisational, + reload: true, + isTenantMode: true, + ), + parentId: widget.entry.node.id, + ) + : ObjectPopup( + namespace: TreeAppController.of( + context, + ).namespace, + parentCallback: () => appController.init( + {}, + argNamespace: + TreeAppController.of(context) + .namespace, + reload: true, + isTenantMode: true, + ), + parentId: widget.entry.node.id, + ), + isDismissible: true, + ), + icon: const Icon( + Icons.add, + color: Colors.black, + ), + ), + ) + else + Container(), + ], + ), ], ), ), diff --git a/APP/lib/widgets/select_objects/treeapp_controller.dart b/APP/lib/widgets/select_objects/treeapp_controller.dart index 9a4d6158e..f16af6763 100644 --- a/APP/lib/widgets/select_objects/treeapp_controller.dart +++ b/APP/lib/widgets/select_objects/treeapp_controller.dart @@ -3,9 +3,8 @@ import 'package:flutter_fancy_tree_view/flutter_fancy_tree_view.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/theme.dart'; -import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; -import 'tree_view/tree_node.dart'; +import 'package:ogree_app/widgets/select_objects/tree_view/tree_node.dart'; bool isSmallDisplay = false; @@ -30,11 +29,13 @@ class TreeAppController with ChangeNotifier { .controller; } - Future init(Map nodes, - {Namespace argNamespace = Namespace.Physical, - bool reload = false, - String dateRange = "", - bool isTenantMode = false}) async { + Future init( + Map nodes, { + Namespace argNamespace = Namespace.Physical, + bool reload = false, + String dateRange = "", + bool isTenantMode = false, + }) async { if (_isInitialized && !reload) return; final rootNode = TreeNode(id: kRootId); @@ -44,17 +45,17 @@ class TreeAppController with ChangeNotifier { fetchedCategories = kDataSampleCategories; } else { namespace = argNamespace; - var result = await fetchObjectsTree( - dateRange: dateRange, - namespace: argNamespace, - isTenantMode: isTenantMode); + final result = await fetchObjectsTree( + dateRange: dateRange, + namespace: argNamespace, + isTenantMode: isTenantMode, + ); switch (result) { case Success(value: final listValue): fetchedData = listValue[0]; fetchedCategories = listValue[1]; - case Failure(exception: final exception): - print(exception); + case Failure(): } } @@ -80,7 +81,7 @@ class TreeAppController with ChangeNotifier { } deepCopy(Map> source, destination) { - for (var item in source.keys) { + for (final item in source.keys) { destination[item] = List.from(source[item]!); } } @@ -92,13 +93,14 @@ class TreeAppController with ChangeNotifier { parent.addChildren( childrenIds.map( (String childId) => TreeNode( - id: childId, - label: parent.id == kRootId - ? childId - : childId.substring(childId.lastIndexOf(".") + 1)), + id: childId, + label: parent.id == kRootId + ? childId + : childId.substring(childId.lastIndexOf(".") + 1), + ), ), ); - for (var node in parent.children) { + for (final node in parent.children) { generateTree(node, data); } } @@ -107,8 +109,11 @@ class TreeAppController with ChangeNotifier { late Map selectedNodes; bool isSelected(String id) => selectedNodes[id] ?? false; - void toggleSelection(String id, - {bool? shouldSelect, bool shouldNotify = true}) { + void toggleSelection( + String id, { + bool? shouldSelect, + bool shouldNotify = true, + }) { shouldSelect ??= !isSelected(id); shouldSelect ? select(id) : deselect(id); @@ -118,18 +123,18 @@ class TreeAppController with ChangeNotifier { void selectAll([bool select = true]) { //treeController.expandAll(); if (select) { - for (var root in treeController.roots) { + for (final root in treeController.roots) { if (root.id[0] != starSymbol) { selectedNodes[root.id] = true; } - for (var descendant in root.descendants) { + for (final descendant in root.descendants) { selectedNodes[descendant.id] = true; } } } else { - for (var root in treeController.roots) { + for (final root in treeController.roots) { selectedNodes.remove(root.id); - for (var descendant in root.descendants) { + for (final descendant in root.descendants) { selectedNodes.remove(descendant.id); } } @@ -154,7 +159,7 @@ class TreeAppController with ChangeNotifier { if (node.id[0] != starSymbol) { toggleSelection(node.id); } - for (var descendant in node.descendants) { + for (final descendant in node.descendants) { toggleSelection(descendant.id); } notifyListeners(); @@ -163,17 +168,17 @@ class TreeAppController with ChangeNotifier { // Filter Tree Functionality void filterTree(String id, int level) { // Deep copy original data - Map> filteredData = {}; + final Map> filteredData = {}; deepCopy(fetchedData, filteredData); // Add or remove filter if (level < 0) { // Clear All - for (var level in _filterLevels.keys) { + for (final level in _filterLevels.keys) { _filterLevels[level] = []; } } else { - var currentLevel = _filterLevels[level]!; + final currentLevel = _filterLevels[level]!; if (!currentLevel.contains(id)) { currentLevel.add(id); } else { @@ -197,9 +202,9 @@ class TreeAppController with ChangeNotifier { } // Apply all filters from root and bellow while (testLevel > 0) { - List newList = []; + final List newList = []; for (var i = 0; i < filters.length; i++) { - var parent = + final parent = filters[i].substring(0, filters[i].lastIndexOf('.')); //parent if (filteredData[parent] != null) { filteredData[parent]!.removeWhere((element) { @@ -220,15 +225,14 @@ class TreeAppController with ChangeNotifier { } filterTreeById(List ids) { - Map> filteredData = {}; + final Map> filteredData = {}; if (ids.isEmpty) { - for (var item in fetchedData.keys) { + for (final item in fetchedData.keys) { filteredData[item] = List.from(fetchedData[item]!); } - print(filteredData); } else { filteredData[kRootId] = []; - for (var id in ids) { + for (final id in ids) { filteredData[kRootId]!.add(id); } } @@ -246,7 +250,7 @@ class TreeAppController with ChangeNotifier { void scrollTo(TreeNode node) { var offset = node.depth * nodeHeight; if (node.ancestors.isNotEmpty) { - var parent = node.ancestors.last; + final parent = node.ancestors.last; offset = offset + parent.children.toList().indexOf(node) * nodeHeight; } scrollController.animateTo( @@ -266,10 +270,10 @@ class TreeAppController with ChangeNotifier { class TreeAppControllerScope extends InheritedWidget { const TreeAppControllerScope({ - Key? key, + super.key, required this.controller, - required Widget child, - }) : super(key: key, child: child); + required super.child, + }); final TreeAppController controller; @@ -291,25 +295,25 @@ const Map> kDataSample = { 'sitePI.B1.1.rack1.devA', 'sitePI.B1.1.rack1.devB', 'sitePI.B1.1.rack1.devC', - 'sitePI.B1.1.rack1.devD' + 'sitePI.B1.1.rack1.devD', ], 'sitePI.B1.1.rack2': [ 'sitePI.B1.1.rack2.devA', 'sitePI.B1.1.rack2.devB', 'sitePI.B1.1.rack2.devC', - 'sitePI.B1.1.rack2.devD' + 'sitePI.B1.1.rack2.devD', ], 'sitePI.B1.1.rack2.devB': [ 'sitePI.B1.1.rack2.devB.1', - 'sitePI.B1.1.rack2.devB.devB-2' + 'sitePI.B1.1.rack2.devB.devB-2', ], 'sitePI.B1.1.rack2.devC': [ 'sitePI.B1.1.rack2.devC.1', - 'sitePI.B1.1.rack2.devC.devC-2' + 'sitePI.B1.1.rack2.devC.devC-2', ], 'sitePI.B2': ['sitePI.B2.1'], 'sitePI.B2.1': ['sitePI.B2.1.rack1'], - 'siteNO': ['siteNO.BA1', 'siteNO.BB1', 'siteNO.BI1', 'siteNO.BL'] + 'siteNO': ['siteNO.BA1', 'siteNO.BB1', 'siteNO.BI1', 'siteNO.BL'], }; const Map> kDataSampleCategories = { @@ -324,14 +328,14 @@ const Map> kDataSampleCategories = { 'siteNO.BA1', 'siteNO.BB1', 'siteNO.BI1', - 'siteNO.BL' + 'siteNO.BL', ], "room": [ 'sitePA.A2.1', 'sitePI.B1.1', 'sitePI.B1.2', 'sitePI.B1.3', - 'sitePI.B2.1' + 'sitePI.B2.1', ], - "rack": ['sitePI.B1.1.rack1', 'sitePI.B1.1.rack2', 'sitePI.B2.1.rack1'] + "rack": ['sitePI.B1.1.rack1', 'sitePI.B1.1.rack2', 'sitePI.B2.1.rack1'], }; diff --git a/APP/lib/widgets/select_objects/view_object_popup.dart b/APP/lib/widgets/select_objects/view_object_popup.dart index a8e75cda3..3a4a484a0 100644 --- a/APP/lib/widgets/select_objects/view_object_popup.dart +++ b/APP/lib/widgets/select_objects/view_object_popup.dart @@ -1,13 +1,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; - -import 'object_popup.dart'; +import 'package:ogree_app/widgets/select_objects/object_popup.dart'; class ViewObjectPopup extends StatefulWidget { String objId; @@ -19,7 +18,6 @@ class ViewObjectPopup extends StatefulWidget { } class _ViewObjectPopupState extends State { - bool _isSmallDisplay = false; String _objCategory = LogCategories.group.name; String? _loadFileResult; @@ -31,25 +29,24 @@ class _ViewObjectPopupState extends State { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - _isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return FutureBuilder( - future: _loadFileResult == null ? getObject() : null, - builder: (context, _) { - if (_loadFileResult == null) { - return const Center(child: CircularProgressIndicator()); - } + future: _loadFileResult == null ? getObject() : null, + builder: (context, _) { + if (_loadFileResult == null) { + return const Center(child: CircularProgressIndicator()); + } - return Center( - child: Container( - width: 500, - constraints: BoxConstraints(maxHeight: 430), - margin: const EdgeInsets.symmetric(horizontal: 20), - decoration: PopupDecoration, - child: Padding( - padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), - child: ScaffoldMessenger( - child: Builder( + return Center( + child: Container( + width: 500, + constraints: const BoxConstraints(maxHeight: 430), + margin: const EdgeInsets.symmetric(horizontal: 20), + decoration: PopupDecoration, + child: Padding( + padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), + child: ScaffoldMessenger( + child: Builder( builder: (context) => Scaffold( backgroundColor: Colors.white, body: SingleChildScrollView( @@ -71,16 +68,17 @@ class _ViewObjectPopupState extends State { height: 35, width: 147, child: DropdownButtonFormField( - isExpanded: true, - borderRadius: BorderRadius.circular(12.0), - decoration: GetFormInputDecoration( - false, - null, - icon: Icons.bookmark, - ), - value: _objCategory, - items: getCategoryMenuItems(), - onChanged: null), + isExpanded: true, + borderRadius: BorderRadius.circular(12.0), + decoration: GetFormInputDecoration( + false, + null, + icon: Icons.bookmark, + ), + value: _objCategory, + items: getCategoryMenuItems(), + onChanged: null, + ), ), ], ), @@ -91,22 +89,25 @@ class _ViewObjectPopupState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ ElevatedButton.icon( - onPressed: () { - Navigator.of(context).pop(); - }, - label: const Text("OK"), - icon: const Icon(Icons.thumb_up, size: 16)) + onPressed: () { + Navigator.of(context).pop(); + }, + label: const Text("OK"), + icon: const Icon(Icons.thumb_up, size: 16), + ), ], - ) + ), ], ), ), ), - )), + ), ), ), - ); - }); + ), + ); + }, + ); } List> getCategoryMenuItems() { @@ -117,19 +118,21 @@ class _ViewObjectPopupState extends State { _objCategory, overflow: TextOverflow.ellipsis, ), - ) + ), ]; } - getObject() async { + Future getObject() async { // Get object info for popup final messenger = ScaffoldMessenger.of(context); var errMsg = ""; // Try both id and slug since we dont know the obj's category - for (var keyId in ["id", "slug"]) { - var result = await fetchObject( - widget.objId, AppLocalizations.of(context)!, - idKey: keyId); + for (final keyId in ["id", "slug"]) { + final result = await fetchObject( + widget.objId, + AppLocalizations.of(context)!, + idKey: keyId, + ); switch (result) { case Success(value: final value): if (widget.namespace == Namespace.Logical) { @@ -147,7 +150,7 @@ class _ViewObjectPopupState extends State { // physical or organisational _objCategory = value["category"]; } - var encoder = const JsonEncoder.withIndent(" "); + const encoder = JsonEncoder.withIndent(" "); _loadFileResult = encoder.convert(value); return; case Failure(exception: final exception): @@ -155,23 +158,26 @@ class _ViewObjectPopupState extends State { } } showSnackBar(messenger, errMsg, isError: true); - if (context.mounted) Navigator.pop(context); + if (mounted) Navigator.pop(context); } - getViewForm(AppLocalizations localeMsg) { + Center getViewForm(AppLocalizations localeMsg) { return Center( - child: ListView(shrinkWrap: true, children: [ - Container( - color: Colors.black, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: SelectableText( - _loadFileResult!, - style: const TextStyle(color: Colors.white), + child: ListView( + shrinkWrap: true, + children: [ + Container( + color: Colors.black, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SelectableText( + _loadFileResult!, + style: const TextStyle(color: Colors.white), + ), ), ), - ) - ]), + ], + ), ); } } diff --git a/APP/lib/widgets/tenants/api_stats_view.dart b/APP/lib/widgets/tenants/api_stats_view.dart index ec693d325..f20bb5d18 100644 --- a/APP/lib/widgets/tenants/api_stats_view.dart +++ b/APP/lib/widgets/tenants/api_stats_view.dart @@ -8,8 +8,8 @@ import 'package:ogree_app/pages/results_page.dart'; // Define a stateful widget that displays API usage statistics for a given tenant class ApiStatsView extends StatefulWidget { const ApiStatsView({ - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _ApiStatsViewState(); @@ -42,7 +42,7 @@ class _ApiStatsViewState extends State { Padding( padding: const EdgeInsets.only(top: 16), child: Text( - "${AppLocalizations.of(context)!.noObjectsFound} :("), + "${AppLocalizations.of(context)!.noObjectsFound} :(",), ), ], ); @@ -54,10 +54,10 @@ class _ApiStatsViewState extends State { cardTheme: const CardTheme( elevation: 0, surfaceTintColor: Colors.white, - color: Colors.white), + color: Colors.white,), ), child: SingleChildScrollView( - padding: const EdgeInsets.only(right: 16, top: 0), + padding: const EdgeInsets.only(right: 16), child: PaginatedDataTable( horizontalMargin: 15, columnSpacing: 30, @@ -68,18 +68,18 @@ class _ApiStatsViewState extends State { label: Text( localeMsg.parameter, style: titleStyle, - )), + ),), DataColumn( label: Text( localeMsg.value, style: titleStyle, - )) + ),), ], source: _DataSource(context, _tenantStats!), ), - )); + ),); } - }); + },); } getTenantStats() async { @@ -98,8 +98,8 @@ class _ApiStatsViewState extends State { result = await fetchTenantApiVersion(); switch (result) { case Success(value: final value): - Map versionStats = value; - for (var key in versionStats.keys) { + final Map versionStats = value; + for (final key in versionStats.keys) { if (key.contains("Build")) { _tenantStats!["API$key"] = versionStats[key]; } else { @@ -152,9 +152,9 @@ class _DataSource extends DataTableSource { int get selectedRowCount => _selectedCount; List getChildren() { - List children = []; - for (var key in stats.keys) { - List row = [label(key), label(stats[key].toString())]; + final List children = []; + for (final key in stats.keys) { + final List row = [label(key), label(stats[key].toString())]; children.add(CustomRow(row)); } return children; @@ -167,6 +167,6 @@ class _DataSource extends DataTableSource { fontSize: 14, fontWeight: FontWeight.w400, ), - )); + ),); } } diff --git a/APP/lib/widgets/tenants/docker_view.dart b/APP/lib/widgets/tenants/docker_view.dart index 8f532393f..045d7c269 100644 --- a/APP/lib/widgets/tenants/docker_view.dart +++ b/APP/lib/widgets/tenants/docker_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; @@ -8,7 +9,6 @@ import 'package:ogree_app/pages/results_page.dart'; import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; import 'package:ogree_app/widgets/tenants/popups/backup_popup.dart'; import 'package:ogree_app/widgets/tenants/popups/container_logs_popup.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class DockerView extends StatelessWidget { final String tName; @@ -20,83 +20,95 @@ class DockerView extends StatelessWidget { Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; return FutureBuilder( - future: getData(context), - builder: (context, _) { - if (_dockerInfo == null) { - return const Center(child: CircularProgressIndicator()); - } else if (_dockerInfo!.isEmpty) { - return Text(localeMsg.noDockerInfo); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Theme( - data: Theme.of(context).copyWith( - cardTheme: const CardTheme( - elevation: 0, - surfaceTintColor: Colors.white, - color: Colors.white), - ), - child: SingleChildScrollView( - padding: const EdgeInsets.only(right: 16, top: 0), - child: PaginatedDataTable( - horizontalMargin: 15, - columnSpacing: 30, - showCheckboxColumn: false, - rowsPerPage: _dockerInfo!.length, - columns: getColumns(localeMsg), - source: _DataSource(context, _dockerInfo!), - ), - )), - Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only(bottom: 20, right: 20), - child: ElevatedButton.icon( - onPressed: () => showCustomPopup( - context, BackupPopup(tenantName: tName), - isDismissible: true), - icon: const Icon(Icons.history), - label: Text(localeMsg.backup.capitalize()), + future: getData(context), + builder: (context, _) { + if (_dockerInfo == null) { + return const Center(child: CircularProgressIndicator()); + } else if (_dockerInfo!.isEmpty) { + return Text(localeMsg.noDockerInfo); + } else { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Theme( + data: Theme.of(context).copyWith( + cardTheme: const CardTheme( + elevation: 0, + surfaceTintColor: Colors.white, + color: Colors.white, + ), + ), + child: SingleChildScrollView( + padding: const EdgeInsets.only(right: 16), + child: PaginatedDataTable( + horizontalMargin: 15, + columnSpacing: 30, + showCheckboxColumn: false, + rowsPerPage: _dockerInfo!.length, + columns: getColumns(localeMsg), + source: _DataSource(context, _dockerInfo!), + ), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(bottom: 20, right: 20), + child: ElevatedButton.icon( + onPressed: () => showCustomPopup( + context, + BackupPopup(tenantName: tName), + isDismissible: true, ), + icon: const Icon(Icons.history), + label: Text(localeMsg.backup.capitalize()), ), ), - ], - ); - } - }); + ), + ], + ); + } + }, + ); } getData(BuildContext context) async { + final messenger = ScaffoldMessenger.of(context); final result = await fetchTenantDockerInfo(tName); switch (result) { case Success(value: final value): _dockerInfo = value; case Failure(exception: final exception): - showSnackBar(ScaffoldMessenger.of(context), exception.toString(), - isError: true); + showSnackBar( + messenger, + exception.toString(), + isError: true, + ); _dockerInfo = []; } } List getColumns(AppLocalizations localeMsg) { - TextStyle titleStyle = const TextStyle(fontWeight: FontWeight.w600); - List columns = []; - for (var col in [ + const TextStyle titleStyle = TextStyle(fontWeight: FontWeight.w600); + final List columns = []; + for (final col in [ "Logs", localeMsg.name, localeMsg.lastStarted, "Status", "Image", localeMsg.size, - "Port(s)" + "Port(s)", ]) { - columns.add(DataColumn( + columns.add( + DataColumn( label: Text( - col, - style: titleStyle, - ))); + col, + style: titleStyle, + ), + ), + ); } return columns; } @@ -142,31 +154,38 @@ class _DataSource extends DataTableSource { int get selectedRowCount => _selectedCount; List getChildren() { - List children = []; - for (var container in dockerList) { - List row = []; - row.add(DataCell(Align( - alignment: Alignment.centerLeft, - child: CircleAvatar( - radius: 13, - child: IconButton( - splashRadius: 18, - iconSize: 14, - padding: const EdgeInsets.all(2), - onPressed: () => showCustomPopup( - context, ContainerLogsPopup(containerName: container.name)), - icon: const Icon( - Icons.search, - )), + final List children = []; + for (final container in dockerList) { + final List row = []; + row.add( + DataCell( + Align( + alignment: Alignment.centerLeft, + child: CircleAvatar( + radius: 13, + child: IconButton( + splashRadius: 18, + iconSize: 14, + padding: const EdgeInsets.all(2), + onPressed: () => showCustomPopup( + context, + ContainerLogsPopup(containerName: container.name), + ), + icon: const Icon( + Icons.search, + ), + ), + ), + ), ), - ))); + ); row.addAll([ label(container.name), label(container.lastStarted), label(container.status), label(container.image), label(container.size), - label(container.ports) + label(container.ports), ]); children.add(CustomRow(row)); } @@ -177,24 +196,28 @@ class _DataSource extends DataTableSource { return DataCell(getDockerText(label)); } - getDockerText(String value) { + Widget getDockerText(String value) { if (value.contains("run")) { - return Row(children: [ - const Icon(Icons.directions_run, color: Colors.green), - Text( - value.capitalize(), - style: const TextStyle(color: Colors.green), - ) - ]); + return Row( + children: [ + const Icon(Icons.directions_run, color: Colors.green), + Text( + value.capitalize(), + style: const TextStyle(color: Colors.green), + ), + ], + ); } else if (value.contains("exit")) { - return Row(children: [ - const Icon(Icons.error_outline, color: Colors.red), - const SizedBox(width: 2), - Text( - value.capitalize(), - style: const TextStyle(color: Colors.red), - ) - ]); + return Row( + children: [ + const Icon(Icons.error_outline, color: Colors.red), + const SizedBox(width: 2), + Text( + value.capitalize(), + style: const TextStyle(color: Colors.red), + ), + ], + ); } else { return Text( value, diff --git a/APP/lib/widgets/tenants/domain_view.dart b/APP/lib/widgets/tenants/domain_view.dart index b6149e2d3..40049f33a 100644 --- a/APP/lib/widgets/tenants/domain_view.dart +++ b/APP/lib/widgets/tenants/domain_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; import 'package:ogree_app/common/theme.dart'; @@ -6,7 +7,6 @@ import 'package:ogree_app/widgets/select_objects/settings_view/settings_view.dar import 'package:ogree_app/widgets/select_objects/tree_view/custom_tree_view.dart'; import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; import 'package:ogree_app/widgets/tenants/popups/domain_popup.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class DomainView extends StatefulWidget { const DomainView({super.key}); @@ -30,7 +30,7 @@ class _DomainViewState extends State { future: appController.init({}, argNamespace: Namespace.Organisational, reload: _reloadDomains, - isTenantMode: true), + isTenantMode: true,), builder: (_, __) { if (_reloadDomains) { _reloadDomains = false; @@ -53,9 +53,7 @@ class _DomainViewState extends State { } return Stack(children: [ const CustomTreeView(isTenantMode: true), - isSmallDisplay - ? Container() - : const Align( + if (isSmallDisplay) Container() else const Align( alignment: Alignment.topRight, child: Padding( padding: EdgeInsets.only(right: 16), @@ -66,10 +64,10 @@ class _DomainViewState extends State { child: SettingsView( isTenantMode: true, namespace: Namespace.Organisational, - ))), + ),),), ), ), - ]); + ],); } return const Center(child: CircularProgressIndicator()); }, @@ -85,12 +83,12 @@ class _DomainViewState extends State { setState(() { _reloadDomains = true; }); - })), + },),), icon: const Icon(Icons.add), label: Text("${localeMsg.create} ${localeMsg.domain}"), ), ), ), - ]); + ],); } } diff --git a/APP/lib/widgets/tenants/locked_view.dart b/APP/lib/widgets/tenants/locked_view.dart index 44851f843..28c22f238 100644 --- a/APP/lib/widgets/tenants/locked_view.dart +++ b/APP/lib/widgets/tenants/locked_view.dart @@ -8,13 +8,13 @@ import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/tenant.dart'; class LockedView extends StatefulWidget { - Tenant tenant; - Function parentCallback; - LockedView({ - Key? key, + final Tenant tenant; + final Function parentCallback; + const LockedView({ + super.key, required this.tenant, required this.parentCallback, - }) : super(key: key); + }); @override State createState() => _LockedViewState(); } @@ -54,8 +54,12 @@ class _LockedViewState extends State { } return null; }, - decoration: GetFormInputDecoration(isSmallDisplay, 'E-mail', - icon: Icons.alternate_email, hint: 'abc@example.com'), + decoration: GetFormInputDecoration( + isSmallDisplay, + 'E-mail', + icon: Icons.alternate_email, + hint: 'abc@example.com', + ), ), ), const SizedBox(height: 20), @@ -72,8 +76,11 @@ class _LockedViewState extends State { return null; }, decoration: GetFormInputDecoration( - isSmallDisplay, localeMsg.password, - icon: Icons.lock_outline_rounded, hint: '********'), + isSmallDisplay, + localeMsg.password, + icon: Icons.lock_outline_rounded, + hint: '********', + ), ), ), const SizedBox(height: 20), @@ -106,13 +113,16 @@ class _LockedViewState extends State { formKey.currentState!.save(); final messenger = ScaffoldMessenger.of(context); final localeMsg = AppLocalizations.of(context)!; - final result = await loginAPITenant(_email!, _password!, - "${widget.tenant.apiUrl}:${widget.tenant.apiPort}"); + final result = await loginAPITenant( + _email!, + _password!, + "${widget.tenant.apiUrl}:${widget.tenant.apiPort}", + ); switch (result) { case Success(): widget.parentCallback(); case Failure(exception: final exception): - String errorMsg = exception.toString() == "Exception" + final String errorMsg = exception.toString() == "Exception" ? localeMsg.invalidLogin : exception.toString(); showSnackBar(messenger, errorMsg, isError: true); diff --git a/APP/lib/widgets/tenants/popups/backup_popup.dart b/APP/lib/widgets/tenants/popups/backup_popup.dart index 561efb0d6..883b58402 100644 --- a/APP/lib/widgets/tenants/popups/backup_popup.dart +++ b/APP/lib/widgets/tenants/popups/backup_popup.dart @@ -1,22 +1,23 @@ +import 'dart:convert'; +import 'dart:io'; + import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:ogree_app/widgets/common/form_field.dart'; -import 'package:universal_html/html.dart' as html; -import 'dart:convert'; -import 'dart:io'; import 'package:path_provider/path_provider.dart'; +import 'package:universal_html/html.dart' as html; class BackupPopup extends StatefulWidget { - String tenantName; - BackupPopup({super.key, required this.tenantName}); + final String tenantName; + const BackupPopup({super.key, required this.tenantName}); @override State createState() => _BackupPopupState(); @@ -51,71 +52,78 @@ class _BackupPopupState extends State decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 20 : 40, 8, _isSmallDisplay ? 20 : 40, 15), + _isSmallDisplay ? 20 : 40, + 8, + _isSmallDisplay ? 20 : 40, + 15, + ), child: Form( key: _formKey, child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + tabAlignment: TabAlignment.center, + controller: _tabController, + labelStyle: TextStyle( + fontSize: 15, + fontFamily: GoogleFonts.inter().fontFamily, + ), + unselectedLabelStyle: TextStyle( + fontSize: 15, + fontFamily: GoogleFonts.inter().fontFamily, + ), + isScrollable: true, + indicatorSize: TabBarIndicatorSize.label, + tabs: [ + Tab( + text: "${localeMsg.toBackup} DB", + ), + Tab( + text: "${localeMsg.restore} DB", + ), + ], + ), + SizedBox( + height: 189, + child: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, children: [ - TabBar( - tabAlignment: TabAlignment.center, - controller: _tabController, - labelStyle: TextStyle( - fontSize: 15, - fontFamily: GoogleFonts.inter().fontFamily), - unselectedLabelStyle: TextStyle( - fontSize: 15, - fontFamily: GoogleFonts.inter().fontFamily), - isScrollable: true, - indicatorSize: TabBarIndicatorSize.label, - tabs: [ - Tab( - text: "${localeMsg.toBackup} DB", - ), - Tab( - text: "${localeMsg.restore} DB", - ), - ], - ), - SizedBox( - height: 189, - child: Padding( - padding: const EdgeInsets.only(top: 16.0), - child: TabBarView( - physics: - const NeverScrollableScrollPhysics(), - controller: _tabController, - children: [ - getBackupView(localeMsg), - getRestoreView(localeMsg), - ], - ), - ), - ), - // const SizedBox(height: 20), + getBackupView(localeMsg), + getRestoreView(localeMsg), ], ), - ))), + ), + ), + // const SizedBox(height: 20), + ], + ), + ), + ), + ), ), ), ), ); } - getBackupView(AppLocalizations localeMsg) { + ListView getBackupView(AppLocalizations localeMsg) { return ListView( padding: EdgeInsets.zero, children: [ const SizedBox(height: 10), CustomFormField( - save: (newValue) => _tenantPassword = newValue, - label: localeMsg.tenantPassword, - icon: Icons.lock), + save: (newValue) => _tenantPassword = newValue, + label: localeMsg.tenantPassword, + icon: Icons.lock, + ), const SizedBox(height: 10), Row( children: [ @@ -151,8 +159,11 @@ class _BackupPopupState extends State height: 1.5, ), padding: const EdgeInsets.all(16), - child: const Icon(Icons.info_outline_rounded, - color: Colors.blueAccent, size: 18), + child: const Icon( + Icons.info_outline_rounded, + color: Colors.blueAccent, + size: 18, + ), ), ], ), @@ -162,7 +173,8 @@ class _BackupPopupState extends State children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900, + ), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -172,33 +184,35 @@ class _BackupPopupState extends State ), const SizedBox(width: 15), ElevatedButton.icon( - onPressed: requestBackup, - label: Text(localeMsg.toBackup), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.history, size: 16)) + onPressed: requestBackup, + label: Text(localeMsg.toBackup), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.history, size: 16), + ), ], - ) + ), ], ); } - getRestoreView(AppLocalizations localeMsg) { + ListView getRestoreView(AppLocalizations localeMsg) { return ListView( padding: EdgeInsets.zero, children: [ CustomFormField( - save: (newValue) => _tenantPassword = newValue, - label: localeMsg.tenantPassword, - icon: Icons.lock), + save: (newValue) => _tenantPassword = newValue, + label: localeMsg.tenantPassword, + icon: Icons.lock, + ), const SizedBox(height: 2), Row( children: [ @@ -234,52 +248,59 @@ class _BackupPopupState extends State height: 1.5, ), padding: const EdgeInsets.all(16), - child: const Icon(Icons.info_outline_rounded, - color: Colors.blueAccent, size: 18), + child: const Icon( + Icons.info_outline_rounded, + color: Colors.blueAccent, + size: 18, + ), ), ], ), const SizedBox(height: 10), - _loadFileResult == null - ? Align( - alignment: Alignment.bottomRight, - child: ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(withData: true); - if (result != null) { - setState(() { - _loadedFile = result.files.single; - }); - } - }, - icon: Icon(_loadedFile != null - ? Icons.check_circle - : Icons.download), - label: _loadedFile != null - ? Text(_loadedFile!.name) - : Text("${localeMsg.select} ${localeMsg.backup}")), - ) - : Container(), - _loadFileResult != null - ? Container( - color: Colors.black, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Result:\n$_loadFileResult', - style: const TextStyle(color: Colors.white), - ), - ), - ) - : Container(), + if (_loadFileResult == null) + Align( + alignment: Alignment.bottomRight, + child: ElevatedButton.icon( + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles(withData: true); + if (result != null) { + setState(() { + _loadedFile = result.files.single; + }); + } + }, + icon: Icon( + _loadedFile != null ? Icons.check_circle : Icons.download, + ), + label: _loadedFile != null + ? Text(_loadedFile!.name) + : Text("${localeMsg.select} ${localeMsg.backup}"), + ), + ) + else + Container(), + if (_loadFileResult != null) + Container( + color: Colors.black, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Result:\n$_loadFileResult', + style: const TextStyle(color: Colors.white), + ), + ), + ) + else + Container(), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900, + ), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -289,21 +310,22 @@ class _BackupPopupState extends State ), const SizedBox(width: 15), ElevatedButton.icon( - onPressed: requestRestore, - label: Text(localeMsg.restore), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.history, size: 16)) + onPressed: requestRestore, + label: Text(localeMsg.restore), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.history, size: 16), + ), ], - ) + ), ], ); } @@ -314,30 +336,35 @@ class _BackupPopupState extends State setState(() { _isLoading = true; }); + final localMsg = AppLocalizations.of(context)!; final messenger = ScaffoldMessenger.of(context); final result = await backupTenantDB(widget.tenantName, _tenantPassword!, _isChecked); switch (result) { case Success(value: final value): if (_isChecked) { - String filename = + final String filename = "${widget.tenantName}_db_${DateFormat('yyyy-MM-ddTHHmmss').format(DateTime.now())}.archive"; if (kIsWeb) { // If web, use html to download csv html.AnchorElement( - href: - 'data:application/octet-stream;base64,${base64Encode(value)}') + href: + 'data:application/octet-stream;base64,${base64Encode(value)}', + ) ..setAttribute("download", filename) ..click(); } else { // Save to local filesystem - var path = (await getApplicationDocumentsDirectory()).path; - var fileName = '$path/$filename'; - var file = File(fileName); - file.writeAsBytes(value, flush: true).then((value) => - showSnackBar(messenger, - "${AppLocalizations.of(context)!.fileSavedTo} $fileName", - copyTextTap: fileName)); + final path = (await getApplicationDocumentsDirectory()).path; + final fileName = '$path/$filename'; + final file = File(fileName); + file.writeAsBytes(value, flush: true).then( + (value) => showSnackBar( + messenger, + "${localMsg.fileSavedTo} $fileName", + copyTextTap: fileName, + ), + ); } } else { showSnackBar(messenger, value, isSuccess: true); @@ -351,7 +378,7 @@ class _BackupPopupState extends State } } - requestRestore() async { + Future requestRestore() async { final localeMsg = AppLocalizations.of(context)!; if (_loadedFile == null) { showSnackBar(ScaffoldMessenger.of(context), localeMsg.mustSelectFile); @@ -365,12 +392,18 @@ class _BackupPopupState extends State }); final messenger = ScaffoldMessenger.of(context); final result = await restoreTenantDB( - _loadedFile!, widget.tenantName, _tenantPassword!, _isChecked); + _loadedFile!, + widget.tenantName, + _tenantPassword!, + _isChecked, + ); switch (result) { case Success(value: final value): - showSnackBar(messenger, - "${localeMsg.backupRestored} ${value.substring(value.lastIndexOf("+0000") + 5).trim()}", - duration: const Duration(seconds: 10)); + showSnackBar( + messenger, + "${localeMsg.backupRestored} ${value.substring(value.lastIndexOf("+0000") + 5).trim()}", + duration: const Duration(seconds: 10), + ); setState(() { _loadFileResult = value; _isLoading = false; diff --git a/APP/lib/widgets/tenants/popups/confirm_popup.dart b/APP/lib/widgets/tenants/popups/confirm_popup.dart index a7d161ba1..a96bf0142 100644 --- a/APP/lib/widgets/tenants/popups/confirm_popup.dart +++ b/APP/lib/widgets/tenants/popups/confirm_popup.dart @@ -9,11 +9,12 @@ class ConfirmPopup extends StatefulWidget { final String objName; final bool isStart; final Function parentCallback; - const ConfirmPopup( - {super.key, - required this.objName, - required this.parentCallback, - required this.isStart}); + const ConfirmPopup({ + super.key, + required this.objName, + required this.parentCallback, + required this.isStart, + }); @override State createState() => _ConfirmPopupState(); @@ -54,89 +55,101 @@ class _ConfirmPopupState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - widget.isStart - ? localeMsg.startingTenant - : localeMsg.areYouSure, - style: Theme.of(context).textTheme.headlineMedium), - widget.isStart - ? Container() - : Padding( - padding: const EdgeInsets.symmetric(vertical: 30), - child: Text(localeMsg.stopTenantWarn), - ), - widget.isStart - ? Container() - : Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.close), - icon: const Icon( - Icons.cancel_outlined, - size: 16, - ), - ), - const SizedBox(width: 15), - ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red.shade900), - onPressed: () => submitStopStartTenant( - localeMsg, context, widget.objName), - label: Text("Stop"), - icon: _isLoading - ? Container( - width: 22, - height: 22, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.stop_circle, size: 16)) - ], + widget.isStart + ? localeMsg.startingTenant + : localeMsg.areYouSure, + style: Theme.of(context).textTheme.headlineMedium, + ), + if (widget.isStart) + Container() + else + Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: Text(localeMsg.stopTenantWarn), + ), + if (widget.isStart) + Container() + else + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, + ), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.close), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), ), - _updateResult != "" - ? Padding( - padding: const EdgeInsets.only(top: 12), - child: Container( - height: 110, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.black, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - controller: _outputController, - children: [ - SelectableText( - "Output:$_updateResult", - style: const TextStyle(color: Colors.white), - ), - ], - ), - ), + const SizedBox(width: 15), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red.shade900, + ), + onPressed: () => submitStopStartTenant( + localeMsg, + context, + widget.objName, ), - ) - : Container(), - widget.isStart - ? Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, + label: const Text("Stop"), + icon: _isLoading + ? Container( + width: 22, + height: 22, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.stop_circle, size: 16), + ), + ], + ), + if (_updateResult != "") + Padding( + padding: const EdgeInsets.only(top: 12), + child: Container( + height: 110, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.black, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + controller: _outputController, children: [ - ElevatedButton.icon( - onPressed: () => Navigator.pop(context), - label: Text("OK"), - icon: const Icon(Icons.check_circle, size: 16)) + SelectableText( + "Output:$_updateResult", + style: const TextStyle(color: Colors.white), + ), ], ), - ) - : Container(), + ), + ), + ) + else + Container(), + if (widget.isStart) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton.icon( + onPressed: () => Navigator.pop(context), + label: const Text("OK"), + icon: const Icon(Icons.check_circle, size: 16), + ), + ], + ), + ) + else + Container(), ], ), ), @@ -145,8 +158,11 @@ class _ConfirmPopupState extends State { ); } - submitStopStartTenant(AppLocalizations localeMsg, BuildContext popupContext, - String tenantName) async { + submitStopStartTenant( + AppLocalizations localeMsg, + BuildContext popupContext, + String tenantName, + ) async { final messenger = ScaffoldMessenger.of(popupContext); Result, Exception> result; setState(() { @@ -163,10 +179,9 @@ class _ConfirmPopupState extends State { if (_updateResult.isNotEmpty) { _updateResult = "$_updateResult\nOutput:"; } - await for (var chunk in value) { + await for (final chunk in value) { // Process each chunk as it is received - print(chunk); - var newLine = chunk.split("data:").last.trim(); + final newLine = chunk.split("data:").last.trim(); if (newLine.isNotEmpty) { setState(() { _updateResult = "$_updateResult\n$newLine"; @@ -185,17 +200,21 @@ class _ConfirmPopupState extends State { setState(() { _isLoading = false; }); - showSnackBar(messenger, "$finalMsg. Check output log below.", - isError: true); + showSnackBar( + messenger, + "$finalMsg. Check output log below.", + isError: true, + ); } else { widget.parentCallback(); - if (context.mounted) { + if (mounted) { showSnackBar( - ScaffoldMessenger.of(context), - widget.isStart - ? "${localeMsg.startTenantOK} 🥳" - : localeMsg.stopTenantOK, - isSuccess: true); + ScaffoldMessenger.of(context), + widget.isStart + ? "${localeMsg.startTenantOK} 🥳" + : localeMsg.stopTenantOK, + isSuccess: true, + ); } if (popupContext.mounted) Navigator.of(popupContext).pop(); } diff --git a/APP/lib/widgets/tenants/popups/container_logs_popup.dart b/APP/lib/widgets/tenants/popups/container_logs_popup.dart index e21c49081..364d5110e 100644 --- a/APP/lib/widgets/tenants/popups/container_logs_popup.dart +++ b/APP/lib/widgets/tenants/popups/container_logs_popup.dart @@ -6,8 +6,8 @@ import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; class ContainerLogsPopup extends StatefulWidget { - String containerName; - ContainerLogsPopup({super.key, required this.containerName}); + final String containerName; + const ContainerLogsPopup({super.key, required this.containerName}); @override State createState() => _ContainerLogsPopupState(); @@ -25,7 +25,9 @@ class _ContainerLogsPopupState extends State { width: 525, margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), decoration: BoxDecoration( - color: Colors.white, borderRadius: BorderRadius.circular(20)), + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), child: Padding( padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), child: Material( @@ -49,32 +51,35 @@ class _ContainerLogsPopupState extends State { ), const Divider(height: 45), FutureBuilder( - future: getTenantStats(), - builder: (context, _) { - if (logs == null) { - return const Center(child: CircularProgressIndicator()); - } else if (logs != "") { - return Expanded( - child: SingleChildScrollView( - child: SelectableText(logs!)), - ); - } else { - // Empty messages - return Text("${localeMsg.noDockerLogs} :("); - } - }), + future: getTenantStats(), + builder: (context, _) { + if (logs == null) { + return const Center(child: CircularProgressIndicator()); + } else if (logs != "") { + return Expanded( + child: SingleChildScrollView( + child: SelectableText(logs!), + ), + ); + } else { + // Empty messages + return Text("${localeMsg.noDockerLogs} :("); + } + }, + ), const SizedBox(height: 25), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ ElevatedButton.icon( - onPressed: () { - Navigator.of(context).pop(); - }, - label: const Text("OK"), - icon: const Icon(Icons.thumb_up, size: 16)) + onPressed: () { + Navigator.of(context).pop(); + }, + label: const Text("OK"), + icon: const Icon(Icons.thumb_up, size: 16), + ), ], - ) + ), ], ), ), diff --git a/APP/lib/widgets/tenants/popups/create_server_popup.dart b/APP/lib/widgets/tenants/popups/create_server_popup.dart index 08eb20d13..f968b84b9 100644 --- a/APP/lib/widgets/tenants/popups/create_server_popup.dart +++ b/APP/lib/widgets/tenants/popups/create_server_popup.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; class CreateServerPopup extends StatefulWidget { @@ -39,15 +39,16 @@ class _CreateServerPopupState extends State { child: Container( width: 500, constraints: BoxConstraints( - maxHeight: backendType == BackendType.kubernetes ? 470 : 560), + maxHeight: backendType == BackendType.kubernetes ? 470 : 560, + ), margin: const EdgeInsets.symmetric(horizontal: 20), decoration: PopupDecoration, child: Padding( padding: const EdgeInsets.fromLTRB(40, 20, 40, 15), child: Form( - key: _formKey, - child: ScaffoldMessenger( - child: Builder( + key: _formKey, + child: ScaffoldMessenger( + child: Builder( builder: (context) => Scaffold( backgroundColor: Colors.white, body: ListView( @@ -64,13 +65,15 @@ class _CreateServerPopupState extends State { // const Divider(height: 45), const SizedBox(height: 20), getFormField( - save: (newValue) => _sshHost = newValue, - label: localeMsg.sshHost, - icon: Icons.dns), + save: (newValue) => _sshHost = newValue, + label: localeMsg.sshHost, + icon: Icons.dns, + ), getFormField( - save: (newValue) => _sshUser = newValue, - label: localeMsg.sshUser, - icon: Icons.person), + save: (newValue) => _sshUser = newValue, + label: localeMsg.sshUser, + icon: Icons.person, + ), const SizedBox(height: 4), Wrap( children: [ @@ -107,82 +110,94 @@ class _CreateServerPopupState extends State { ], ), const SizedBox(height: 4), - _authOption == AuthOption.pKey - ? Column( - children: [ - getFormField( - save: (newValue) => _sshKey = newValue, - label: - "${localeMsg.sshPrivateKey} (/local/path/file)", - icon: Icons.lock), - getFormField( - save: (newValue) => _sshKeyPass = newValue, - label: - "${localeMsg.sshPrivateKeyPassphrase} (${localeMsg.optional})", - icon: Icons.lock, - shouldValidate: false), - ], - ) - : getFormField( - save: (newValue) => _sshPassword = newValue, - label: localeMsg.password, - icon: Icons.lock), - backendType == BackendType.kubernetes - ? getFormField( - save: (newValue) => _kubeDns = newValue, - label: "Cluster DNS", - icon: Icons.dns) - : Container(), - backendType != BackendType.kubernetes - ? getFormField( - save: (newValue) => _installPath = newValue, - label: localeMsg.serverPath, - icon: Icons.folder) - : Container(), - backendType != BackendType.kubernetes - ? getFormField( - save: (newValue) => _port = newValue, - label: localeMsg.portServer, - icon: Icons.onetwothree, - formatters: [ - FilteringTextInputFormatter.digitsOnly - ]) - : Container(), + if (_authOption == AuthOption.pKey) + Column( + children: [ + getFormField( + save: (newValue) => _sshKey = newValue, + label: + "${localeMsg.sshPrivateKey} (/local/path/file)", + icon: Icons.lock, + ), + getFormField( + save: (newValue) => _sshKeyPass = newValue, + label: + "${localeMsg.sshPrivateKeyPassphrase} (${localeMsg.optional})", + icon: Icons.lock, + shouldValidate: false, + ), + ], + ) + else + getFormField( + save: (newValue) => _sshPassword = newValue, + label: localeMsg.password, + icon: Icons.lock, + ), + if (backendType == BackendType.kubernetes) + getFormField( + save: (newValue) => _kubeDns = newValue, + label: "Cluster DNS", + icon: Icons.dns, + ) + else + Container(), + if (backendType != BackendType.kubernetes) + getFormField( + save: (newValue) => _installPath = newValue, + label: localeMsg.serverPath, + icon: Icons.folder, + ) + else + Container(), + if (backendType != BackendType.kubernetes) + getFormField( + save: (newValue) => _port = newValue, + label: localeMsg.portServer, + icon: Icons.onetwothree, + formatters: [ + FilteringTextInputFormatter.digitsOnly, + ], + ) + else + Container(), SizedBox( - height: - backendType == BackendType.kubernetes ? 0 : 13), - backendType != BackendType.kubernetes - ? Row( - children: [ - const SizedBox(width: 40), - SizedBox( - height: 24, - width: 24, - child: Checkbox( - activeColor: Colors.blue.shade600, - value: _shouldStartup, - onChanged: (bool? value) => - setState(() => _shouldStartup = value!), - ), - ), - const SizedBox(width: 8), - Text( - localeMsg.runAtStart, - style: const TextStyle( - fontSize: 14, - color: Colors.black, - ), - ), - ], - ) - : Container(), + height: backendType == BackendType.kubernetes ? 0 : 13, + ), + if (backendType != BackendType.kubernetes) + Row( + children: [ + const SizedBox(width: 40), + SizedBox( + height: 24, + width: 24, + child: Checkbox( + activeColor: Colors.blue.shade600, + value: _shouldStartup, + onChanged: (bool? value) => + setState(() => _shouldStartup = value!), + ), + ), + const SizedBox(width: 8), + Text( + localeMsg.runAtStart, + style: const TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + ], + ) + else + Container(), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900, + ), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -192,25 +207,28 @@ class _CreateServerPopupState extends State { ), const SizedBox(width: 15), ElevatedButton.icon( - onPressed: () => submitCreateServer(localeMsg), - label: Text(localeMsg.create), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, size: 16)) + onPressed: () => submitCreateServer(localeMsg), + label: Text(localeMsg.create), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.check_circle, size: 16), + ), ], - ) + ), ], ), ), - ))), + ), + ), + ), ), ), ); @@ -223,26 +241,26 @@ class _CreateServerPopupState extends State { _isLoading = true; }); - Map serverInfo = { - 'host': _sshHost!, - 'user': _sshUser!, + final Map serverInfo = { + 'host': _sshHost, + 'user': _sshUser, }; if (backendType == BackendType.kubernetes) { - serverInfo['dns'] = _kubeDns!; + serverInfo['dns'] = _kubeDns; } else { serverInfo.addAll({ - 'dstpath': _installPath!, - 'runport': _port!, + 'dstpath': _installPath, + 'runport': _port, 'startup': _shouldStartup, }); } if (_authOption == AuthOption.pKey) { serverInfo.addAll({ - 'pkey': _sshKey!, + 'pkey': _sshKey, 'pkeypass': _sshKeyPass.toString(), }); } else { - serverInfo['password'] = _sshPassword!; + serverInfo['password'] = _sshPassword; } final messenger = ScaffoldMessenger.of(context); @@ -251,7 +269,7 @@ class _CreateServerPopupState extends State { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.createOK, isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): setState(() { _isLoading = false; @@ -261,14 +279,15 @@ class _CreateServerPopupState extends State { } } - getFormField( - {required Function(String?) save, - required String label, - required IconData icon, - String? prefix, - String? suffix, - List? formatters, - bool shouldValidate = true}) { + Padding getFormField({ + required Function(String?) save, + required String label, + required IconData icon, + String? prefix, + String? suffix, + List? formatters, + bool shouldValidate = true, + }) { return Padding( padding: FormInputPadding, child: TextFormField( @@ -282,8 +301,13 @@ class _CreateServerPopupState extends State { return null; }, inputFormatters: formatters, - decoration: GetFormInputDecoration(_isSmallDisplay, label, - prefixText: prefix, suffixText: suffix, icon: icon), + decoration: GetFormInputDecoration( + _isSmallDisplay, + label, + prefixText: prefix, + suffixText: suffix, + icon: icon, + ), cursorWidth: 1.3, style: const TextStyle(fontSize: 14), ), diff --git a/APP/lib/widgets/tenants/popups/create_tenant_popup.dart b/APP/lib/widgets/tenants/popups/create_tenant_popup.dart index 15e74e2c7..5bc4f072e 100644 --- a/APP/lib/widgets/tenants/popups/create_tenant_popup.dart +++ b/APP/lib/widgets/tenants/popups/create_tenant_popup.dart @@ -1,10 +1,10 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/tenant.dart'; @@ -43,224 +43,249 @@ class _CreateTenantPopupState extends State { child: Container( width: 500, constraints: BoxConstraints( - maxHeight: backendType == BackendType.kubernetes - ? 420 - : (_createResult == "" || !_hasWeb ? 540 : 660)), + maxHeight: backendType == BackendType.kubernetes + ? 420 + : (_createResult == "" || !_hasWeb ? 540 : 660), + ), margin: const EdgeInsets.symmetric(horizontal: 20), decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 30 : 40, 20, _isSmallDisplay ? 30 : 40, 15), + _isSmallDisplay ? 30 : 40, + 20, + _isSmallDisplay ? 30 : 40, + 15, + ), child: Form( key: _formKey, child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: ListView( - padding: EdgeInsets.zero, - //shrinkWrap: true, + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: ListView( + padding: EdgeInsets.zero, + //shrinkWrap: true, + children: [ + Center( + child: Text( + "${localeMsg.create} tenant", + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + // const Divider(height: 35), + const SizedBox(height: 20), + getFormField( + save: (newValue) => _tenantName = newValue, + label: localeMsg.tenantName, + icon: Icons.business_center, + ), + getFormField( + save: (newValue) => _tenantPassword = newValue, + label: localeMsg.tenantPassword, + icon: Icons.lock, + ), + const SizedBox(height: 4), + Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + getCheckBox( + "API", + true, + (_) {}, + enabled: false, + ), + getCheckBox( + "WEB", + _hasWeb, + (value) => setState(() { + _hasWeb = value!; + }), + ), + getCheckBox( + "DOC", + _hasDoc, + (value) => setState(() { + _hasDoc = value!; + }), + ), + ], + ), + const SizedBox(height: 10), + getFormField( + save: (newValue) => _imageTag = newValue!, + label: localeMsg.deployVersion, + icon: Icons.access_time, + initial: _imageTag, + ), + if (backendType != BackendType.kubernetes) + getFormField( + save: (newValue) { + final splitted = newValue!.split(":"); + _apiUrl = "${splitted[0]}:${splitted[1]}"; + _apiPort = splitted[2]; + }, + label: + "${localeMsg.apiUrl} (${localeMsg.hostnamePort})", + icon: Icons.cloud, + initial: "http://", + isUrl: true, + ) + else + Container(), + if (_hasWeb && backendType != BackendType.kubernetes) + getFormField( + save: (newValue) { + final splitted = newValue!.split(":"); + _webUrl = "${splitted[0]}:${splitted[1]}"; + _webPort = splitted[2]; + }, + label: + "${localeMsg.webUrl} (${localeMsg.hostnamePort})", + icon: Icons.monitor, + initial: "http://", + isUrl: true, + ) + else + Container(), + if (_hasWeb) + Padding( + padding: const EdgeInsets.only( + top: 8.0, + bottom: 8, + ), + child: Wrap( + alignment: WrapAlignment.end, + crossAxisAlignment: WrapCrossAlignment.center, children: [ - Center( - child: Text( - "${localeMsg.create} tenant", - style: - Theme.of(context).textTheme.headlineMedium, - )), - // const Divider(height: 35), - const SizedBox(height: 20), - getFormField( - save: (newValue) => _tenantName = newValue, - label: localeMsg.tenantName, - icon: Icons.business_center), - getFormField( - save: (newValue) => - _tenantPassword = newValue, - label: localeMsg.tenantPassword, - icon: Icons.lock), - const SizedBox(height: 4), - Wrap( - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - getCheckBox("API", true, (_) {}, - enabled: false), - getCheckBox( - "WEB", - _hasWeb, - (value) => setState(() { - _hasWeb = value!; - })), - getCheckBox( - "DOC", - _hasDoc, - (value) => setState(() { - _hasDoc = value!; - })), - ], - ), - const SizedBox(height: 10), - getFormField( - save: (newValue) => _imageTag = newValue!, - label: localeMsg.deployVersion, - icon: Icons.access_time, - initial: _imageTag), - backendType != BackendType.kubernetes - ? getFormField( - save: (newValue) { - var splitted = newValue!.split(":"); - _apiUrl = - "${splitted[0]}:${splitted[1]}"; - _apiPort = splitted[2]; - }, - label: - "${localeMsg.apiUrl} (${localeMsg.hostnamePort})", - icon: Icons.cloud, - initial: "http://", - isUrl: true, - ) - : Container(), - _hasWeb && backendType != BackendType.kubernetes - ? getFormField( - save: (newValue) { - var splitted = newValue!.split(":"); - _webUrl = - "${splitted[0]}:${splitted[1]}"; - _webPort = splitted[2]; - }, - label: - "${localeMsg.webUrl} (${localeMsg.hostnamePort})", - icon: Icons.monitor, - initial: "http://", - isUrl: true, - ) - : Container(), - _hasWeb - ? Padding( - padding: const EdgeInsets.only( - top: 8.0, bottom: 8), - child: Wrap( - alignment: WrapAlignment.end, - crossAxisAlignment: - WrapCrossAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - right: 20), - child: _loadedImage == null - ? Image.asset( - "assets/custom/logo.png", - height: 40, - ) - : Image.memory( - _loadedImage!.bytes!, - height: 40, - ), - ), - ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform - .pickFiles( - type: - FileType.custom, - allowedExtensions: [ - "png" - ], - withData: true); - if (result != null) { - setState(() { - _loadedImage = - result.files.single; - }); - } - }, - icon: const Icon(Icons.download), - label: Text(_isSmallDisplay - ? "Web Logo" - : localeMsg.selectLogo)), - ], + Padding( + padding: const EdgeInsets.only( + right: 20, + ), + child: _loadedImage == null + ? Image.asset( + "assets/custom/logo.png", + height: 40, + ) + : Image.memory( + _loadedImage!.bytes!, + height: 40, ), - ) - : Container(), - _hasDoc && backendType != BackendType.kubernetes - ? getFormField( - save: (newValue) { - var splitted = newValue!.split(":"); - _docUrl = splitted[0] + splitted[1]; - _docPort = splitted[2]; - }, - label: - "${localeMsg.docUrl} (${localeMsg.hostnamePort})", - icon: Icons.book, - initial: "http://", - isUrl: true, - ) - : Container(), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.end, + ), + ElevatedButton.icon( + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: [ + "png", + ], + withData: true, + ); + if (result != null) { + setState(() { + _loadedImage = result.files.single; + }); + } + }, + icon: const Icon(Icons.download), + label: Text( + _isSmallDisplay + ? "Web Logo" + : localeMsg.selectLogo, + ), + ), + ], + ), + ) + else + Container(), + if (_hasDoc && backendType != BackendType.kubernetes) + getFormField( + save: (newValue) { + final splitted = newValue!.split(":"); + _docUrl = splitted[0] + splitted[1]; + _docPort = splitted[2]; + }, + label: + "${localeMsg.docUrl} (${localeMsg.hostnamePort})", + icon: Icons.book, + initial: "http://", + isUrl: true, + ) + else + Container(), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, + ), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.cancel), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + const SizedBox(width: 15), + ElevatedButton.icon( + onPressed: () => submitCreateTenant( + localeMsg, + context, + ), + label: Text(localeMsg.create), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon( + Icons.check_circle, + size: 16, + ), + ), + ], + ), + if (_createResult != "") + Padding( + padding: const EdgeInsets.only(top: 12), + child: Container( + height: 110, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.black, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + controller: _outputController, children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.cancel), - icon: const Icon( - Icons.cancel_outlined, - size: 16, + SelectableText( + "Output:$_createResult", + style: const TextStyle( + color: Colors.white, ), ), - const SizedBox(width: 15), - ElevatedButton.icon( - onPressed: () => submitCreateTenant( - localeMsg, context), - label: Text(localeMsg.create), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: - const EdgeInsets.all(2.0), - child: - const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, - size: 16)) ], ), - _createResult != "" - ? Padding( - padding: const EdgeInsets.only(top: 12), - child: Container( - height: 110, - decoration: BoxDecoration( - borderRadius: - BorderRadius.circular(12), - color: Colors.black, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - controller: _outputController, - children: [ - SelectableText( - "Output:$_createResult", - style: const TextStyle( - color: Colors.white), - ), - ], - ), - ), - ), - ) - : Container() - ], + ), ), - ))), + ) + else + Container(), + ], + ), + ), + ), + ), ), ), ), @@ -268,7 +293,9 @@ class _CreateTenantPopupState extends State { } submitCreateTenant( - AppLocalizations localeMsg, BuildContext popupContext) async { + AppLocalizations localeMsg, + BuildContext popupContext, + ) async { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); setState(() { @@ -277,7 +304,7 @@ class _CreateTenantPopupState extends State { // Load logo first, if provided final messenger = ScaffoldMessenger.of(popupContext); if (_loadedImage != null) { - var result = await uploadImage(_loadedImage!, _tenantName!); + final result = await uploadImage(_loadedImage!, _tenantName!); switch (result) { case Success(): break; @@ -286,7 +313,8 @@ class _CreateTenantPopupState extends State { } } // Create tenant - var result = await createTenant(Tenant( + final result = await createTenant( + Tenant( _tenantName!, _tenantPassword!, _apiUrl!, @@ -297,17 +325,18 @@ class _CreateTenantPopupState extends State { _hasDoc, _docUrl, _docPort, - _imageTag)); + _imageTag, + ), + ); switch (result) { case Success(value: final value): String finalMsg = ""; if (_createResult.isNotEmpty) { _createResult = "$_createResult\nOutput:"; } - await for (var chunk in value) { + await for (final chunk in value) { // Process each chunk as it is received - print(chunk); - var newLine = chunk.split("data:").last.trim(); + final newLine = chunk.split("data:").last.trim(); if (newLine.isNotEmpty) { setState(() { _createResult = "$_createResult\n$newLine"; @@ -326,14 +355,19 @@ class _CreateTenantPopupState extends State { setState(() { _isLoading = false; }); - showSnackBar(messenger, "$finalMsg. Check output log below.", - isError: true); + showSnackBar( + messenger, + "$finalMsg. Check output log below.", + isError: true, + ); } else { widget.parentCallback(); - if (context.mounted) { - showSnackBar(ScaffoldMessenger.of(context), - "${localeMsg.tenantCreated} 🥳", - isSuccess: true); + if (mounted) { + showSnackBar( + ScaffoldMessenger.of(context), + "${localeMsg.tenantCreated} 🥳", + isSuccess: true, + ); } if (popupContext.mounted) Navigator.of(popupContext).pop(); } @@ -346,8 +380,12 @@ class _CreateTenantPopupState extends State { } } - getCheckBox(String title, bool value, Function(bool?) onChange, - {bool enabled = true}) { + SizedBox getCheckBox( + String title, + bool value, + Function(bool?) onChange, { + bool enabled = true, + }) { return SizedBox( width: 95, child: CheckboxListTile( @@ -358,20 +396,23 @@ class _CreateTenantPopupState extends State { enabled: enabled, onChanged: (value) => onChange(value), title: Transform.translate( - offset: const Offset(-10, 0), child: Text(title)), + offset: const Offset(-10, 0), + child: Text(title), + ), ), ); } - getFormField( - {required Function(String?) save, - required String label, - required IconData icon, - String? prefix, - String? suffix, - List? formatters, - String? initial, - bool isUrl = false}) { + Padding getFormField({ + required Function(String?) save, + required String label, + required IconData icon, + String? prefix, + String? suffix, + List? formatters, + String? initial, + bool isUrl = false, + }) { return Padding( padding: FormInputPadding, child: TextFormField( @@ -398,8 +439,13 @@ class _CreateTenantPopupState extends State { return null; }, inputFormatters: formatters, - decoration: GetFormInputDecoration(_isSmallDisplay, label, - prefixText: prefix, suffixText: suffix, icon: icon), + decoration: GetFormInputDecoration( + _isSmallDisplay, + label, + prefixText: prefix, + suffixText: suffix, + icon: icon, + ), cursorWidth: 1.3, style: const TextStyle(fontSize: 14), ), diff --git a/APP/lib/widgets/tenants/popups/domain_popup.dart b/APP/lib/widgets/tenants/popups/domain_popup.dart index 40c9c3f41..f0f518737 100644 --- a/APP/lib/widgets/tenants/popups/domain_popup.dart +++ b/APP/lib/widgets/tenants/popups/domain_popup.dart @@ -1,21 +1,24 @@ +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/domain.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:ogree_app/widgets/common/form_field.dart'; class DomainPopup extends StatefulWidget { Function() parentCallback; String? domainId; String? parentId; - DomainPopup( - {super.key, required this.parentCallback, this.domainId, this.parentId}); + DomainPopup({ + super.key, + required this.parentCallback, + this.domainId, + this.parentId, + }); @override State createState() => _DomainPopupState(); @@ -27,7 +30,6 @@ class _DomainPopupState extends State String? _domainParent; String? _domainName; String? _domainColor; - Color? _localColor; String? _domainDescription; bool _isLoading = false; bool _isLoadingDelete = false; @@ -56,7 +58,7 @@ class _DomainPopupState extends State future: _isEdit && domain == null ? getDomain(localeMsg) : null, builder: (context, _) { if (!_isEdit || (_isEdit && domain != null)) { - return DomainForm(localeMsg); + return domainForm(localeMsg); } else { return const Center(child: CircularProgressIndicator()); } @@ -64,7 +66,7 @@ class _DomainPopupState extends State ); } - getDomain(AppLocalizations localeMsg) async { + Future getDomain(AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); final result = await fetchDomain(widget.domainId!); switch (result) { @@ -73,16 +75,15 @@ class _DomainPopupState extends State domainId = domain!.parent == "" ? domain!.name : "${domain!.parent}.${domain!.name}"; - _localColor = Color(int.parse("0xFF${domain!.color}")); colorTextController.text = domain!.color; case Failure(): showSnackBar(messenger, localeMsg.noDomain, isError: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); return; } } - DomainForm(AppLocalizations localeMsg) { + Center domainForm(AppLocalizations localeMsg) { return Center( child: Container( width: 500, @@ -90,7 +91,11 @@ class _DomainPopupState extends State decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 30 : 40, 8, _isSmallDisplay ? 30 : 40, 15), + _isSmallDisplay ? 30 : 40, + 8, + _isSmallDisplay ? 30 : 40, + 15, + ), child: Material( color: Colors.white, child: Form( @@ -103,11 +108,13 @@ class _DomainPopupState extends State tabAlignment: TabAlignment.center, controller: _tabController, labelStyle: TextStyle( - fontSize: 15, - fontFamily: GoogleFonts.inter().fontFamily), + fontSize: 15, + fontFamily: GoogleFonts.inter().fontFamily, + ), unselectedLabelStyle: TextStyle( - fontSize: 15, - fontFamily: GoogleFonts.inter().fontFamily), + fontSize: 15, + fontFamily: GoogleFonts.inter().fontFamily, + ), isScrollable: true, indicatorSize: TabBarIndicatorSize.label, tabs: _isEdit @@ -149,7 +156,8 @@ class _DomainPopupState extends State children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900, + ), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -157,55 +165,68 @@ class _DomainPopupState extends State size: 16, ), ), - _isEdit - ? TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.red.shade900), - onPressed: () => removeDomain(localeMsg), - label: - Text(_isSmallDisplay ? "" : localeMsg.delete), - icon: _isLoadingDelete - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: CircularProgressIndicator( - color: Colors.red.shade900, - strokeWidth: 3, - ), - ) - : const Icon( - Icons.delete, - size: 16, - ), - ) - : Container(), - _isSmallDisplay ? Container() : const SizedBox(width: 10), + if (_isEdit) + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.red.shade900, + ), + onPressed: () => removeDomain(localeMsg), + label: Text(_isSmallDisplay ? "" : localeMsg.delete), + icon: _isLoadingDelete + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: CircularProgressIndicator( + color: Colors.red.shade900, + strokeWidth: 3, + ), + ) + : const Icon( + Icons.delete, + size: 16, + ), + ) + else + Container(), + if (_isSmallDisplay) + Container() + else + const SizedBox(width: 10), ElevatedButton.icon( onPressed: () async { final messenger = ScaffoldMessenger.of(context); if (_tabController.index == 1) { if (_loadedFile == null) { - showSnackBar(ScaffoldMessenger.of(context), - localeMsg.mustSelectJSON); + showSnackBar( + ScaffoldMessenger.of(context), + localeMsg.mustSelectJSON, + ); } else if (_loadFileResult != null) { widget.parentCallback(); Navigator.of(context).pop(); } else { - var result = await createBulkFile( - _loadedFile!.bytes!, "domains"); + final result = await createBulkFile( + _loadedFile!.bytes!, + "domains", + ); switch (result) { case Success(value: final value): setState(() { _loadFileResult = value.replaceAll(",", ",\n"); - _loadFileResult = _loadFileResult! - .substring( - 1, _loadFileResult!.length - 1); + _loadFileResult = + _loadFileResult!.substring( + 1, + _loadFileResult!.length - 1, + ); }); case Failure(exception: final exception): - showSnackBar(messenger, exception.toString(), - isError: true); + showSnackBar( + messenger, + exception.toString(), + isError: true, + ); } } } else { @@ -214,11 +235,12 @@ class _DomainPopupState extends State setState(() { _isLoading = true; }); - var newDomain = Domain( - _domainName!, - _domainColor!, - _domainDescription!, - _domainParent!); + final newDomain = Domain( + _domainName!, + _domainColor!, + _domainDescription!, + _domainParent!, + ); Result result; if (_isEdit) { result = @@ -229,27 +251,34 @@ class _DomainPopupState extends State switch (result) { case Success(): widget.parentCallback(); - showSnackBar(messenger, - "${_isEdit ? localeMsg.modifyOK : localeMsg.createOK} 🥳", - isSuccess: true); - if (context.mounted) { + showSnackBar( + messenger, + "${_isEdit ? localeMsg.modifyOK : localeMsg.createOK} 🥳", + isSuccess: true, + ); + if (mounted) { Navigator.of(context).pop(); } case Failure(exception: final exception): setState(() { _isLoading = false; }); - showSnackBar(messenger, exception.toString(), - isError: true); + showSnackBar( + messenger, + exception.toString(), + isError: true, + ); } } } }, - label: Text(_isEdit - ? localeMsg.modify - : (_loadFileResult == null - ? localeMsg.create - : "OK")), + label: Text( + _isEdit + ? localeMsg.modify + : (_loadFileResult == null + ? localeMsg.create + : "OK"), + ), icon: _isLoading ? Container( width: 24, @@ -263,7 +292,7 @@ class _DomainPopupState extends State : const Icon(Icons.check_circle, size: 16), ), ], - ) + ), ], ), ), @@ -278,12 +307,12 @@ class _DomainPopupState extends State _isLoadingDelete = true; }); final messenger = ScaffoldMessenger.of(context); - var result = await removeObject(domainId!, "domains"); + final result = await removeObject(domainId!, "domains"); switch (result) { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.deleteOK); - if (context.mounted) { + if (mounted) { Navigator.of(context).pop(); } case Failure(exception: final exception): @@ -294,79 +323,90 @@ class _DomainPopupState extends State } } - getDomainForm() { + ListView getDomainForm() { final localeMsg = AppLocalizations.of(context)!; return ListView( padding: EdgeInsets.zero, children: [ CustomFormField( - save: (newValue) => _domainParent = newValue, - label: localeMsg.parentDomain, - icon: Icons.auto_awesome_mosaic, - initialValue: _isEdit ? domain!.parent : widget.parentId, - shouldValidate: false), + save: (newValue) => _domainParent = newValue, + label: localeMsg.parentDomain, + icon: Icons.auto_awesome_mosaic, + initialValue: _isEdit ? domain!.parent : widget.parentId, + shouldValidate: false, + ), CustomFormField( - save: (newValue) => _domainName = newValue, - label: localeMsg.domainName, - icon: Icons.auto_awesome_mosaic, - initialValue: _isEdit ? domain!.name : null), + save: (newValue) => _domainName = newValue, + label: localeMsg.domainName, + icon: Icons.auto_awesome_mosaic, + initialValue: _isEdit ? domain!.name : null, + ), CustomFormField( - save: (newValue) => _domainDescription = newValue, - label: "Description", - icon: Icons.message, - initialValue: _isEdit ? domain!.description : null), + save: (newValue) => _domainDescription = newValue, + label: "Description", + icon: Icons.message, + initialValue: _isEdit ? domain!.description : null, + ), CustomFormField( - save: (newValue) => _domainColor = newValue, - label: localeMsg.color, - icon: Icons.color_lens, - isColor: true, - colorTextController: colorTextController, - maxLength: 6), + save: (newValue) => _domainColor = newValue, + label: localeMsg.color, + icon: Icons.color_lens, + isColor: true, + colorTextController: colorTextController, + maxLength: 6, + ), ], ); } - getBulkFileView() { + Center getBulkFileView() { final localeMsg = AppLocalizations.of(context)!; return Center( - child: ListView(shrinkWrap: true, children: [ - _loadFileResult == null - ? Align( - child: ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(withData: true); - if (result != null) { - setState(() { - _loadedFile = result.files.single; - }); - } - }, - icon: const Icon(Icons.download), - label: Text(localeMsg.selectJSON)), - ) - : Container(), - _loadedFile != null - ? Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), - child: Align( - child: Text(localeMsg.fileLoaded(_loadedFile!.name)), - ), - ) - : Container(), - _loadFileResult != null - ? Container( - color: Colors.black, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Result:\n $_loadFileResult', - style: const TextStyle(color: Colors.white), - ), + child: ListView( + shrinkWrap: true, + children: [ + if (_loadFileResult == null) + Align( + child: ElevatedButton.icon( + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles(withData: true); + if (result != null) { + setState(() { + _loadedFile = result.files.single; + }); + } + }, + icon: const Icon(Icons.download), + label: Text(localeMsg.selectJSON), + ), + ) + else + Container(), + if (_loadedFile != null) + Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), + child: Align( + child: Text(localeMsg.fileLoaded(_loadedFile!.name)), + ), + ) + else + Container(), + if (_loadFileResult != null) + Container( + color: Colors.black, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Result:\n $_loadFileResult', + style: const TextStyle(color: Colors.white), ), - ) - : Container(), - ]), + ), + ) + else + Container(), + ], + ), ); } } diff --git a/APP/lib/widgets/tenants/popups/tags_popup.dart b/APP/lib/widgets/tenants/popups/tags_popup.dart index f5cf44fcb..6dddd225c 100644 --- a/APP/lib/widgets/tenants/popups/tags_popup.dart +++ b/APP/lib/widgets/tenants/popups/tags_popup.dart @@ -1,22 +1,20 @@ import 'dart:convert'; -import 'package:flex_color_picker/flex_color_picker.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:ogree_app/models/tag.dart'; import 'package:ogree_app/widgets/common/actionbtn_row.dart'; import 'package:ogree_app/widgets/common/form_field.dart'; class TagsPopup extends StatefulWidget { - Function() parentCallback; - String? tagId; - TagsPopup({super.key, required this.parentCallback, this.tagId}); + final Function() parentCallback; + final String? tagId; + const TagsPopup({super.key, required this.parentCallback, this.tagId}); @override State createState() => _TagsPopupState(); @@ -51,7 +49,7 @@ class _TagsPopupState extends State with TickerProviderStateMixin { ); } - getTag(AppLocalizations localeMsg) async { + Future getTag(AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); final result = await fetchTag(widget.tagId!); switch (result) { @@ -59,12 +57,12 @@ class _TagsPopupState extends State with TickerProviderStateMixin { tag = value; case Failure(): showSnackBar(messenger, localeMsg.noDomain, isError: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); return; } } - getTagForm(AppLocalizations localeMsg) { + Center getTagForm(AppLocalizations localeMsg) { return Center( child: Container( width: 500, @@ -73,7 +71,11 @@ class _TagsPopupState extends State with TickerProviderStateMixin { decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 30 : 40, 8, _isSmallDisplay ? 30 : 40, 15), + _isSmallDisplay ? 30 : 40, + 8, + _isSmallDisplay ? 30 : 40, + 15, + ), child: Material( color: Colors.white, child: Column( @@ -99,10 +101,11 @@ class _TagsPopupState extends State with TickerProviderStateMixin { ), const SizedBox(height: 5), ActionBtnRow( - isEdit: _isEdit, - submitCreate: () => onActionBtnPressed(localeMsg), - submitModify: () => onActionBtnPressed(localeMsg), - submitDelete: () => () => onDeleteBtnPressed(localeMsg)), + isEdit: _isEdit, + submitCreate: () => onActionBtnPressed(localeMsg), + submitModify: () => onActionBtnPressed(localeMsg), + submitDelete: () => () => onDeleteBtnPressed(localeMsg), + ), ], ), ), @@ -113,29 +116,29 @@ class _TagsPopupState extends State with TickerProviderStateMixin { onDeleteBtnPressed(AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); - var result = await removeObject(tag!.slug, "tags"); + final result = await removeObject(tag!.slug, "tags"); switch (result) { case Success(): widget.parentCallback(); showSnackBar(messenger, localeMsg.deleteOK); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); } } - onActionBtnPressed(AppLocalizations localeMsg) async { + Future onActionBtnPressed(AppLocalizations localeMsg) async { final messenger = ScaffoldMessenger.of(context); final localeMsg = AppLocalizations.of(context)!; - Tag? newTag = _tagFormKey.currentState!.onActionBtnPressed(); + final Tag? newTag = _tagFormKey.currentState!.onActionBtnPressed(); if (newTag == null) { return; } Result result; if (_isEdit) { - var newTagMap = newTag!.toMap(); + final newTagMap = newTag.toMap(); if (newTag.image == "" && tag!.image != "") { newTagMap.remove("image"); // patch and keep old one } @@ -146,10 +149,12 @@ class _TagsPopupState extends State with TickerProviderStateMixin { switch (result) { case Success(): widget.parentCallback(); - showSnackBar(messenger, - "${_isEdit ? localeMsg.modifyOK : localeMsg.createOK} 🥳", - isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + showSnackBar( + messenger, + "${_isEdit ? localeMsg.modifyOK : localeMsg.createOK} 🥳", + isSuccess: true, + ); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); } @@ -157,8 +162,8 @@ class _TagsPopupState extends State with TickerProviderStateMixin { } class TagForm extends StatefulWidget { - Tag? tag; - TagForm({super.key, this.tag}); + final Tag? tag; + const TagForm({super.key, this.tag}); @override State createState() => TagFormState(); } @@ -168,14 +173,13 @@ class TagFormState extends State { String? _tagSlug; String? _tagDescription; String? _tagColor; - Color _localColor = Colors.grey; bool _isEdit = false; Tag? tag; final colorTextController = TextEditingController(); PlatformFile? _loadedImage; - bool _isSmallDisplay = false; + final bool _isSmallDisplay = false; @override void initState() { @@ -183,7 +187,6 @@ class TagFormState extends State { if (widget.tag != null) { tag = widget.tag; - _localColor = Color(int.parse("0xFF${tag!.color}")); colorTextController.text = tag!.color; _isEdit = true; } @@ -198,15 +201,17 @@ class TagFormState extends State { padding: EdgeInsets.zero, children: [ CustomFormField( - save: (newValue) => _tagSlug = newValue, - label: "Slug", - icon: Icons.auto_awesome_mosaic, - initialValue: _isEdit ? tag!.slug : null), + save: (newValue) => _tagSlug = newValue, + label: "Slug", + icon: Icons.auto_awesome_mosaic, + initialValue: _isEdit ? tag!.slug : null, + ), CustomFormField( - save: (newValue) => _tagDescription = newValue, - label: "Description", - icon: Icons.auto_awesome_mosaic, - initialValue: _isEdit ? tag!.description : null), + save: (newValue) => _tagDescription = newValue, + label: "Description", + icon: Icons.auto_awesome_mosaic, + initialValue: _isEdit ? tag!.description : null, + ), CustomFormField( save: (newValue) => _tagColor = newValue, label: localeMsg.color, @@ -221,21 +226,23 @@ class TagFormState extends State { alignment: WrapAlignment.end, crossAxisAlignment: WrapCrossAlignment.center, children: [ - _loadedImage != null || (_isEdit && tag!.image != "") - ? IconButton( - padding: const EdgeInsets.all(4), - constraints: const BoxConstraints(), - iconSize: 14, - onPressed: () { - setState(() { - _loadedImage = null; - tag!.image = ""; - }); - }, - icon: const Icon( - Icons.cancel_outlined, - )) - : Container(), + if (_loadedImage != null || (_isEdit && tag!.image != "")) + IconButton( + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), + iconSize: 14, + onPressed: () { + setState(() { + _loadedImage = null; + tag!.image = ""; + }); + }, + icon: const Icon( + Icons.cancel_outlined, + ), + ) + else + Container(), Padding( padding: const EdgeInsets.only(right: 20), child: _loadedImage == null @@ -251,25 +258,27 @@ class TagFormState extends State { ), ), ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = await FilePicker.platform - .pickFiles( - type: FileType.custom, - allowedExtensions: ["png", "jpg", "jpeg", "webp"], - withData: true); - if (result != null) { - setState(() { - _loadedImage = result.files.single; - }); - } - }, - icon: const Icon(Icons.download), - label: Text(_isSmallDisplay - ? "Image" - : "${localeMsg.select} image")), + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ["png", "jpg", "jpeg", "webp"], + withData: true, + ); + if (result != null) { + setState(() { + _loadedImage = result.files.single; + }); + } + }, + icon: const Icon(Icons.download), + label: Text( + _isSmallDisplay ? "Image" : "${localeMsg.select} image", + ), + ), ], ), - ) + ), ], ), ); @@ -278,13 +287,14 @@ class TagFormState extends State { Tag? onActionBtnPressed() { if (_formKey.currentState!.validate()) { _formKey.currentState!.save(); - var newTag = Tag( - slug: _tagSlug!, - description: _tagDescription!, - color: _tagColor!, - image: _loadedImage != null - ? "data:image/png;base64,${base64Encode(_loadedImage!.bytes!)}" - : ""); + final newTag = Tag( + slug: _tagSlug!, + description: _tagDescription!, + color: _tagColor!, + image: _loadedImage != null + ? "data:image/png;base64,${base64Encode(_loadedImage!.bytes!)}" + : "", + ); return newTag; } return null; diff --git a/APP/lib/widgets/tenants/popups/update_tenant_popup.dart b/APP/lib/widgets/tenants/popups/update_tenant_popup.dart index bbd4013ed..b8184bb17 100644 --- a/APP/lib/widgets/tenants/popups/update_tenant_popup.dart +++ b/APP/lib/widgets/tenants/popups/update_tenant_popup.dart @@ -1,17 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/tenant.dart'; class UpdateTenantPopup extends StatefulWidget { Function parentCallback; Tenant tenant; - UpdateTenantPopup( - {super.key, required this.tenant, required this.parentCallback}); + UpdateTenantPopup({ + super.key, + required this.tenant, + required this.parentCallback, + }); @override State createState() => _UpdateTenantPopupState(); @@ -36,7 +39,11 @@ class _UpdateTenantPopupState extends State { decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 30 : 40, 20, _isSmallDisplay ? 30 : 40, 15), + _isSmallDisplay ? 30 : 40, + 20, + _isSmallDisplay ? 30 : 40, + 15, + ), child: Material( color: Colors.white, child: Form( @@ -53,32 +60,34 @@ class _UpdateTenantPopupState extends State { ), const SizedBox(height: 12), Wrap( - alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.center, children: [ getCheckBox("API", true, (_) {}, enabled: false), getCheckBox( - "WEB", - widget.tenant.hasWeb, - (value) => setState(() { - widget.tenant.hasWeb = value!; - })), + "WEB", + widget.tenant.hasWeb, + (value) => setState(() { + widget.tenant.hasWeb = value!; + }), + ), getCheckBox( - "DOC", - widget.tenant.hasDoc, - (value) => setState(() { - widget.tenant.hasDoc = value!; - })), + "DOC", + widget.tenant.hasDoc, + (value) => setState(() { + widget.tenant.hasDoc = value!; + }), + ), ], ), getFormField( - save: (newValue) => widget.tenant.imageTag = newValue!, - label: localeMsg.deployVersion, - icon: Icons.access_time, - initial: widget.tenant.imageTag), + save: (newValue) => widget.tenant.imageTag = newValue!, + label: localeMsg.deployVersion, + icon: Icons.access_time, + initial: widget.tenant.imageTag, + ), getFormField( save: (newValue) { - var splitted = newValue!.split(":"); + final splitted = newValue!.split(":"); widget.tenant.apiUrl = "${splitted[0]}:${splitted[1]}"; widget.tenant.apiPort = splitted[2]; }, @@ -89,47 +98,46 @@ class _UpdateTenantPopupState extends State { ? "${widget.tenant.apiUrl}:${widget.tenant.apiPort}" : "http://", ), - widget.tenant.hasWeb - ? getFormField( - save: (newValue) { - var splitted = newValue!.split(":"); - widget.tenant.webUrl = - "${splitted[0]}:${splitted[1]}"; - widget.tenant.webPort = splitted[2]; - }, - label: - "${localeMsg.webUrl} (${localeMsg.hostnamePort})", - icon: Icons.monitor, - isUrl: true, - initial: widget.tenant.webUrl != "" - ? "${widget.tenant.webUrl}:${widget.tenant.webPort}" - : "http://", - ) - : Container(), - widget.tenant.hasDoc - ? getFormField( - save: (newValue) { - var splitted = newValue!.split(":"); - widget.tenant.docUrl = - "${splitted[0]}:${splitted[1]}"; - widget.tenant.docPort = splitted[2]; - }, - label: - "${localeMsg.docUrl} (${localeMsg.hostnamePort})", - icon: Icons.book, - isUrl: true, - initial: widget.tenant.docUrl != "" - ? "${widget.tenant.docUrl}:${widget.tenant.docPort}" - : "http://", - ) - : Container(), + if (widget.tenant.hasWeb) + getFormField( + save: (newValue) { + final splitted = newValue!.split(":"); + widget.tenant.webUrl = "${splitted[0]}:${splitted[1]}"; + widget.tenant.webPort = splitted[2]; + }, + label: "${localeMsg.webUrl} (${localeMsg.hostnamePort})", + icon: Icons.monitor, + isUrl: true, + initial: widget.tenant.webUrl != "" + ? "${widget.tenant.webUrl}:${widget.tenant.webPort}" + : "http://", + ) + else + Container(), + if (widget.tenant.hasDoc) + getFormField( + save: (newValue) { + final splitted = newValue!.split(":"); + widget.tenant.docUrl = "${splitted[0]}:${splitted[1]}"; + widget.tenant.docPort = splitted[2]; + }, + label: "${localeMsg.docUrl} (${localeMsg.hostnamePort})", + icon: Icons.book, + isUrl: true, + initial: widget.tenant.docUrl != "" + ? "${widget.tenant.docUrl}:${widget.tenant.docPort}" + : "http://", + ) + else + Container(), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), + foregroundColor: Colors.blue.shade900, + ), onPressed: () => Navigator.pop(context), label: Text(localeMsg.cancel), icon: const Icon( @@ -139,53 +147,55 @@ class _UpdateTenantPopupState extends State { ), const SizedBox(width: 15), ElevatedButton.icon( - onPressed: () async { - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - setState(() { - _isLoading = true; - }); - submitUpdateTenant(localeMsg, context); - } - }, - label: Text(localeMsg.toUpdate), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.update_rounded, size: 16)) + onPressed: () async { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + setState(() { + _isLoading = true; + }); + submitUpdateTenant(localeMsg, context); + } + }, + label: Text(localeMsg.toUpdate), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.update_rounded, size: 16), + ), ], ), - _updateResult != "" - ? Padding( - padding: const EdgeInsets.only(top: 12), - child: Container( - height: 110, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: Colors.black, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - controller: _outputController, - children: [ - SelectableText( - "Output:$_updateResult", - style: const TextStyle(color: Colors.white), - ), - ], + if (_updateResult != "") + Padding( + padding: const EdgeInsets.only(top: 12), + child: Container( + height: 110, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.black, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListView( + controller: _outputController, + children: [ + SelectableText( + "Output:$_updateResult", + style: const TextStyle(color: Colors.white), ), - ), + ], ), - ) - : Container() + ), + ), + ) + else + Container(), ], ), ), @@ -196,7 +206,9 @@ class _UpdateTenantPopupState extends State { } submitUpdateTenant( - AppLocalizations localeMsg, BuildContext popupContext) async { + AppLocalizations localeMsg, + BuildContext popupContext, + ) async { final messenger = ScaffoldMessenger.of(popupContext); final result = await updateTenant(widget.tenant); switch (result) { @@ -205,10 +217,9 @@ class _UpdateTenantPopupState extends State { if (_updateResult.isNotEmpty) { _updateResult = "$_updateResult\nOutput:"; } - await for (var chunk in value) { + await for (final chunk in value) { // Process each chunk as it is received - print(chunk); - var newLine = chunk.split("data:").last.trim(); + final newLine = chunk.split("data:").last.trim(); if (newLine.isNotEmpty) { setState(() { _updateResult = "$_updateResult\n$newLine"; @@ -227,14 +238,19 @@ class _UpdateTenantPopupState extends State { setState(() { _isLoading = false; }); - showSnackBar(messenger, "$finalMsg. Check output log below.", - isError: true); + showSnackBar( + messenger, + "$finalMsg. Check output log below.", + isError: true, + ); } else { widget.parentCallback(); - if (context.mounted) { + if (mounted) { showSnackBar( - ScaffoldMessenger.of(context), "${localeMsg.modifyOK} 🥳", - isSuccess: true); + ScaffoldMessenger.of(context), + "${localeMsg.modifyOK} 🥳", + isSuccess: true, + ); } if (popupContext.mounted) Navigator.of(popupContext).pop(); } @@ -246,8 +262,12 @@ class _UpdateTenantPopupState extends State { } } - getCheckBox(String title, bool value, Function(bool?) onChange, - {bool enabled = true}) { + SizedBox getCheckBox( + String title, + bool value, + Function(bool?) onChange, { + bool enabled = true, + }) { return SizedBox( width: 95, child: CheckboxListTile( @@ -258,20 +278,23 @@ class _UpdateTenantPopupState extends State { enabled: enabled, onChanged: (value) => onChange(value), title: Transform.translate( - offset: const Offset(-10, 0), child: Text(title)), + offset: const Offset(-10, 0), + child: Text(title), + ), ), ); } - getFormField( - {required Function(String?) save, - required String label, - required IconData icon, - String? prefix, - String? suffix, - List? formatters, - String? initial, - bool isUrl = false}) { + Padding getFormField({ + required Function(String?) save, + required String label, + required IconData icon, + String? prefix, + String? suffix, + List? formatters, + String? initial, + bool isUrl = false, + }) { return Padding( padding: FormInputPadding, child: TextFormField( @@ -298,8 +321,13 @@ class _UpdateTenantPopupState extends State { return null; }, inputFormatters: formatters, - decoration: GetFormInputDecoration(_isSmallDisplay, label, - prefixText: prefix, suffixText: suffix, icon: icon), + decoration: GetFormInputDecoration( + _isSmallDisplay, + label, + prefixText: prefix, + suffixText: suffix, + icon: icon, + ), cursorWidth: 1.3, style: const TextStyle(fontSize: 14), ), diff --git a/APP/lib/widgets/tenants/popups/user_popup.dart b/APP/lib/widgets/tenants/popups/user_popup.dart index c261268c7..4965b7ebf 100644 --- a/APP/lib/widgets/tenants/popups/user_popup.dart +++ b/APP/lib/widgets/tenants/popups/user_popup.dart @@ -1,20 +1,19 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/user.dart'; import 'package:ogree_app/widgets/common/form_field.dart'; import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; class UserPopup extends StatefulWidget { - Function() parentCallback; - User? modifyUser; - UserPopup({super.key, required this.parentCallback, this.modifyUser}); + final Function() parentCallback; + final User? modifyUser; + const UserPopup({super.key, required this.parentCallback, this.modifyUser}); @override State createState() => _UserPopupState(); @@ -49,199 +48,132 @@ class _UserPopupState extends State with TickerProviderStateMixin { final localeMsg = AppLocalizations.of(context)!; _isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return FutureBuilder( - future: domainList == null ? getDomains() : null, - builder: (context, _) { - if (domainList == null) { - return const Center(child: CircularProgressIndicator()); - } - return Center( - child: Container( - width: 500, - margin: const EdgeInsets.symmetric(horizontal: 10), - decoration: PopupDecoration, - child: Padding( - padding: EdgeInsets.fromLTRB(_isSmallDisplay ? 20 : 40, 8, - _isSmallDisplay ? 20 : 40, 15), - child: Material( - color: Colors.white, - child: Form( - key: _formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - TabBar( - tabAlignment: TabAlignment.center, - controller: _tabController, - labelStyle: TextStyle( - fontSize: 15, - fontFamily: GoogleFonts.inter().fontFamily), - unselectedLabelStyle: TextStyle( - fontSize: 15, - fontFamily: GoogleFonts.inter().fontFamily), - isScrollable: true, - indicatorSize: TabBarIndicatorSize.label, - tabs: _isEdit - ? [ - Tab( - text: localeMsg.modifyUser, - ), - ] - : [ - Tab( - text: localeMsg.createUser, - ), - Tab( - text: localeMsg.createBulkFile, - ), - ], + future: domainList == null ? getDomains() : null, + builder: (context, _) { + if (domainList == null) { + return const Center(child: CircularProgressIndicator()); + } + return Center( + child: Container( + width: 500, + margin: const EdgeInsets.symmetric(horizontal: 10), + decoration: PopupDecoration, + child: Padding( + padding: EdgeInsets.fromLTRB( + _isSmallDisplay ? 20 : 40, + 8, + _isSmallDisplay ? 20 : 40, + 15, + ), + child: Material( + color: Colors.white, + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TabBar( + tabAlignment: TabAlignment.center, + controller: _tabController, + labelStyle: TextStyle( + fontSize: 15, + fontFamily: GoogleFonts.inter().fontFamily, ), - SizedBox( - height: 320, - child: Padding( - padding: const EdgeInsets.only(top: 16.0), - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - controller: _tabController, - children: _isEdit - ? [ - getUserView(localeMsg), - ] - : [ - getUserView(localeMsg), - getBulkFileView(localeMsg), - ], - ), + unselectedLabelStyle: TextStyle( + fontSize: 15, + fontFamily: GoogleFonts.inter().fontFamily, + ), + isScrollable: true, + indicatorSize: TabBarIndicatorSize.label, + tabs: _isEdit + ? [ + Tab( + text: localeMsg.modifyUser, + ), + ] + : [ + Tab( + text: localeMsg.createUser, + ), + Tab( + text: localeMsg.createBulkFile, + ), + ], + ), + SizedBox( + height: 320, + child: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: TabBarView( + physics: const NeverScrollableScrollPhysics(), + controller: _tabController, + children: _isEdit + ? [ + getUserView(localeMsg), + ] + : [ + getUserView(localeMsg), + getBulkFileView(localeMsg), + ], ), ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.cancel), - icon: const Icon( - Icons.cancel_outlined, - size: 16, - ), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, ), - const SizedBox(width: 15), - ElevatedButton.icon( - onPressed: () async { - final messenger = - ScaffoldMessenger.of(context); - if (_tabController.index == 1) { - if (_loadedFile == null) { - showSnackBar( - messenger, localeMsg.mustSelectJSON); - } else if (_loadFileResult != null) { - widget.parentCallback(); - Navigator.of(context).pop(); - } else { - var result = await createBulkFile( - _loadedFile!.bytes!, "users"); - switch (result) { - case Success(value: final value): - setState(() { - _loadFileResult = value - .replaceAll("},", "},\n> ") - .replaceFirst("{", "> "); - }); - case Failure( - exception: final exception - ): - showSnackBar( - messenger, exception.toString(), - isError: true); - } - } - } else { - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - try { - Map roles = - getRolesMap(); - setState(() { - _isLoading = true; - }); - - Result response; - if (_isEdit) { - response = await modifyUser( - widget.modifyUser!.id!, roles); - } else { - response = await createUser(User( - name: _userName!, - email: _userEmail!, - password: _userPassword!, - roles: roles)); - } - - switch (response) { - case Success(): - widget.parentCallback(); - showSnackBar( - messenger, - _isEdit - ? localeMsg.modifyOK - : localeMsg.createOK, - isSuccess: true); - if (context.mounted) { - Navigator.of(context).pop(); - } - case Failure( - exception: final exception - ): - setState(() { - _isLoading = false; - }); - showSnackBar( - messenger, exception.toString(), - isError: true); - } - } catch (e) { - showSnackBar(messenger, e.toString(), - isError: true); - return; - } - } - } - }, - label: Text(_isEdit - ? localeMsg.modify - : (_loadFileResult == null - ? localeMsg.create - : "OK")), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, size: 16)) - ], - ) - ], - ), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.cancel), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + const SizedBox(width: 15), + ElevatedButton.icon( + onPressed: () => userAction(localeMsg), + label: Text( + _isEdit + ? localeMsg.modify + : (_loadFileResult == null + ? localeMsg.create + : "OK"), + ), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : const Icon(Icons.check_circle, size: 16), + ), + ], + ), + ], ), ), ), ), - ); - }); + ), + ); + }, + ); } - getDomains() async { + Future getDomains() async { final messenger = ScaffoldMessenger.of(context); - var result = await fetchObjectsTree( - namespace: Namespace.Organisational, isTenantMode: true); + final result = await fetchObjectsTree( + namespace: Namespace.Organisational, + isTenantMode: true, + ); switch (result) { case Success(value: final listValue): domainList = listValue[0] @@ -249,7 +181,7 @@ class _UserPopupState extends State with TickerProviderStateMixin { .reduce((value, element) => List.from(value + element)); case Failure(exception: final exception): showSnackBar(messenger, exception.toString(), isError: true); - if (context.mounted) Navigator.pop(context); + if (mounted) Navigator.pop(context); return; } @@ -260,7 +192,7 @@ class _UserPopupState extends State with TickerProviderStateMixin { } } else { domainList!.add(allDomainsConvert); - var roles = widget.modifyUser!.roles; + final roles = widget.modifyUser!.roles; for (var i = 0; i < roles.length; i++) { selectedDomain.add(roles.keys.elementAt(i)); selectedRole.add(roles.values.elementAt(i).capitalize()); @@ -270,7 +202,7 @@ class _UserPopupState extends State with TickerProviderStateMixin { } Map getRolesMap() { - Map roles = {}; + final Map roles = {}; for (var i = 0; i < selectedDomain.length; i++) { if (roles.containsKey(selectedDomain[i])) { throw Exception(AppLocalizations.of(context)!.onlyOneRoleDomain); @@ -280,28 +212,31 @@ class _UserPopupState extends State with TickerProviderStateMixin { return roles; } - getUserView(AppLocalizations localeMsg) { + ListView getUserView(AppLocalizations localeMsg) { return ListView( padding: EdgeInsets.zero, children: [ CustomFormField( - save: (newValue) => _userName = newValue, - label: "Name", - icon: Icons.person, - initialValue: _isEdit ? widget.modifyUser!.name : null), + save: (newValue) => _userName = newValue, + label: "Name", + icon: Icons.person, + initialValue: _isEdit ? widget.modifyUser!.name : null, + ), CustomFormField( - save: (newValue) => _userEmail = newValue, - label: "Email", - icon: Icons.alternate_email, - initialValue: _isEdit ? widget.modifyUser!.email : null, - isReadOnly: _isEdit), + save: (newValue) => _userEmail = newValue, + label: "Email", + icon: Icons.alternate_email, + initialValue: _isEdit ? widget.modifyUser!.email : null, + isReadOnly: _isEdit, + ), CustomFormField( - save: (newValue) => _userPassword = newValue, - label: localeMsg.password, - icon: Icons.lock, - initialValue: _isEdit ? widget.modifyUser!.password : null, - isObscure: true, - isReadOnly: _isEdit), + save: (newValue) => _userPassword = newValue, + label: localeMsg.password, + icon: Icons.lock, + initialValue: _isEdit ? widget.modifyUser!.password : null, + isObscure: true, + isReadOnly: _isEdit, + ), const Padding( padding: EdgeInsets.only(top: 8.0, bottom: 10, left: 12), child: Text("Permissions"), @@ -315,58 +250,65 @@ class _UserPopupState extends State with TickerProviderStateMixin { child: Align( alignment: Alignment.bottomLeft, child: TextButton.icon( - onPressed: () => setState(() { - domainRoleRows - .add(addDomainRoleRow(domainRoleRows.length)); - }), - icon: const Icon(Icons.add), - label: Text(localeMsg.domain)), + onPressed: () => setState(() { + domainRoleRows.add(addDomainRoleRow(domainRoleRows.length)); + }), + icon: const Icon(Icons.add), + label: Text(localeMsg.domain), + ), ), - ) + ), ], ); } - getBulkFileView(AppLocalizations localeMsg) { + Center getBulkFileView(AppLocalizations localeMsg) { return Center( - child: ListView(shrinkWrap: true, children: [ - _loadFileResult == null - ? Align( - child: ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles(withData: true); - if (result != null) { - setState(() { - _loadedFile = result.files.single; - }); - } - }, - icon: const Icon(Icons.download), - label: Text(localeMsg.selectJSON)), - ) - : Container(), - _loadedFile != null - ? Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), - child: Align( - child: Text(localeMsg.fileLoaded(_loadedFile!.name)), - ), - ) - : Container(), - _loadFileResult != null - ? Container( - color: Colors.black, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Result:\n$_loadFileResult', - style: const TextStyle(color: Colors.white), - ), + child: ListView( + shrinkWrap: true, + children: [ + if (_loadFileResult == null) + Align( + child: ElevatedButton.icon( + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles(withData: true); + if (result != null) { + setState(() { + _loadedFile = result.files.single; + }); + } + }, + icon: const Icon(Icons.download), + label: Text(localeMsg.selectJSON), + ), + ) + else + Container(), + if (_loadedFile != null) + Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), + child: Align( + child: Text(localeMsg.fileLoaded(_loadedFile!.name)), + ), + ) + else + Container(), + if (_loadFileResult != null) + Container( + color: Colors.black, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Result:\n$_loadFileResult', + style: const TextStyle(color: Colors.white), ), - ) - : Container(), - ]), + ), + ) + else + Container(), + ], + ), ); } @@ -383,103 +325,195 @@ class _UserPopupState extends State with TickerProviderStateMixin { rebuildDomainRole(); } - addDomainRoleRow(int rowIdx, {bool useDefaultValue = true}) { + StatefulBuilder addDomainRoleRow(int rowIdx, {bool useDefaultValue = true}) { if (useDefaultValue) { selectedDomain.add(domainList!.first); selectedRole.add(roleList.first); } - return StatefulBuilder(builder: (context, localSetState) { - return Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Flexible( - flex: 3, - child: DecoratedBox( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 248, 247, 247), - borderRadius: BorderRadius.circular(12.0), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: DropdownButton( + return StatefulBuilder( + builder: (context, localSetState) { + return Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Row( + children: [ + Flexible( + flex: 3, + child: DecoratedBox( + decoration: BoxDecoration( + color: const Color.fromARGB(255, 248, 247, 247), borderRadius: BorderRadius.circular(12.0), - underline: Container(), - style: const TextStyle(fontSize: 14, color: Colors.black), - isExpanded: true, - value: selectedDomain[rowIdx], - items: domainList! - .map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (String? value) { - localSetState(() { - selectedDomain[rowIdx] = value!; - }); - }, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: DropdownButton( + borderRadius: BorderRadius.circular(12.0), + underline: Container(), + style: const TextStyle(fontSize: 14, color: Colors.black), + isExpanded: true, + value: selectedDomain[rowIdx], + items: domainList! + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? value) { + localSetState(() { + selectedDomain[rowIdx] = value!; + }); + }, + ), ), ), ), - ), - Padding( - padding: - EdgeInsets.symmetric(horizontal: _isSmallDisplay ? 0 : 16.0), - child: Icon( - Icons.arrow_forward, - color: Colors.blue.shade600, - ), - ), - Flexible( - flex: 2, - child: DecoratedBox( - decoration: BoxDecoration( - color: const Color.fromARGB(255, 248, 247, 247), - borderRadius: BorderRadius.circular(12.0), + Padding( + padding: EdgeInsets.symmetric( + horizontal: _isSmallDisplay ? 0 : 16.0), + child: Icon( + Icons.arrow_forward, + color: Colors.blue.shade600, ), - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: _isSmallDisplay ? 6 : 12.0), - child: DropdownButton( + ), + Flexible( + flex: 2, + child: DecoratedBox( + decoration: BoxDecoration( + color: const Color.fromARGB(255, 248, 247, 247), borderRadius: BorderRadius.circular(12.0), - underline: Container(), - style: const TextStyle(fontSize: 14, color: Colors.black), - isExpanded: true, - value: selectedRole[rowIdx], - items: - roleList.map>((String value) { - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (String? value) { - localSetState(() { - selectedRole[rowIdx] = value!; - }); - }, + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: _isSmallDisplay ? 6 : 12.0, + ), + child: DropdownButton( + borderRadius: BorderRadius.circular(12.0), + underline: Container(), + style: const TextStyle(fontSize: 14, color: Colors.black), + isExpanded: true, + value: selectedRole[rowIdx], + items: roleList + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? value) { + localSetState(() { + selectedRole[rowIdx] = value!; + }); + }, + ), ), ), ), - ), - rowIdx > 0 - ? IconButton( - padding: const EdgeInsets.all(4), - constraints: const BoxConstraints(), - iconSize: 14, - onPressed: () => - setState(() => removeDomainRoleRow(rowIdx)), - icon: Icon( - Icons.delete, - color: Colors.red.shade400, - )) - : const SizedBox(width: 22), - ], - ), - ); - }); + if (rowIdx > 0) + IconButton( + padding: const EdgeInsets.all(4), + constraints: const BoxConstraints(), + iconSize: 14, + onPressed: () => setState(() => removeDomainRoleRow(rowIdx)), + icon: Icon( + Icons.delete, + color: Colors.red.shade400, + ), + ) + else + const SizedBox(width: 22), + ], + ), + ); + }, + ); + } + + userAction(AppLocalizations localeMsg) async { + final messenger = ScaffoldMessenger.of(context); + if (_tabController.index == 1) { + if (_loadedFile == null) { + showSnackBar( + messenger, + localeMsg.mustSelectJSON, + ); + } else if (_loadFileResult != null) { + widget.parentCallback(); + Navigator.of(context).pop(); + } else { + final result = await createBulkFile( + _loadedFile!.bytes!, + "users", + ); + switch (result) { + case Success(value: final value): + setState(() { + _loadFileResult = + value.replaceAll("},", "},\n> ").replaceFirst("{", "> "); + }); + case Failure(exception: final exception): + showSnackBar( + messenger, + exception.toString(), + isError: true, + ); + } + } + } else { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + try { + final Map roles = getRolesMap(); + setState(() { + _isLoading = true; + }); + + Result response; + if (_isEdit) { + response = await modifyUser( + widget.modifyUser!.id!, + roles, + ); + } else { + response = await createUser( + User( + name: _userName!, + email: _userEmail!, + password: _userPassword!, + roles: roles, + ), + ); + } + + switch (response) { + case Success(): + widget.parentCallback(); + showSnackBar( + messenger, + _isEdit ? localeMsg.modifyOK : localeMsg.createOK, + isSuccess: true, + ); + if (mounted) { + Navigator.of(context).pop(); + } + case Failure(exception: final exception): + setState(() { + _isLoading = false; + }); + showSnackBar( + messenger, + exception.toString(), + isError: true, + ); + } + } catch (e) { + showSnackBar( + messenger, + e.toString(), + isError: true, + ); + return; + } + } + } } } diff --git a/APP/lib/widgets/tenants/tags_view.dart b/APP/lib/widgets/tenants/tags_view.dart index a88d1e58b..e4a59091b 100644 --- a/APP/lib/widgets/tenants/tags_view.dart +++ b/APP/lib/widgets/tenants/tags_view.dart @@ -1,4 +1,7 @@ +// ignore_for_file: constant_identifier_names + import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; @@ -7,7 +10,6 @@ import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/tag.dart'; import 'package:ogree_app/pages/results_page.dart'; import 'package:ogree_app/widgets/common/delete_dialog_popup.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/widgets/tenants/popups/tags_popup.dart'; enum TagSearchFields { Description, Slug, Color } @@ -41,209 +43,225 @@ class _TagsViewState extends State { final localeMsg = AppLocalizations.of(context)!; final isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return FutureBuilder( - future: _loadTags ? getTags() : null, - builder: (context, _) { - if (_tags == null) { - return const Center(child: CircularProgressIndicator()); - } - return Theme( - data: Theme.of(context).copyWith( - cardTheme: const CardTheme( - elevation: 0, - surfaceTintColor: Colors.white, - color: Colors.white), + future: _loadTags ? getTags() : null, + builder: (context, _) { + if (_tags == null) { + return const Center(child: CircularProgressIndicator()); + } + return Theme( + data: Theme.of(context).copyWith( + cardTheme: const CardTheme( + elevation: 0, + surfaceTintColor: Colors.white, + color: Colors.white, ), - child: SingleChildScrollView( - padding: const EdgeInsets.only(right: 16, top: 0), - child: PaginatedDataTable( - sortColumnIndex: 1, - sortAscending: sort, - checkboxHorizontalMargin: 0, - header: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - SizedBox( - height: isSmallDisplay ? 30 : 35, - width: isSmallDisplay ? 115 : 145, - child: DropdownButtonFormField( - isExpanded: true, - borderRadius: BorderRadius.circular(12.0), - decoration: GetFormInputDecoration( - isSmallDisplay, - null, - icon: Icons.search_rounded, - contentPadding: isSmallDisplay - ? const EdgeInsets.only( - top: 0, - bottom: 15, - left: 12, - right: 5, - ) - : const EdgeInsets.only( - top: 3.0, - bottom: 12.0, - left: 20.0, - right: 14.0, - ), - ), - value: _searchField, - items: TagSearchFields.values - .map>( - (TagSearchFields value) { - return DropdownMenuItem( - value: value, - child: Text( - value.name, - overflow: TextOverflow.ellipsis, + ), + child: SingleChildScrollView( + padding: const EdgeInsets.only(right: 16), + child: PaginatedDataTable( + sortColumnIndex: 1, + sortAscending: sort, + checkboxHorizontalMargin: 0, + header: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + SizedBox( + height: isSmallDisplay ? 30 : 35, + width: isSmallDisplay ? 115 : 145, + child: DropdownButtonFormField( + isExpanded: true, + borderRadius: BorderRadius.circular(12.0), + decoration: GetFormInputDecoration( + isSmallDisplay, + null, + icon: Icons.search_rounded, + contentPadding: isSmallDisplay + ? const EdgeInsets.only( + bottom: 15, + left: 12, + right: 5, + ) + : const EdgeInsets.only( + top: 3.0, + bottom: 12.0, + left: 20.0, + right: 14.0, + ), + ), + value: _searchField, + items: TagSearchFields.values + .map>( + (TagSearchFields value) { + return DropdownMenuItem( + value: value, + child: Text( + value.name, + overflow: TextOverflow.ellipsis, + ), + ); + }).toList(), + onChanged: (TagSearchFields? value) { + setState(() { + _searchField = value!; + }); + }, + ), + ), + const SizedBox(width: 8), + SizedBox( + width: 150, + child: TextFormField( + textAlignVertical: TextAlignVertical.center, + onChanged: (value) { + setState(() { + _tags = searchTags(value); + }); + }, + decoration: InputDecoration( + border: InputBorder.none, + isDense: true, + label: isSmallDisplay ? null : Text(localeMsg.search), + prefixIcon: isSmallDisplay + ? const Icon(Icons.search_rounded) + : null, + ), + ), + ), + ], + ), + actions: [ + Padding( + padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 4), + child: IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + splashRadius: isSmallDisplay ? 16 : 23, + onPressed: () => selectedTags.isNotEmpty + ? showCustomPopup( + context, + TagsPopup( + parentCallback: () { + setState(() { + _loadTags = true; + }); + }, + tagId: selectedTags.first.slug, + ), + isDismissible: true, + ) + : null, + icon: const Icon( + Icons.edit, + ), + ), + ), + Padding( + padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 8.0), + child: IconButton( + splashRadius: isSmallDisplay ? 16 : 23, + // iconSize: 14, + onPressed: () => selectedTags.isNotEmpty + ? showCustomPopup( + context, + DeleteDialog( + objName: selectedTags.map((e) { + return e.slug; + }).toList(), + objType: "tags", + parentCallback: () { + setState(() { + _loadTags = true; + }); + }, ), - ); - }).toList(), - onChanged: (TagSearchFields? value) { + isDismissible: true, + ) + : null, + icon: Icon( + Icons.delete, + color: Colors.red.shade900, + ), + ), + ), + if (isSmallDisplay) + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + splashRadius: 16, + onPressed: () => showCustomPopup( + context, + TagsPopup( + parentCallback: () { setState(() { - _searchField = value!; + _loadTags = true; }); }, ), ), - const SizedBox(width: 8), - SizedBox( - width: 150, - child: TextFormField( - textAlignVertical: TextAlignVertical.center, - onChanged: (value) { - setState(() { - _tags = searchTags(value); - }); - }, - decoration: InputDecoration( - border: InputBorder.none, - isDense: true, - label: - isSmallDisplay ? null : Text(localeMsg.search), - prefixIcon: isSmallDisplay - ? const Icon(Icons.search_rounded) - : null, - )), + icon: Icon( + Icons.add, + color: Colors.blue.shade900, ), - ], - ), - actions: [ + ) + else Padding( - padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 4), - child: IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - splashRadius: isSmallDisplay ? 16 : 23, - onPressed: () => selectedTags.isNotEmpty - ? showCustomPopup( - context, - TagsPopup( - parentCallback: () { - setState(() { - _loadTags = true; - }); - }, - tagId: selectedTags.first.slug, - ), - isDismissible: true) - : null, - icon: const Icon( - Icons.edit, - )), - ), - Padding( - padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 8.0), - child: IconButton( - splashRadius: isSmallDisplay ? 16 : 23, - // iconSize: 14, - onPressed: () => selectedTags.isNotEmpty - ? showCustomPopup( - context, - DeleteDialog( - objName: selectedTags.map((e) { - print(e); - return e.slug; - }).toList(), - objType: "tags", - parentCallback: () { - setState(() { - _loadTags = true; - }); - }, - ), - isDismissible: true) - : null, - icon: Icon( - Icons.delete, - color: Colors.red.shade900, - )), - ), - isSmallDisplay - ? IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - splashRadius: 16, - onPressed: () => showCustomPopup(context, - TagsPopup(parentCallback: () { + padding: const EdgeInsets.only(right: 6.0), + child: ElevatedButton.icon( + onPressed: () => showCustomPopup( + context, + TagsPopup( + parentCallback: () { setState(() { _loadTags = true; }); - })), - icon: Icon( - Icons.add, - color: Colors.blue.shade900, - ), - ) - : Padding( - padding: const EdgeInsets.only(right: 6.0), - child: ElevatedButton.icon( - onPressed: () => showCustomPopup(context, - TagsPopup(parentCallback: () { - setState(() { - _loadTags = true; - }); - })), - icon: const Icon(Icons.add, color: Colors.white), - label: Text("${localeMsg.create} Tag"), - ), + }, ), - ], - rowsPerPage: _tags!.isEmpty - ? 1 - : (_tags!.length >= 6 ? 6 : _tags!.length), - columns: [ - DataColumn( - label: Text( + ), + icon: const Icon(Icons.add, color: Colors.white), + label: Text("${localeMsg.create} Tag"), + ), + ), + ], + rowsPerPage: + _tags!.isEmpty ? 1 : (_tags!.length >= 6 ? 6 : _tags!.length), + columns: [ + DataColumn( + label: Text( localeMsg.color, style: const TextStyle(fontWeight: FontWeight.w600), - )), - DataColumn( - label: const Text( - "Slug", - style: TextStyle(fontWeight: FontWeight.w600), - ), - onSort: (columnIndex, ascending) { - setState(() { - sort = !sort; - }); - onsortColum(columnIndex, ascending); - }), - const DataColumn( - label: Text( + ), + ), + DataColumn( + label: const Text( + "Slug", + style: TextStyle(fontWeight: FontWeight.w600), + ), + onSort: (columnIndex, ascending) { + setState(() { + sort = !sort; + }); + onsortColum(columnIndex, ascending); + }, + ), + const DataColumn( + label: Text( "Description", style: TextStyle(fontWeight: FontWeight.w600), - )), - const DataColumn( - label: Text( + ), + ), + const DataColumn( + label: Text( "Image", style: TextStyle(fontWeight: FontWeight.w600), - )) - ], - source: _DataSource(context, _tags!, onTagSelected), - ), + ), + ), + ], + source: _DataSource(context, _tags!, onTagSelected), ), - ); - }); + ), + ); + }, + ); } getTags() async { @@ -260,7 +278,7 @@ class _TagsViewState extends State { _loadTags = false; } - searchTags(String searchText) { + List searchTags(String searchText) { if (searchText.trim().isEmpty) { return _filterTags!.toList(); } @@ -275,8 +293,11 @@ class _TagsViewState extends State { .toList(); case TagSearchFields.Color: return _filterTags! - .where((element) => - element.color.toLowerCase().contains(searchText.toLowerCase())) + .where( + (element) => element.color + .toLowerCase() + .contains(searchText.toLowerCase()), + ) .toList(); } } @@ -335,9 +356,9 @@ class _DataSource extends DataTableSource { int get selectedRowCount => _selectedCount; List getChildren() { - List children = []; - for (var tag in tags) { - List row = []; + final List children = []; + for (final tag in tags) { + final List row = []; row.add(colorLabel(tag.color)); row.add(label(tag.slug, fontWeight: FontWeight.w500)); row.add(label(tag.description)); @@ -352,8 +373,9 @@ class _DataSource extends DataTableSource { Padding( padding: const EdgeInsets.all(8.0), child: Tooltip( - message: color, - child: Icon(Icons.circle, color: Color(int.parse("0xFF$color")))), + message: color, + child: Icon(Icons.circle, color: Color(int.parse("0xFF$color"))), + ), ), ); } diff --git a/APP/lib/widgets/tenants/tenant_card.dart b/APP/lib/widgets/tenants/tenant_card.dart index 52a136902..e50a7f4a2 100644 --- a/APP/lib/widgets/tenants/tenant_card.dart +++ b/APP/lib/widgets/tenants/tenant_card.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:ogree_app/common/api_backend.dart'; -import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; -import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/models/tenant.dart'; import 'package:ogree_app/pages/tenant_page.dart'; import 'package:ogree_app/widgets/common/delete_dialog_popup.dart'; @@ -15,7 +12,7 @@ class TenantCard extends StatelessWidget { final Tenant tenant; final Function parentCallback; const TenantCard( - {super.key, required this.tenant, required this.parentCallback}); + {super.key, required this.tenant, required this.parentCallback,}); @override Widget build(BuildContext context) { @@ -29,7 +26,7 @@ class TenantCard extends StatelessWidget { margin: const EdgeInsets.all(10), child: Padding( padding: const EdgeInsets.only( - right: 20.0, left: 20.0, top: 15, bottom: 13), + right: 20.0, left: 20.0, top: 15, bottom: 13,), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -57,10 +54,10 @@ class TenantCard extends StatelessWidget { UpdateTenantPopup( parentCallback: parentCallback, tenant: tenant, - )), + ),), icon: const Icon( Icons.edit, - )), + ),), ), const SizedBox(width: 8), CircleAvatar( @@ -72,12 +69,12 @@ class TenantCard extends StatelessWidget { onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => TenantPage( - userEmail: "admin", tenant: tenant), + userEmail: "admin", tenant: tenant,), ), ), icon: const Icon( Icons.search, - )), + ),), ), ], ), @@ -87,14 +84,14 @@ class TenantCard extends StatelessWidget { Row( children: [ Icon(Icons.circle, - color: getTenantStatusColor(tenant.status), size: 10), + color: getTenantStatusColor(tenant.status), size: 10,), const SizedBox(width: 6), SizedBox( width: 160, child: Text(tenant.name, overflow: TextOverflow.clip, style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 16)), + fontWeight: FontWeight.bold, fontSize: 16,),), ), ], ), @@ -118,21 +115,19 @@ class TenantCard extends StatelessWidget { padding: EdgeInsets.only(bottom: 2.0), child: Text("Web URL:"), ), - tenant.webUrl.isEmpty && tenant.webPort.isEmpty - ? Text(localeMsg.notCreated, + if (tenant.webUrl.isEmpty && tenant.webPort.isEmpty) Text(localeMsg.notCreated, style: - TextStyle(backgroundColor: Colors.grey.shade200)) - : InkWell( + TextStyle(backgroundColor: Colors.grey.shade200),) else InkWell( child: Text( "${tenant.webUrl}:${tenant.webPort}", style: TextStyle( backgroundColor: Colors.grey.shade200, color: Colors.blue, decoration: TextDecoration.underline, - decorationColor: Colors.blue), + decorationColor: Colors.blue,), ), onTap: () => launchUrl( - Uri.parse("${tenant.webUrl}:${tenant.webPort}")), + Uri.parse("${tenant.webUrl}:${tenant.webPort}"),), ), ], ), @@ -154,18 +149,18 @@ class TenantCard extends StatelessWidget { parentCallback: parentCallback, objType: "tenants", ), - isDismissible: true), + isDismissible: true,), icon: Icon( Icons.delete, color: Colors.red.shade900, - )), + ),), ), Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ IconButton( - constraints: BoxConstraints(), + constraints: const BoxConstraints(), padding: EdgeInsets.zero, onPressed: () => showCustomPopup( context, @@ -173,7 +168,7 @@ class TenantCard extends StatelessWidget { parentCallback: parentCallback, objName: tenant.name, isStart: false, - )), + ),), icon: Icon( Icons.stop_circle_sharp, color: Colors.orange.shade800, @@ -183,7 +178,7 @@ class TenantCard extends StatelessWidget { ), const SizedBox(width: 4), IconButton( - constraints: BoxConstraints(), + constraints: const BoxConstraints(), padding: EdgeInsets.zero, onPressed: () => showCustomPopup( context, @@ -191,7 +186,7 @@ class TenantCard extends StatelessWidget { parentCallback: parentCallback, objName: tenant.name, isStart: true, - )), + ),), icon: Icon( Icons.play_circle, color: Colors.blue.shade700, @@ -203,7 +198,7 @@ class TenantCard extends StatelessWidget { ), ), ], - ) + ), ], ), ), diff --git a/APP/lib/widgets/tenants/user_view.dart b/APP/lib/widgets/tenants/user_view.dart index 10b24e80a..6365adeb5 100644 --- a/APP/lib/widgets/tenants/user_view.dart +++ b/APP/lib/widgets/tenants/user_view.dart @@ -1,4 +1,7 @@ +// ignore_for_file: constant_identifier_names + import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/popup_dialog.dart'; @@ -7,9 +10,7 @@ import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/user.dart'; import 'package:ogree_app/pages/results_page.dart'; import 'package:ogree_app/widgets/common/delete_dialog_popup.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import 'popups/user_popup.dart'; +import 'package:ogree_app/widgets/tenants/popups/user_popup.dart'; enum UserSearchFields { Name, Email, Domain, Role } @@ -17,11 +18,12 @@ class UserView extends StatefulWidget { UserSearchFields searchField; String? searchText; Function? parentCallback; - UserView( - {super.key, - this.searchField = UserSearchFields.Name, - this.searchText, - this.parentCallback}); + UserView({ + super.key, + this.searchField = UserSearchFields.Name, + this.searchText, + this.parentCallback, + }); @override State createState() => _UserViewState(); } @@ -56,205 +58,220 @@ class _UserViewState extends State { final localeMsg = AppLocalizations.of(context)!; final isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return FutureBuilder( - future: _loadUsers ? getUsers() : null, - builder: (context, _) { - if (_users == null) { - return const Center(child: CircularProgressIndicator()); - } - return Theme( - data: Theme.of(context).copyWith( - cardTheme: const CardTheme( - elevation: 0, - surfaceTintColor: Colors.white, - color: Colors.white), + future: _loadUsers ? getUsers() : null, + builder: (context, _) { + if (_users == null) { + return const Center(child: CircularProgressIndicator()); + } + return Theme( + data: Theme.of(context).copyWith( + cardTheme: const CardTheme( + elevation: 0, + surfaceTintColor: Colors.white, + color: Colors.white, ), - child: SingleChildScrollView( - padding: const EdgeInsets.only(right: 16, top: 0), - child: PaginatedDataTable( - sortColumnIndex: 1, - sortAscending: sort, - checkboxHorizontalMargin: 0, - header: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - SizedBox( - height: isSmallDisplay ? 30 : 35, - width: isSmallDisplay ? 115 : 145, - child: DropdownButtonFormField( - borderRadius: BorderRadius.circular(12.0), - decoration: GetFormInputDecoration( - isSmallDisplay, - null, - icon: Icons.search_rounded, - contentPadding: isSmallDisplay - ? const EdgeInsets.only( - top: 0, - bottom: 15, - left: 12, - right: 5, - ) - : const EdgeInsets.only( - top: 3.0, - bottom: 12.0, - left: 20.0, - right: 14.0, - ), - ), - value: _searchField, - items: UserSearchFields.values - .map>( - (UserSearchFields value) { - return DropdownMenuItem( - value: value, - child: Text( - value.name, - overflow: TextOverflow.ellipsis, + ), + child: SingleChildScrollView( + padding: const EdgeInsets.only(right: 16), + child: PaginatedDataTable( + sortColumnIndex: 1, + sortAscending: sort, + checkboxHorizontalMargin: 0, + header: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + SizedBox( + height: isSmallDisplay ? 30 : 35, + width: isSmallDisplay ? 115 : 145, + child: DropdownButtonFormField( + borderRadius: BorderRadius.circular(12.0), + decoration: GetFormInputDecoration( + isSmallDisplay, + null, + icon: Icons.search_rounded, + contentPadding: isSmallDisplay + ? const EdgeInsets.only( + bottom: 15, + left: 12, + right: 5, + ) + : const EdgeInsets.only( + top: 3.0, + bottom: 12.0, + left: 20.0, + right: 14.0, + ), + ), + value: _searchField, + items: UserSearchFields.values + .map>( + (UserSearchFields value) { + return DropdownMenuItem( + value: value, + child: Text( + value.name, + overflow: TextOverflow.ellipsis, + ), + ); + }).toList(), + onChanged: (UserSearchFields? value) { + setState(() { + _searchField = value!; + }); + }, + ), + ), + const SizedBox(width: 8), + SizedBox( + width: 225, + child: TextFormField( + textAlignVertical: TextAlignVertical.center, + initialValue: widget.searchText, + onChanged: (value) { + setState(() { + _users = searchUsers(value); + }); + }, + decoration: InputDecoration( + border: InputBorder.none, + isDense: true, + label: isSmallDisplay ? null : Text(localeMsg.search), + prefixIcon: isSmallDisplay + ? const Icon(Icons.search_rounded) + : null, + ), + ), + ), + ], + ), + actions: [ + Padding( + padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 4), + child: IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + splashRadius: isSmallDisplay ? 16 : 23, + onPressed: () => selectedUsers.isNotEmpty + ? showCustomPopup( + context, + UserPopup( + parentCallback: () { + setState(() { + _loadUsers = true; + }); + }, + modifyUser: selectedUsers.first, ), - ); - }).toList(), - onChanged: (UserSearchFields? value) { + isDismissible: true, + ) + : null, + icon: const Icon( + Icons.edit, + ), + ), + ), + Padding( + padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 8.0), + child: IconButton( + splashRadius: isSmallDisplay ? 16 : 23, + // iconSize: 14, + onPressed: () => selectedUsers.isNotEmpty + ? showCustomPopup( + context, + DeleteDialog( + objName: selectedUsers.map((e) { + return e.id!; + }).toList(), + objType: "users", + parentCallback: () { + setState(() { + _loadUsers = true; + }); + }, + ), + isDismissible: true, + ) + : null, + icon: Icon( + Icons.delete, + color: Colors.red.shade900, + ), + ), + ), + if (isSmallDisplay) + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + splashRadius: 16, + onPressed: () => showCustomPopup( + context, + UserPopup( + parentCallback: () { setState(() { - _searchField = value!; + _loadUsers = true; }); }, ), ), - const SizedBox(width: 8), - SizedBox( - width: 225, - child: TextFormField( - textAlignVertical: TextAlignVertical.center, - initialValue: widget.searchText, - onChanged: (value) { - setState(() { - _users = searchUsers(value); - }); - }, - decoration: InputDecoration( - border: InputBorder.none, - isDense: true, - label: - isSmallDisplay ? null : Text(localeMsg.search), - prefixIcon: isSmallDisplay - ? const Icon(Icons.search_rounded) - : null, - )), + icon: Icon( + Icons.add, + color: Colors.blue.shade900, ), - ], - ), - actions: [ - Padding( - padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 4), - child: IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - splashRadius: isSmallDisplay ? 16 : 23, - onPressed: () => selectedUsers.isNotEmpty - ? showCustomPopup( - context, - UserPopup( - parentCallback: () { - setState(() { - _loadUsers = true; - }); - }, - modifyUser: selectedUsers.first, - ), - isDismissible: true) - : null, - icon: const Icon( - Icons.edit, - )), - ), + ) + else Padding( - padding: EdgeInsets.only(right: isSmallDisplay ? 0 : 8.0), - child: IconButton( - splashRadius: isSmallDisplay ? 16 : 23, - // iconSize: 14, - onPressed: () => selectedUsers.isNotEmpty - ? showCustomPopup( - context, - DeleteDialog( - objName: selectedUsers.map((e) { - print(e); - return e.id!; - }).toList(), - objType: "users", - parentCallback: () { - setState(() { - _loadUsers = true; - }); - }, - ), - isDismissible: true) - : null, - icon: Icon( - Icons.delete, - color: Colors.red.shade900, - )), - ), - isSmallDisplay - ? IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - splashRadius: 16, - onPressed: () => showCustomPopup(context, - UserPopup(parentCallback: () { + padding: const EdgeInsets.only(right: 6.0), + child: ElevatedButton.icon( + onPressed: () => showCustomPopup( + context, + UserPopup( + parentCallback: () { setState(() { _loadUsers = true; }); - })), - icon: Icon( - Icons.add, - color: Colors.blue.shade900, - ), - ) - : Padding( - padding: const EdgeInsets.only(right: 6.0), - child: ElevatedButton.icon( - onPressed: () => showCustomPopup(context, - UserPopup(parentCallback: () { - setState(() { - _loadUsers = true; - }); - })), - icon: const Icon(Icons.add, color: Colors.white), - label: - Text("${localeMsg.create} ${localeMsg.user}"), - ), + }, ), - ], - rowsPerPage: _users!.isEmpty - ? 1 - : (_users!.length >= 6 ? 6 : _users!.length), - columns: [ - const DataColumn( - label: Text( + ), + icon: const Icon(Icons.add, color: Colors.white), + label: Text("${localeMsg.create} ${localeMsg.user}"), + ), + ), + ], + rowsPerPage: _users!.isEmpty + ? 1 + : (_users!.length >= 6 ? 6 : _users!.length), + columns: [ + const DataColumn( + label: Text( "Name", style: TextStyle(fontWeight: FontWeight.w600), - )), - DataColumn( - label: const Text( - "Email", - style: TextStyle(fontWeight: FontWeight.w600), - ), - onSort: (columnIndex, ascending) { - setState(() { - sort = !sort; - }); - onsortColum(columnIndex, ascending); - }), - const DataColumn( - label: Text( + ), + ), + DataColumn( + label: const Text( + "Email", + style: TextStyle(fontWeight: FontWeight.w600), + ), + onSort: (columnIndex, ascending) { + setState(() { + sort = !sort; + }); + onsortColum(columnIndex, ascending); + }, + ), + const DataColumn( + label: Text( "Domains (roles)", style: TextStyle(fontWeight: FontWeight.w600), - )) - ], - source: _DataSource(context, _users!, onUserSelected), - ), + ), + ), + ], + source: _DataSource(context, _users!, onUserSelected), ), - ); - }); + ), + ); + }, + ); } getUsers() async { @@ -277,7 +294,7 @@ class _UserViewState extends State { _loadUsers = false; } - searchUsers(String searchText) { + List searchUsers(String searchText) { if (searchText.trim().isEmpty) { return _filterUsers!.toList(); } @@ -292,7 +309,7 @@ class _UserViewState extends State { .toList(); case UserSearchFields.Domain: return _filterUsers!.where((element) { - for (var domain in element.roles.keys) { + for (final domain in element.roles.keys) { if (domain.contains(searchText) || domain == allDomainsConvert) { return true; } @@ -301,7 +318,7 @@ class _UserViewState extends State { }).toList(); case UserSearchFields.Role: return _filterUsers!.where((element) { - for (var role in element.roles.values) { + for (final role in element.roles.values) { if (role.contains(searchText)) { return true; } @@ -365,13 +382,13 @@ class _DataSource extends DataTableSource { int get selectedRowCount => _selectedCount; List getChildren() { - List children = []; - for (var user in users) { - List row = []; + final List children = []; + for (final user in users) { + final List row = []; row.add(label(user.name == "null" ? "-" : user.name)); row.add(label(user.email, fontWeight: FontWeight.w500)); String domainStr = ""; - for (var domain in user.roles.keys) { + for (final domain in user.roles.keys) { domainStr = "$domainStr $domain (${user.roles[domain]});"; } row.add(label(domainStr)); diff --git a/APP/lib/widgets/tools/create_netbox_popup.dart b/APP/lib/widgets/tools/create_netbox_popup.dart index 0f002ab9f..50e282f43 100644 --- a/APP/lib/widgets/tools/create_netbox_popup.dart +++ b/APP/lib/widgets/tools/create_netbox_popup.dart @@ -1,20 +1,23 @@ import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/models/netbox.dart'; import 'package:ogree_app/widgets/common/form_field.dart'; //Create Nbox: Netbox or Nautobot class CreateNboxPopup extends StatefulWidget { - Function() parentCallback; - Tools tool; - CreateNboxPopup( - {super.key, required this.parentCallback, required this.tool}); + final Function() parentCallback; + final Tools tool; + const CreateNboxPopup({ + super.key, + required this.parentCallback, + required this.tool, + }); @override State createState() => _CreateNboxPopupState(); @@ -47,79 +50,88 @@ class _CreateNboxPopupState extends State { decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 30 : 40, 20, _isSmallDisplay ? 30 : 40, 15), + _isSmallDisplay ? 30 : 40, + 20, + _isSmallDisplay ? 30 : 40, + 15, + ), child: Form( key: _formKey, child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: ListView( - padding: EdgeInsets.zero, - children: [ - Center( - child: Text( - "${localeMsg.create} $toolName", - style: - Theme.of(context).textTheme.headlineMedium, - )), - const SizedBox(height: 20), - CustomFormField( - save: (newValue) => _userName = newValue, - label: localeMsg.toolUsername(toolName), - icon: Icons.person), - CustomFormField( - save: (newValue) => _userPassword = newValue, - label: localeMsg.toolPassword(toolName), - icon: Icons.lock), - CustomFormField( - save: (newValue) => _port = newValue, - label: localeMsg.toolPort(toolName), - initialValue: _port, - icon: Icons.numbers, - formatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(4), - ], - ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.cancel), - icon: const Icon( - Icons.cancel_outlined, - size: 16, + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: ListView( + padding: EdgeInsets.zero, + children: [ + Center( + child: Text( + "${localeMsg.create} $toolName", + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + const SizedBox(height: 20), + CustomFormField( + save: (newValue) => _userName = newValue, + label: localeMsg.toolUsername(toolName), + icon: Icons.person, + ), + CustomFormField( + save: (newValue) => _userPassword = newValue, + label: localeMsg.toolPassword(toolName), + icon: Icons.lock, + ), + CustomFormField( + save: (newValue) => _port = newValue, + label: localeMsg.toolPort(toolName), + initialValue: _port, + icon: Icons.numbers, + formatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(4), + ], + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, + ), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.cancel), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + const SizedBox(width: 15), + ElevatedButton.icon( + onPressed: () => submitCreateNbox(localeMsg), + label: Text(localeMsg.create), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, ), + ) + : const Icon( + Icons.check_circle, + size: 16, ), - const SizedBox(width: 15), - ElevatedButton.icon( - onPressed: () => - submitCreateNbox(localeMsg), - label: Text(localeMsg.create), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: - const EdgeInsets.all(2.0), - child: - const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, - size: 16)) - ], - ) - ], ), - ))), + ], + ), + ], + ), + ), + ), + ), ), ), ), @@ -144,7 +156,7 @@ class _CreateNboxPopupState extends State { case Success(): widget.parentCallback(); showSnackBar(messenger, "${localeMsg.createOK} 🥳", isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): setState(() { _isLoading = false; diff --git a/APP/lib/widgets/tools/create_opendcim_popup.dart b/APP/lib/widgets/tools/create_opendcim_popup.dart index c9c882f82..a6fad6613 100644 --- a/APP/lib/widgets/tools/create_opendcim_popup.dart +++ b/APP/lib/widgets/tools/create_opendcim_popup.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/api_backend.dart'; import 'package:ogree_app/common/definitions.dart'; import 'package:ogree_app/common/snackbar.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:ogree_app/common/theme.dart'; import 'package:ogree_app/widgets/common/form_field.dart'; class CreateOpenDcimPopup extends StatefulWidget { - Function() parentCallback; - CreateOpenDcimPopup({super.key, required this.parentCallback}); + final Function() parentCallback; + const CreateOpenDcimPopup({super.key, required this.parentCallback}); @override State createState() => _CreateOpenDcimPopupState(); @@ -34,83 +34,90 @@ class _CreateOpenDcimPopupState extends State { decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - _isSmallDisplay ? 30 : 40, 20, _isSmallDisplay ? 30 : 40, 15), + _isSmallDisplay ? 30 : 40, + 20, + _isSmallDisplay ? 30 : 40, + 15, + ), child: Form( key: _formKey, child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: ListView( - padding: EdgeInsets.zero, - //shrinkWrap: true, - children: [ - Center( - child: Text( - "${localeMsg.create} OpenDCIM", - style: - Theme.of(context).textTheme.headlineMedium, - )), - // const Divider(height: 35), - const SizedBox(height: 20), - CustomFormField( - save: (newValue) => _dcimPort = newValue, - label: "OpenDCIM port", - initialValue: _dcimPort, - icon: Icons.numbers, - formatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(4), - ], - ), - CustomFormField( - save: (newValue) => _adminerPort = newValue, - label: "Adminer port", - initialValue: _adminerPort, - icon: Icons.numbers, - formatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(4), - ], - ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.cancel), - icon: const Icon( - Icons.cancel_outlined, - size: 16, + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: ListView( + padding: EdgeInsets.zero, + //shrinkWrap: true, + children: [ + Center( + child: Text( + "${localeMsg.create} OpenDCIM", + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + // const Divider(height: 35), + const SizedBox(height: 20), + CustomFormField( + save: (newValue) => _dcimPort = newValue, + label: "OpenDCIM port", + initialValue: _dcimPort, + icon: Icons.numbers, + formatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(4), + ], + ), + CustomFormField( + save: (newValue) => _adminerPort = newValue, + label: "Adminer port", + initialValue: _adminerPort, + icon: Icons.numbers, + formatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(4), + ], + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, + ), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.cancel), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + const SizedBox(width: 15), + ElevatedButton.icon( + onPressed: () => submitCreateOpenDcim(localeMsg), + label: Text(localeMsg.create), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, ), + ) + : const Icon( + Icons.check_circle, + size: 16, ), - const SizedBox(width: 15), - ElevatedButton.icon( - onPressed: () => - submitCreateOpenDcim(localeMsg), - label: Text(localeMsg.create), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: - const EdgeInsets.all(2.0), - child: - const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, - size: 16)) - ], - ) - ], ), - ))), + ], + ), + ], + ), + ), + ), + ), ), ), ), @@ -125,12 +132,12 @@ class _CreateOpenDcimPopupState extends State { }); final messenger = ScaffoldMessenger.of(context); // Create dcim - var result = await createOpenDcim(_dcimPort!, _adminerPort!); + final result = await createOpenDcim(_dcimPort!, _adminerPort); switch (result) { case Success(): widget.parentCallback(); showSnackBar(messenger, "${localeMsg.createOK} 🥳", isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): setState(() { _isLoading = false; diff --git a/APP/lib/widgets/tools/download_tool_popup.dart b/APP/lib/widgets/tools/download_tool_popup.dart index 51c10b570..8300ea760 100644 --- a/APP/lib/widgets/tools/download_tool_popup.dart +++ b/APP/lib/widgets/tools/download_tool_popup.dart @@ -1,22 +1,22 @@ import 'dart:io'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; -import 'package:ogree_app/common/snackbar.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:http/http.dart' as http; +import 'package:ogree_app/common/snackbar.dart'; import 'package:ogree_app/common/theme.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:ogree_app/models/netbox.dart'; import 'package:ogree_app/widgets/select_objects/settings_view/tree_filter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:universal_html/html.dart' as html; -import 'package:http/http.dart' as http; enum ToolOS { windows, linux, macOS } // Currently used to download cli or unity class DownloadToolPopup extends StatefulWidget { - Tools tool; - DownloadToolPopup({super.key, required this.tool}); + final Tools tool; + const DownloadToolPopup({super.key, required this.tool}); @override State createState() => _DownloadCliPopupState(); @@ -38,96 +38,105 @@ class _DownloadCliPopupState extends State { decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - isSmallDisplay ? 30 : 40, 20, isSmallDisplay ? 30 : 40, 15), + isSmallDisplay ? 30 : 40, + 20, + isSmallDisplay ? 30 : 40, + 15, + ), child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: ListView( - padding: EdgeInsets.zero, - children: [ - Center( + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: ListView( + padding: EdgeInsets.zero, + children: [ + Center( + child: Text( + widget.tool == Tools.cli + ? localeMsg.downloadCliTitle + : localeMsg.downloadUnityTitle, + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(localeMsg.selectOS), + const SizedBox(width: 20), + SizedBox( + height: 35, + width: 165, + child: DropdownButtonFormField( + borderRadius: BorderRadius.circular(12.0), + decoration: GetFormInputDecoration( + false, + null, + icon: Icons.desktop_windows, + ), + value: _selectedOS, + items: ToolOS.values + .map>((ToolOS value) { + return DropdownMenuItem( + value: value, child: Text( - widget.tool == Tools.cli - ? localeMsg.downloadCliTitle - : localeMsg.downloadUnityTitle, - style: Theme.of(context).textTheme.headlineMedium, - )), - const SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(localeMsg.selectOS), - const SizedBox(width: 20), - SizedBox( - height: 35, - width: 165, - child: DropdownButtonFormField( - borderRadius: BorderRadius.circular(12.0), - decoration: GetFormInputDecoration( - false, - null, - icon: Icons.desktop_windows, - ), - value: _selectedOS, - items: ToolOS.values - .map>( - (ToolOS value) { - return DropdownMenuItem( - value: value, - child: Text( - value == ToolOS.macOS - ? value.name - : value.name.capitalize(), - overflow: TextOverflow.ellipsis, - ), - ); - }).toList(), - onChanged: (ToolOS? value) { - setState(() { - _selectedOS = value!; - }); - }, - ), + value == ToolOS.macOS + ? value.name + : value.name.capitalize(), + overflow: TextOverflow.ellipsis, ), - ], - ), - const SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.cancel), - icon: const Icon( - Icons.cancel_outlined, - size: 16, + ); + }).toList(), + onChanged: (ToolOS? value) { + setState(() { + _selectedOS = value!; + }); + }, + ), + ), + ], + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, + ), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.cancel), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + const SizedBox(width: 15), + ElevatedButton.icon( + onPressed: () => submitDownloadTool( + widget.tool, + localeMsg, + ), + label: Text(localeMsg.download), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, ), - ), - const SizedBox(width: 15), - ElevatedButton.icon( - onPressed: () => submitDownloadTool( - widget.tool, localeMsg), - label: Text(localeMsg.download), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: - const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.download, size: 16)) - ], - ) - ], + ) + : const Icon(Icons.download, size: 16), ), - ))), + ], + ), + ], + ), + ), + ), + ), ), ), ); @@ -140,12 +149,10 @@ class _DownloadCliPopupState extends State { switch (_selectedOS) { case ToolOS.windows: cliName = "$cliName.exe"; - break; case ToolOS.linux: break; case ToolOS.macOS: cliName = "$cliName.mac"; - break; } return (urlPath, cliName); } @@ -157,19 +164,17 @@ class _DownloadCliPopupState extends State { switch (_selectedOS) { case ToolOS.windows: cliName = "${cliName}_win.zip"; - break; case ToolOS.linux: cliName = "${cliName}_Linux.zip"; - break; case ToolOS.macOS: cliName = "${cliName}_macOS.zip"; - break; } return (urlPath, cliName); } submitDownloadTool(Tools tool, AppLocalizations localeMsg) async { - String urlPath, cliName; + String urlPath; + String cliName; if (tool == Tools.cli) { (urlPath, cliName) = getCliInfo(); } else { @@ -190,17 +195,20 @@ class _DownloadCliPopupState extends State { final response = await http.get(Uri.parse(urlPath + cliName)); navigator.pop(); if (response.statusCode >= 200 && response.statusCode < 300) { - var path = (await getApplicationDocumentsDirectory()).path; + final path = (await getApplicationDocumentsDirectory()).path; var fileName = '$path/$cliName'; var file = File(fileName); for (var i = 1; await file.exists(); i++) { fileName = '$path/$cliName ($i)'; file = File(fileName); } - file.writeAsBytes(response.bodyBytes, flush: true).then((value) => - showSnackBar(ScaffoldMessenger.of(context), + file.writeAsBytes(response.bodyBytes, flush: true).then( + (value) => showSnackBar( + messenger, "${localeMsg.fileSavedTo} $fileName", - copyTextAction: fileName)); + copyTextAction: fileName, + ), + ); } else { showSnackBar(messenger, localeMsg.unableDownload); } diff --git a/APP/lib/widgets/tools/tool_card.dart b/APP/lib/widgets/tools/tool_card.dart index af80d98fd..b7bfd4691 100644 --- a/APP/lib/widgets/tools/tool_card.dart +++ b/APP/lib/widgets/tools/tool_card.dart @@ -15,11 +15,12 @@ class ToolCard extends StatelessWidget { final Tools type; final DockerContainer container; final Function parentCallback; - const ToolCard( - {super.key, - required this.type, - required this.container, - required this.parentCallback}); + const ToolCard({ + super.key, + required this.type, + required this.container, + required this.parentCallback, + }); @override Widget build(BuildContext context) { @@ -33,7 +34,11 @@ class ToolCard extends StatelessWidget { margin: const EdgeInsets.all(10), child: Padding( padding: const EdgeInsets.only( - right: 20.0, left: 20.0, top: 15, bottom: 13), + right: 20.0, + left: 20.0, + top: 15, + bottom: 13, + ), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, @@ -52,29 +57,37 @@ class ToolCard extends StatelessWidget { : const Text(" NETBOX "), ), ), - type != Tools.netbox - ? Container() - : CircleAvatar( - radius: 13, - child: IconButton( - splashRadius: 18, - iconSize: 14, - padding: const EdgeInsets.all(2), - onPressed: () => showCustomPopup( - context, const ImportNetboxPopup()), - icon: const Icon( - Icons.upload, - )), + if (type != Tools.netbox) + Container() + else + CircleAvatar( + radius: 13, + child: IconButton( + splashRadius: 18, + iconSize: 14, + padding: const EdgeInsets.all(2), + onPressed: () => showCustomPopup( + context, + const ImportNetboxPopup(), + ), + icon: const Icon( + Icons.upload, ), + ), + ), ], ), const SizedBox(height: 1), SizedBox( width: 145, - child: Text(container.name, - overflow: TextOverflow.clip, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 16)), + child: Text( + container.name, + overflow: TextOverflow.clip, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -101,35 +114,38 @@ class ToolCard extends StatelessWidget { backgroundColor: Colors.red.shade100, radius: 13, child: IconButton( - splashRadius: 18, - iconSize: 14, - padding: const EdgeInsets.all(2), - onPressed: () => showCustomPopup( - context, - DeleteDialog( - objName: [container.name], - parentCallback: parentCallback, - objType: type.name, - ), - isDismissible: true), - icon: Icon( - Icons.delete, - color: Colors.red.shade900, - )), + splashRadius: 18, + iconSize: 14, + padding: const EdgeInsets.all(2), + onPressed: () => showCustomPopup( + context, + DeleteDialog( + objName: [container.name], + parentCallback: parentCallback, + objType: type.name, + ), + isDismissible: true, + ), + icon: Icon( + Icons.delete, + color: Colors.red.shade900, + ), + ), ), SizedBox( height: 26, width: 26, child: IconButton.filled( style: IconButton.styleFrom( - backgroundColor: Colors.blue.shade700), + backgroundColor: Colors.blue.shade700, + ), // splashColor: Colors.blue, padding: EdgeInsets.zero, onPressed: () { launchUrl(Uri.parse(container.ports)); }, iconSize: 16, - icon: Icon( + icon: const Icon( Icons.open_in_new_rounded, // color: Colors.blue.shade800, ), @@ -138,7 +154,7 @@ class ToolCard extends StatelessWidget { ), ], ), - ) + ), ], ), ), @@ -161,7 +177,7 @@ class _ImportNetboxPopupState extends State { @override Widget build(BuildContext context) { final localeMsg = AppLocalizations.of(context)!; - var isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); + final isSmallDisplay = IsSmallDisplay(MediaQuery.of(context).size.width); return Center( child: Container( width: 500, @@ -170,81 +186,100 @@ class _ImportNetboxPopupState extends State { decoration: PopupDecoration, child: Padding( padding: EdgeInsets.fromLTRB( - isSmallDisplay ? 30 : 40, 20, isSmallDisplay ? 30 : 40, 15), + isSmallDisplay ? 30 : 40, + 20, + isSmallDisplay ? 30 : 40, + 15, + ), child: ScaffoldMessenger( - child: Builder( - builder: (context) => Scaffold( - backgroundColor: Colors.white, - body: ListView(shrinkWrap: true, children: [ - Center( - child: Text( - localeMsg.importNetbox, - style: Theme.of(context).textTheme.headlineMedium, - )), - // const Divider(height: 35), - const SizedBox(height: 50), - Align( - child: ElevatedButton.icon( - onPressed: () async { - FilePickerResult? result = - await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ["sql"], - withData: true); - if (result != null) { - setState(() { - _loadedFile = result.files.single; - }); - } - }, - icon: const Icon(Icons.download), - label: Text(localeMsg.selectSQL)), + child: Builder( + builder: (context) => Scaffold( + backgroundColor: Colors.white, + body: ListView( + shrinkWrap: true, + children: [ + Center( + child: Text( + localeMsg.importNetbox, + style: Theme.of(context).textTheme.headlineMedium, + ), + ), + // const Divider(height: 35), + const SizedBox(height: 50), + Align( + child: ElevatedButton.icon( + onPressed: () async { + final FilePickerResult? result = + await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ["sql"], + withData: true, + ); + if (result != null) { + setState(() { + _loadedFile = result.files.single; + }); + } + }, + icon: const Icon(Icons.download), + label: Text(localeMsg.selectSQL), + ), + ), + if (_loadedFile != null) + Padding( + padding: const EdgeInsets.only( + top: 8.0, + bottom: 8.0, + ), + child: Align( + child: Text( + localeMsg.fileLoaded(_loadedFile!.name), + ), + ), + ) + else + Container(), + SizedBox(height: _loadedFile != null ? 27 : 57), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.blue.shade900, ), - _loadedFile != null - ? Padding( - padding: const EdgeInsets.only( - top: 8.0, bottom: 8.0), - child: Align( - child: Text(localeMsg - .fileLoaded(_loadedFile!.name)), + onPressed: () => Navigator.pop(context), + label: Text(localeMsg.cancel), + icon: const Icon( + Icons.cancel_outlined, + size: 16, + ), + ), + const SizedBox(width: 15), + ElevatedButton.icon( + onPressed: () => submitNetboxDump(localeMsg), + label: const Text("OK"), + icon: _isLoading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, ), ) - : Container(), - SizedBox(height: _loadedFile != null ? 27 : 57), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.blue.shade900), - onPressed: () => Navigator.pop(context), - label: Text(localeMsg.cancel), - icon: const Icon( - Icons.cancel_outlined, + : const Icon( + Icons.check_circle, size: 16, ), - ), - const SizedBox(width: 15), - ElevatedButton.icon( - onPressed: () => submitNetboxDump(localeMsg), - label: const Text("OK"), - icon: _isLoading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: - const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : const Icon(Icons.check_circle, - size: 16)) - ], - ) - ]), - ))), + ), + ], + ), + ], + ), + ), + ), + ), ), ), ); @@ -270,7 +305,7 @@ class _ImportNetboxPopupState extends State { switch (result) { case Success(): showSnackBar(messenger, localeMsg.importNetboxOK, isSuccess: true); - if (context.mounted) Navigator.of(context).pop(); + if (mounted) Navigator.of(context).pop(); case Failure(exception: final exception): setState(() { _isLoading = false; @@ -278,8 +313,11 @@ class _ImportNetboxPopupState extends State { showSnackBar(messenger, exception.toString(), isError: true); } } else { - showSnackBar(ScaffoldMessenger.of(context), localeMsg.mustSelectFile, - isError: true); + showSnackBar( + ScaffoldMessenger.of(context), + localeMsg.mustSelectFile, + isError: true, + ); } } } diff --git a/APP/macos/Podfile.lock b/APP/macos/Podfile.lock index 834954115..6a6deafa7 100644 --- a/APP/macos/Podfile.lock +++ b/APP/macos/Podfile.lock @@ -34,8 +34,8 @@ SPEC CHECKSUMS: flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c - url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 PODFILE CHECKSUM: eec3430f260cde219af51c43f1bcfccf35ab54d0 diff --git a/APP/macos/Runner.xcodeproj/project.pbxproj b/APP/macos/Runner.xcodeproj/project.pbxproj index df0c506d3..a9a08e3f4 100644 --- a/APP/macos/Runner.xcodeproj/project.pbxproj +++ b/APP/macos/Runner.xcodeproj/project.pbxproj @@ -203,7 +203,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { diff --git a/APP/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/APP/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a3892c0a3..5415a4fc4 100644 --- a/APP/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/APP/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { return true diff --git a/APP/pubspec.lock b/APP/pubspec.lock index 90d6a7dd3..0df0dbffb 100644 --- a/APP/pubspec.lock +++ b/APP/pubspec.lock @@ -5,26 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.7.0" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -61,10 +66,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" build_resolvers: dependency: transitive description: @@ -77,18 +82,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.12" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -101,10 +106,10 @@ packages: dependency: transitive description: name: built_value - sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb url: "https://pub.dev" source: hosted - version: "8.9.0" + version: "8.9.2" characters: dependency: transitive description: @@ -165,10 +170,10 @@ packages: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" csslib: dependency: transitive description: @@ -189,18 +194,18 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" dart_style: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" enum_to_string: dependency: transitive description: @@ -221,10 +226,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.3" file: dependency: transitive description: @@ -237,10 +242,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6" + sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.2.1" fixnum: dependency: transitive description: @@ -261,18 +266,18 @@ packages: dependency: "direct main" description: name: flex_color_picker - sha256: "0871edc170153cfc3de316d30625f40a85daecfa76ce541641f3cc0ec7757cbf" + sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e" url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.5.1" flex_seed_scheme: dependency: transitive description: name: flex_seed_scheme - sha256: "29c12aba221eb8a368a119685371381f8035011d18de5ba277ad11d7dfb8657f" + sha256: "86470c8dc470f55dd3e28a6d30e3253a1c176df32903263d7daeabfc0c77dbd4" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "3.2.0" flutter: dependency: "direct main" description: flutter @@ -290,10 +295,10 @@ packages: dependency: "direct main" description: name: flutter_fancy_tree_view - sha256: f3883801aa2beedce285ffb7eac9b9d59fb7dcdd3fd71703dea60f3e4e1f29f2 + sha256: e8ef261170be1d63fe56843baa2b501fc353196eadef51d848e882d8cbe10c31 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.6.0" flutter_inappwebview: dependency: "direct main" description: @@ -354,10 +359,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" flutter_localizations: dependency: "direct main" description: flutter @@ -367,10 +372,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.22" flutter_svg: dependency: transitive description: @@ -393,10 +398,10 @@ packages: dependency: transitive description: name: frontend_server_client - sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "4.0.0" glob: dependency: transitive description: @@ -409,18 +414,18 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.1" graphs: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" graphview: dependency: "direct main" description: @@ -441,10 +446,10 @@ packages: dependency: "direct main" description: name: http - sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -465,10 +470,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" io: dependency: transitive description: @@ -489,10 +494,34 @@ packages: dependency: transitive description: name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -509,38 +538,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" mockito: dependency: "direct dev" description: @@ -561,10 +598,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -577,26 +614,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.10" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -617,10 +654,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" petitparser: dependency: transitive description: @@ -633,10 +670,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -665,10 +702,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" screenshot: dependency: "direct main" description: @@ -689,10 +726,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -750,26 +787,26 @@ packages: dependency: transitive description: name: syncfusion_flutter_core - sha256: "4eed0d3ae50c16b5e8e4957f3c1917e9bd0315a08dfb49a104ca8fc10244bef3" + sha256: "7666506885ebc8f62bb928ad4588a73e20caaff2b2cf2b2b56f67d98f4113525" url: "https://pub.dev" source: hosted - version: "24.2.6" + version: "24.2.9" syncfusion_flutter_datepicker: dependency: "direct main" description: name: syncfusion_flutter_datepicker - sha256: "8ed5aa0217fafe1788d815766d98ece641d1cf067b47ca3d71da81e7605b0d5c" + sha256: c010440ccef2beecb988684af3557c57ade8ea300d1177536f01059e3a16ce5d url: "https://pub.dev" source: hosted - version: "24.2.3" + version: "24.2.9" syncfusion_localizations: dependency: "direct main" description: name: syncfusion_localizations - sha256: "621995c5573c0ef503aea42e24d26871704de86dc3efc843a83c5f92f9062064" + sha256: "874cbcb1e5a21a0762ccabf96315fe722876e761f3531eed2e47f3694ae6a6dd" url: "https://pub.dev" source: hosted - version: "24.2.6" + version: "24.2.9" term_glyph: dependency: transitive description: @@ -782,10 +819,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" timing: dependency: transitive description: @@ -822,66 +859,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.2.4" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" vector_graphics: dependency: transitive description: @@ -914,6 +951,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" watcher: dependency: transitive description: @@ -926,26 +971,34 @@ packages: dependency: transitive description: name: web - sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" win32: dependency: transitive description: name: win32 - sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "5.5.4" xdg_directories: dependency: transitive description: @@ -971,5 +1024,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.6 <4.0.0" - flutter: ">=3.16.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/APP/pubspec.yaml b/APP/pubspec.yaml index f3289ce19..f68635e38 100644 --- a/APP/pubspec.yaml +++ b/APP/pubspec.yaml @@ -40,7 +40,7 @@ dependencies: cupertino_icons: ^1.0.2 google_fonts: ^6.1.0 syncfusion_flutter_datepicker: ^24.2.3 - intl: ^0.18.0 + intl: ^0.19.0 syncfusion_localizations: ^24.2.6 flutter_fancy_tree_view: ^1.4.1 http: ^1.1.0 diff --git a/APP/test/api_test.dart b/APP/test/api_test.dart index c9b74e1fb..75cd725e5 100644 --- a/APP/test/api_test.dart +++ b/APP/test/api_test.dart @@ -1,5 +1,5 @@ -import 'package:http/http.dart' as http; import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:ogree_app/common/api_backend.dart'; @@ -9,7 +9,7 @@ import 'package:ogree_app/models/project.dart'; import 'api_test.mocks.dart'; const projectSample = - "{\"data\":{\"projects\":[{\"Id\":\"123xxx\",\"name\":\"test\",\"dateRange\":\"21/02/2023\",\"namespace\":\"Physique\",\"attributes\":[\"vendor\",\"heightUnit\",\"slot\",\"posXY\",\"weigth\"],\"objects\":[\"site.building.room.rack\",\"site.building.room.rack.device\"],\"permissions\":[\"user@email.com\",\"admin\"],\"authorLastUpdate\":\"Admin\",\"lastUpdate\":\"21/02/2023\",\"showAvg\":true,\"showSum\":true,\"isPublic\":false}]}}"; + '{"data":{"projects":[{"Id":"123xxx","name":"test","dateRange":"21/02/2023","namespace":"Physique","attributes":["vendor","heightUnit","slot","posXY","weigth"],"objects":["site.building.room.rack","site.building.room.rack.device"],"permissions":["user@email.com","admin"],"authorLastUpdate":"Admin","lastUpdate":"21/02/2023","showAvg":true,"showSum":true,"isPublic":false}]}}'; @GenerateMocks([http.Client]) void main() { @@ -21,11 +21,11 @@ void main() { // Use Mockito to return a successful response when it calls the // provided http.Client. when(mockClient.get(Uri.parse('$apiUrl/api/projects?user=user@email.com'), - headers: getHeader(token))) + headers: getHeader(token),),) .thenAnswer((_) async => http.Response(projectSample, 200)); expect(await fetchProjects("user@email.com", client: mockClient), - isA, Exception>>()); + isA, Exception>>(),); }); test('throws an exception if the http call completes with an error', @@ -35,11 +35,11 @@ void main() { // Use Mockito to return an unsuccessful response when it calls the // provided http.Client. when(mockClient.get(Uri.parse('$apiUrl/api/projects?user=user@email.com'), - headers: getHeader(token))) + headers: getHeader(token),),) .thenAnswer((_) async => http.Response('Not Found', 404)); expect(await fetchProjects("user@email.com", client: mockClient), - isA, Exception>>()); + isA, Exception>>(),); }); }); } diff --git a/APP/test/common.dart b/APP/test/common.dart index 9f966eb2f..8daa5be2b 100644 --- a/APP/test/common.dart +++ b/APP/test/common.dart @@ -6,9 +6,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; Future> getFrenchMessages() async { final String response = await rootBundle.loadString('lib/l10n/app_fr.arb'); - Map data = json.decode(response); - Map resp = {}; - for (String key in data.keys) { + final Map data = json.decode(response); + final Map resp = {}; + for (final String key in data.keys) { resp[key] = data[key].toString(); } return resp; @@ -16,7 +16,7 @@ Future> getFrenchMessages() async { class LocalizationsInjApp extends StatelessWidget { final Widget child; - const LocalizationsInjApp({Key? key, required this.child}) : super(key: key); + const LocalizationsInjApp({super.key, required this.child}); @override Widget build(BuildContext context) { @@ -34,7 +34,7 @@ class LocalizationsInjApp extends StatelessWidget { class LocalizationsInj extends StatelessWidget { final Widget child; - const LocalizationsInj({Key? key, required this.child}) : super(key: key); + const LocalizationsInj({super.key, required this.child}); @override Widget build(BuildContext context) { diff --git a/APP/test/login_page_test.dart b/APP/test/login_page_test.dart index e277ad2b4..279323e9f 100644 --- a/APP/test/login_page_test.dart +++ b/APP/test/login_page_test.dart @@ -26,12 +26,12 @@ void main() { final loginButton = find.textContaining('Se connecter'); final emailInput = find.ancestor( of: find.textContaining('mail'), - matching: find.byType(TextFormField)); + matching: find.byType(TextFormField),); final passwordInput = find.ancestor( - of: find.text('Mot de passe'), matching: find.byType(TextFormField)); + of: find.text('Mot de passe'), matching: find.byType(TextFormField),); final serverInput = find.ancestor( of: find.text('Choisir serveur'), - matching: find.byType(TextFormField)); + matching: find.byType(TextFormField),); await tester.ensureVisible(loginButton); await tester.pumpAndSettle(); diff --git a/APP/test/results_page_test.dart b/APP/test/results_page_test.dart index 35abff813..e73935ed5 100644 --- a/APP/test/results_page_test.dart +++ b/APP/test/results_page_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: prefer_const_literals_to_create_immutables + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ogree_app/common/definitions.dart'; @@ -8,32 +10,38 @@ import 'common.dart'; void main() { testWidgets('ResultPage loads objects', (tester) async { - await tester.pumpWidget(LocalizationsInjApp( - child: ResultsPage( + await tester.pumpWidget( + LocalizationsInjApp( + child: ResultsPage( dateRange: "", - selectedAttrs: [], + selectedAttrs: [], // should not be const selectedObjects: kDataSample["sitePI"]!, - namespace: Namespace.Test.name), - )); + namespace: Namespace.Test.name, + ), + ), + ); expect(find.text("Objects"), findsOneWidget); - for (var obj in kDataSample["sitePI"]!) { + for (final obj in kDataSample["sitePI"]!) { expect(find.text(obj), findsOneWidget); } }); testWidgets('ResultPage adds attributes upon selection', (tester) async { - await tester.pumpWidget(LocalizationsInjApp( - child: ResultsPage( + await tester.pumpWidget( + LocalizationsInjApp( + child: ResultsPage( dateRange: "", selectedAttrs: [], selectedObjects: kDataSample["siteNO"]!, - namespace: Namespace.Test.name), - )); + namespace: Namespace.Test.name, + ), + ), + ); await tester.tap(find.byIcon(Icons.add).last); await tester.pumpAndSettle(); - for (var attr in ["weight", "vendor"]) { + for (final attr in ["weight", "vendor"]) { await tester.tap(find.textContaining(attr)); await tester.pumpAndSettle(); expect(find.text(attr), findsNWidgets(2)); diff --git a/APP/test/select_objects_test.dart b/APP/test/select_objects_test.dart index e8890b50a..507c4d73e 100644 --- a/APP/test/select_objects_test.dart +++ b/APP/test/select_objects_test.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ogree_app/common/definitions.dart'; -import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; import 'package:ogree_app/widgets/select_objects/select_objects.dart'; +import 'package:ogree_app/widgets/select_objects/treeapp_controller.dart'; import 'common.dart'; @@ -14,22 +14,22 @@ void main() { dateRange: "", load: true, namespace: Namespace.Test, - ))); + ),),); final expandButton = find.text("Développer tout"); await tester.tap(expandButton); await tester.pumpAndSettle(); - for (var node in kDataSample["sitePI.B1.1.rack1"]!) { + for (final node in kDataSample["sitePI.B1.1.rack1"]!) { expect(find.text(node.substring(node.lastIndexOf(".") + 1)), - findsAtLeastNWidgets(1)); + findsAtLeastNWidgets(1),); } final collapseButton = find.text("Réduire tout"); await tester.tap(collapseButton); await tester.pumpAndSettle(); - for (var node in kDataSample["sitePI.B1.1.rack1"]!) { + for (final node in kDataSample["sitePI.B1.1.rack1"]!) { expect( - find.text(node.substring(node.lastIndexOf(".") + 1)), findsNothing); + find.text(node.substring(node.lastIndexOf(".") + 1)), findsNothing,); } }); @@ -39,7 +39,7 @@ void main() { dateRange: "", load: true, namespace: Namespace.Test, - ))); + ),),); final expandButton = find.text("Sélectionner tout"); await tester.tap(expandButton); @@ -53,7 +53,7 @@ void main() { 500.0, scrollable: find.ancestor( of: find.textContaining("siteNO.BB1"), - matching: find.byType(Scrollable)), + matching: find.byType(Scrollable),), ); await tester.tap(collapseButton); await tester.pumpAndSettle(); @@ -68,7 +68,7 @@ void main() { load: true, namespace: Namespace.Test, ), - ))); + ),),); const searchStr = "rack2.devB.devB-2"; final searchInput = @@ -79,7 +79,7 @@ void main() { final List parents = searchStr.split("."); parents.removeAt(0); - for (var name in parents) { + for (final name in parents) { expect(find.text(name), findsOneWidget); } expect(find.text("devC-2"), findsNothing); @@ -93,10 +93,10 @@ void main() { dateRange: "", load: true, namespace: Namespace.Test, - ))); + ),),); // all data is there - for (var site in kDataSample[kRootId]!) { + for (final site in kDataSample[kRootId]!) { expect(find.text(site), findsOneWidget); } @@ -104,7 +104,7 @@ void main() { final searchBldg = find.text("Building"); await tester.press(searchBldg, warnIfMissed: false); await tester.pumpAndSettle(); - for (var building in ['sitePA.A1', 'sitePA.A2', 'sitePI.B1']) { + for (final building in ['sitePA.A1', 'sitePA.A2', 'sitePI.B1']) { expect(find.text(building), findsOneWidget); } @@ -116,17 +116,17 @@ void main() { await tester.pumpAndSettle(); // check if tree is filtered - for (var site in kDataSample[kRootId]!) { + for (final site in kDataSample[kRootId]!) { expect(find.text(site), findsNothing); } final expandButton = find.text("Développer tout"); await tester.tap(expandButton); await tester.pumpAndSettle(); expect(find.text("sitePI.B1"), findsNWidgets(3)); - for (var obj in ["rack1", "rack2"]) { + for (final obj in ["rack1", "rack2"]) { expect(find.text(obj), findsOneWidget); } - for (var obj in ["devA", "devB", "devC", "devD"]) { + for (final obj in ["devA", "devB", "devC", "devD"]) { expect(find.text(obj), findsNWidgets(2)); } @@ -139,7 +139,7 @@ void main() { await tester.pumpAndSettle(); // check if tree is filtered - for (var site in kDataSample[kRootId]!) { + for (final site in kDataSample[kRootId]!) { expect(find.text(site), findsNothing); } await tester.ensureVisible(expandButton); @@ -147,10 +147,10 @@ void main() { await tester.tap(expandButton); await tester.pumpAndSettle(); expect(find.text("sitePI.B1"), findsNWidgets(3)); - for (var obj in ["devA", "devB", "devC", "devD"]) { + for (final obj in ["devA", "devB", "devC", "devD"]) { expect(find.text(obj), findsOneWidget); } - for (var obj in [ + for (final obj in [ "rack1", "B2", "NO", @@ -164,10 +164,10 @@ void main() { final clearButton = find.text("Effacer tout"); await tester.tap(clearButton); await tester.pumpAndSettle(); - for (var obj in ["devA", "devB", "devC", "devD"]) { + for (final obj in ["devA", "devB", "devC", "devD"]) { expect(find.text(obj), findsNothing); } - for (var obj in kDataSample[kRootId]!) { + for (final obj in kDataSample[kRootId]!) { expect(find.text(obj), findsOneWidget); } diff --git a/APP/test/select_page_test.dart b/APP/test/select_page_test.dart index eb290cd99..25a4da41c 100644 --- a/APP/test/select_page_test.dart +++ b/APP/test/select_page_test.dart @@ -9,7 +9,7 @@ void main() { await tester.pumpWidget(LocalizationsInjApp( child: SelectPage( userEmail: 'user@test.com', - ))); + ),),); // Date expect(find.text('Choisir les dates'), findsOneWidget); @@ -25,7 +25,7 @@ void main() { // Namespace expect(find.text("Physical"), findsNWidgets(1)); expect((tester.widget(find.text("Physical")) as Text).style!.color, - Colors.blue); + Colors.blue,); expect((tester.widget(find.text("Logical")) as Text).style!.color, null); // expect(find.text(defaultDate, skipOffstage: false), findsOneWidget); // await tester.tap(find.text("Logical"));