// Copyright 2021 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; import 'package:file_selector/file_selector.dart' as file_selector; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:json2yaml/json2yaml.dart'; import 'package:linting_tool/model/profile.dart'; import 'package:linting_tool/model/rule.dart'; import 'package:linting_tool/repository/hive_service.dart'; import 'package:linting_tool/repository/repository.dart'; import 'package:yaml/yaml.dart'; const _boxName = 'rules_profile'; class ProfilesStore extends ChangeNotifier { late final Repository repository; ProfilesStore(http.Client httpClient) { repository = Repository(httpClient); fetchSavedProfiles(); } bool _isLoading = true; bool get isLoading => _isLoading; List _savedProfiles = []; List get savedProfiles => _savedProfiles; String? _error; String? get error => _error; Future fetchSavedProfiles() async { if (!_isLoading) _isLoading = true; notifyListeners(); try { var profiles = await HiveService.getBoxes(_boxName); _savedProfiles = profiles; } on Exception catch (e) { log(e.toString()); } _isLoading = false; notifyListeners(); } Future addToNewProfile(RulesProfile profile) async { await HiveService.addBox(profile, _boxName); await Future.delayed(const Duration(milliseconds: 100), () async { await fetchSavedProfiles(); }); } Future addToExistingProfile(RulesProfile profile, Rule rule) async { // TODO(abd99): Consider refactoring to LinkedHashSet/SplayTreeSet to avoid // duplication automatically. // ref: https://github.com/flutter/samples/pull/870#discussion_r685666792 var rules = profile.rules; if (!rules.contains(rule)) { rules.add(rule); } RulesProfile newProfile = RulesProfile(name: profile.name, rules: rules); await HiveService.updateBox(profile, newProfile, _boxName); await Future.delayed(const Duration(milliseconds: 100), () async { await fetchSavedProfiles(); }); } Future updateProfile( RulesProfile oldProfile, RulesProfile newProfile) async { await HiveService.updateBox(oldProfile, newProfile, _boxName); await Future.delayed(const Duration(milliseconds: 100), () async { await fetchSavedProfiles(); }); } Future removeRuleFromProfile(RulesProfile profile, Rule rule) async { var newProfile = RulesProfile(name: profile.name, rules: profile.rules..remove(rule)); await updateProfile(profile, newProfile); } Future deleteProfile(RulesProfile profile) async { await HiveService.deleteBox(profile, _boxName); await Future.delayed(const Duration(milliseconds: 100), () async { await fetchSavedProfiles(); }); } Future exportProfileFile( RulesProfile profile, { RulesStyle rulesStyle = RulesStyle.booleanMap, }) async { _isLoading = true; notifyListeners(); var resultSaved = false; try { var templateFileData = await repository.getTemplateFile(); /// Fetch formatted data to create new YamlFile. String newYamlFile = _prepareYamlFile(profile, templateFileData, rulesStyle); resultSaved = await _saveFileToDisk(newYamlFile); } on SocketException catch (e) { log(e.toString()); _error = 'Check internet connection.'; resultSaved = false; } on Exception catch (e) { log(e.toString()); } _isLoading = false; notifyListeners(); return resultSaved; } Future _saveFileToDisk(String newYamlFile) async { const name = 'analysis_options.yaml'; /// Get file path using file picker. var savePath = await file_selector.getSavePath( suggestedName: name, ); final data = Uint8List.fromList(newYamlFile.codeUnits); final file = file_selector.XFile.fromData(data, name: name); /// Save file to disk if path was provided. if (savePath != null) { file.saveTo(savePath); return true; } var errorMessage = 'File path not found.'; _error = errorMessage; throw Exception(errorMessage); } String _prepareYamlFile( RulesProfile profile, YamlMap templateFile, RulesStyle rulesStyle) { var rules = profile.rules.map((e) => e.name).toList(); var rulesData = json.decode(json.encode(templateFile)) as Map; /// Add rules to existing template according to formatting style. if (rulesStyle == RulesStyle.booleanMap) { var rulesMap = Map.fromEntries( rules.map( (e) => MapEntry(e, true), ), ); rulesData.update('linter', (dynamic value) => {'rules': rulesMap}); } else { rulesData.update('linter', (dynamic value) => {'rules': rules}); } return json2yaml(rulesData, yamlStyle: YamlStyle.pubspecYaml); } } /// Formatting style for rules. enum RulesStyle { list, booleanMap, }