mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
[linting_tool] Implement saving rules to DB (#860)
This commit is contained in:
committed by
GitHub
parent
1818925286
commit
bbb8e342f1
@@ -3,6 +3,7 @@ include: package:flutter_lints/flutter.yaml
|
|||||||
analyzer:
|
analyzer:
|
||||||
exclude:
|
exclude:
|
||||||
- lib/model/rule.g.dart
|
- lib/model/rule.g.dart
|
||||||
|
- test/widget_test.mocks.dart
|
||||||
strong-mode:
|
strong-mode:
|
||||||
implicit-casts: false
|
implicit-casts: false
|
||||||
implicit-dynamic: false
|
implicit-dynamic: false
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:linting_tool/model/profiles_store.dart';
|
||||||
import 'package:linting_tool/model/rules_store.dart';
|
import 'package:linting_tool/model/rules_store.dart';
|
||||||
import 'package:linting_tool/theme/app_theme.dart';
|
import 'package:linting_tool/theme/app_theme.dart';
|
||||||
import 'package:linting_tool/widgets/adaptive_nav.dart';
|
import 'package:linting_tool/widgets/adaptive_nav.dart';
|
||||||
import 'package:linting_tool/routes.dart' as routes;
|
import 'package:linting_tool/routes.dart' as routes;
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class LintingTool extends StatefulWidget {
|
class LintingTool extends StatefulWidget {
|
||||||
const LintingTool({Key? key}) : super(key: key);
|
const LintingTool({Key? key}) : super(key: key);
|
||||||
@@ -21,8 +23,15 @@ class LintingTool extends StatefulWidget {
|
|||||||
class _LintingToolState extends State<LintingTool> {
|
class _LintingToolState extends State<LintingTool> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider<RuleStore>(
|
return MultiProvider(
|
||||||
create: (context) => RuleStore(),
|
providers: [
|
||||||
|
ChangeNotifierProvider<RuleStore>(
|
||||||
|
create: (context) => RuleStore(http.Client()),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<ProfilesStore>(
|
||||||
|
create: (context) => ProfilesStore(),
|
||||||
|
),
|
||||||
|
],
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
title: 'Flutter Linting Tool',
|
title: 'Flutter Linting Tool',
|
||||||
theme: AppTheme.buildReplyLightTheme(context),
|
theme: AppTheme.buildReplyLightTheme(context),
|
||||||
|
|||||||
@@ -3,8 +3,15 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:linting_tool/app.dart';
|
import 'package:linting_tool/app.dart';
|
||||||
|
import 'package:linting_tool/model/profile.dart';
|
||||||
|
import 'package:linting_tool/model/rule.dart';
|
||||||
|
|
||||||
void main() {
|
Future<void> main() async {
|
||||||
|
await Hive.initFlutter();
|
||||||
|
Hive.registerAdapter(RuleAdapter());
|
||||||
|
Hive.registerAdapter(RulesProfileAdapter());
|
||||||
|
await Hive.openLazyBox<RulesProfile>('rules_profile');
|
||||||
runApp(const LintingTool());
|
runApp(const LintingTool());
|
||||||
}
|
}
|
||||||
|
|||||||
26
experimental/linting_tool/lib/model/profile.dart
Normal file
26
experimental/linting_tool/lib/model/profile.dart
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// 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 'package:equatable/equatable.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:linting_tool/model/rule.dart';
|
||||||
|
|
||||||
|
part 'profile.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: 1)
|
||||||
|
class RulesProfile extends Equatable {
|
||||||
|
@HiveField(0)
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final List<Rule> rules;
|
||||||
|
|
||||||
|
const RulesProfile({
|
||||||
|
required this.name,
|
||||||
|
required this.rules,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [name];
|
||||||
|
}
|
||||||
44
experimental/linting_tool/lib/model/profile.g.dart
Normal file
44
experimental/linting_tool/lib/model/profile.g.dart
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'profile.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class RulesProfileAdapter extends TypeAdapter<RulesProfile> {
|
||||||
|
@override
|
||||||
|
final int typeId = 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
RulesProfile read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return RulesProfile(
|
||||||
|
name: fields[0] as String,
|
||||||
|
rules: (fields[1] as List).cast<Rule>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, RulesProfile obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(2)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.name)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.rules);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is RulesProfileAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
69
experimental/linting_tool/lib/model/profiles_store.dart
Normal file
69
experimental/linting_tool/lib/model/profiles_store.dart
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// 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:developer';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:linting_tool/model/profile.dart';
|
||||||
|
import 'package:linting_tool/model/rule.dart';
|
||||||
|
import 'package:linting_tool/repository/hive_service.dart';
|
||||||
|
|
||||||
|
const _boxName = 'rules_profile';
|
||||||
|
|
||||||
|
class ProfilesStore extends ChangeNotifier {
|
||||||
|
ProfilesStore() {
|
||||||
|
fetchSavedProfiles();
|
||||||
|
}
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
|
||||||
|
List<RulesProfile> _savedProfiles = [];
|
||||||
|
|
||||||
|
List<RulesProfile> get savedProfiles => _savedProfiles;
|
||||||
|
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
String? get error => _error;
|
||||||
|
|
||||||
|
Future<void> fetchSavedProfiles() async {
|
||||||
|
if (!_isLoading) _isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
try {
|
||||||
|
var profiles = await HiveService.getBoxes<RulesProfile>(_boxName);
|
||||||
|
_savedProfiles = profiles;
|
||||||
|
} on Exception catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
|
_isLoading = false;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addToNewProfile(RulesProfile profile) async {
|
||||||
|
await HiveService.addBox<RulesProfile>(profile, _boxName);
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100), () async {
|
||||||
|
await fetchSavedProfiles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addToExistingProfile(RulesProfile profile, Rule rule) async {
|
||||||
|
RulesProfile newProfile =
|
||||||
|
RulesProfile(name: profile.name, rules: profile.rules..add(rule));
|
||||||
|
|
||||||
|
await HiveService.updateBox<RulesProfile>(profile, newProfile, _boxName);
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100), () async {
|
||||||
|
await fetchSavedProfiles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteProfile(RulesProfile profile) async {
|
||||||
|
await HiveService.deleteBox<RulesProfile>(profile, _boxName);
|
||||||
|
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100), () async {
|
||||||
|
await fetchSavedProfiles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,18 +3,27 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'rule.g.dart';
|
part 'rule.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
@HiveType(typeId: 0)
|
||||||
class Rule extends Equatable {
|
class Rule extends Equatable {
|
||||||
|
@HiveField(0)
|
||||||
final String name;
|
final String name;
|
||||||
|
@HiveField(1)
|
||||||
final String description;
|
final String description;
|
||||||
|
@HiveField(2)
|
||||||
final String group;
|
final String group;
|
||||||
|
@HiveField(3)
|
||||||
final String maturity;
|
final String maturity;
|
||||||
|
@HiveField(4)
|
||||||
final List<String> incompatible;
|
final List<String> incompatible;
|
||||||
|
@HiveField(5)
|
||||||
final List<String> sets;
|
final List<String> sets;
|
||||||
|
@HiveField(6)
|
||||||
final String details;
|
final String details;
|
||||||
|
|
||||||
const Rule({
|
const Rule({
|
||||||
|
|||||||
@@ -2,6 +2,62 @@
|
|||||||
|
|
||||||
part of 'rule.dart';
|
part of 'rule.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// TypeAdapterGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
class RuleAdapter extends TypeAdapter<Rule> {
|
||||||
|
@override
|
||||||
|
final int typeId = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Rule read(BinaryReader reader) {
|
||||||
|
final numOfFields = reader.readByte();
|
||||||
|
final fields = <int, dynamic>{
|
||||||
|
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||||
|
};
|
||||||
|
return Rule(
|
||||||
|
name: fields[0] as String,
|
||||||
|
description: fields[1] as String,
|
||||||
|
group: fields[2] as String,
|
||||||
|
maturity: fields[3] as String,
|
||||||
|
incompatible: (fields[4] as List).cast<String>(),
|
||||||
|
sets: (fields[5] as List).cast<String>(),
|
||||||
|
details: fields[6] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(BinaryWriter writer, Rule obj) {
|
||||||
|
writer
|
||||||
|
..writeByte(7)
|
||||||
|
..writeByte(0)
|
||||||
|
..write(obj.name)
|
||||||
|
..writeByte(1)
|
||||||
|
..write(obj.description)
|
||||||
|
..writeByte(2)
|
||||||
|
..write(obj.group)
|
||||||
|
..writeByte(3)
|
||||||
|
..write(obj.maturity)
|
||||||
|
..writeByte(4)
|
||||||
|
..write(obj.incompatible)
|
||||||
|
..writeByte(5)
|
||||||
|
..write(obj.sets)
|
||||||
|
..writeByte(6)
|
||||||
|
..write(obj.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => typeId.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is RuleAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
|
}
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:linting_tool/model/rule.dart';
|
import 'package:linting_tool/model/rule.dart';
|
||||||
import 'package:linting_tool/repository/repository.dart';
|
import 'package:linting_tool/repository/repository.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class RuleStore extends ChangeNotifier {
|
class RuleStore extends ChangeNotifier {
|
||||||
RuleStore() {
|
late final Repository repository;
|
||||||
|
|
||||||
|
RuleStore(http.Client httpClient) {
|
||||||
|
repository = Repository(httpClient);
|
||||||
fetchRules();
|
fetchRules();
|
||||||
}
|
}
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
@@ -29,7 +33,7 @@ class RuleStore extends ChangeNotifier {
|
|||||||
if (!_isLoading) _isLoading = true;
|
if (!_isLoading) _isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
try {
|
try {
|
||||||
var rules = await Repository().getRulesList();
|
var rules = await repository.getRulesList();
|
||||||
_rules = rules;
|
_rules = rules;
|
||||||
} on SocketException catch (e) {
|
} on SocketException catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
|
|||||||
74
experimental/linting_tool/lib/pages/rules_page.dart
Normal file
74
experimental/linting_tool/lib/pages/rules_page.dart
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
import 'package:linting_tool/layout/adaptive.dart';
|
||||||
|
import 'package:linting_tool/model/profile.dart';
|
||||||
|
import 'package:linting_tool/widgets/saved_rule_tile.dart';
|
||||||
|
|
||||||
|
class RulesPage extends StatelessWidget {
|
||||||
|
final RulesProfile profile;
|
||||||
|
|
||||||
|
const RulesPage({
|
||||||
|
required this.profile,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isDesktop = isDisplayLarge(context);
|
||||||
|
final isTablet = isDisplayMedium(context);
|
||||||
|
var textTheme = Theme.of(context).textTheme;
|
||||||
|
final startPadding = isTablet
|
||||||
|
? 60.0
|
||||||
|
: isDesktop
|
||||||
|
? 120.0
|
||||||
|
: 4.0;
|
||||||
|
final endPadding = isTablet
|
||||||
|
? 60.0
|
||||||
|
: isDesktop
|
||||||
|
? 120.0
|
||||||
|
: 4.0;
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
profile.name,
|
||||||
|
style: textTheme.subtitle2!.copyWith(
|
||||||
|
color: textTheme.bodyText1!.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 80.0),
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.arrow_back_ios_new),
|
||||||
|
label: const Text('Back'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leadingWidth: 160.0,
|
||||||
|
toolbarHeight: 38.0,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
),
|
||||||
|
body: ListView.separated(
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
start: startPadding,
|
||||||
|
end: endPadding,
|
||||||
|
top: isDesktop ? 28 : 0,
|
||||||
|
bottom: isDesktop ? kToolbarHeight : 0,
|
||||||
|
),
|
||||||
|
itemCount: profile.rules.length,
|
||||||
|
cacheExtent: 5,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return SavedRuleTile(
|
||||||
|
rule: profile.rules[index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,124 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:linting_tool/layout/adaptive.dart';
|
||||||
|
import 'package:linting_tool/model/profiles_store.dart';
|
||||||
|
import 'package:linting_tool/pages/rules_page.dart';
|
||||||
|
import 'package:linting_tool/theme/colors.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class SavedLintsPage extends StatelessWidget {
|
class SavedLintsPage extends StatelessWidget {
|
||||||
const SavedLintsPage({Key? key}) : super(key: key);
|
const SavedLintsPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// TODO(abd99): Implement SavedLintsPage, showing a list of saved lint rules profiles.
|
return Consumer<ProfilesStore>(
|
||||||
return const Text('Saved Profiles');
|
builder: (context, profilesStore, child) {
|
||||||
|
if (profilesStore.isLoading) {
|
||||||
|
return const CircularProgressIndicator.adaptive();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profilesStore.isLoading) {
|
||||||
|
if (profilesStore.savedProfiles.isNotEmpty) {
|
||||||
|
final isDesktop = isDisplayLarge(context);
|
||||||
|
final isTablet = isDisplayMedium(context);
|
||||||
|
final startPadding = isTablet
|
||||||
|
? 60.0
|
||||||
|
: isDesktop
|
||||||
|
? 120.0
|
||||||
|
: 4.0;
|
||||||
|
final endPadding = isTablet
|
||||||
|
? 60.0
|
||||||
|
: isDesktop
|
||||||
|
? 120.0
|
||||||
|
: 4.0;
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
padding: EdgeInsetsDirectional.only(
|
||||||
|
start: startPadding,
|
||||||
|
end: endPadding,
|
||||||
|
top: isDesktop ? 28 : 0,
|
||||||
|
bottom: isDesktop ? kToolbarHeight : 0,
|
||||||
|
),
|
||||||
|
itemCount: profilesStore.savedProfiles.length,
|
||||||
|
cacheExtent: 5,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
var profile = profilesStore.savedProfiles[index];
|
||||||
|
return ListTile(
|
||||||
|
title: Text(
|
||||||
|
profile.name,
|
||||||
|
),
|
||||||
|
tileColor: AppColors.white50,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push<void>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => RulesPage(profile: profile),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
onPressed: () {
|
||||||
|
// TODO(abd99): Implement edit functionality.
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8.0,
|
||||||
|
),
|
||||||
|
PopupMenuButton<String>(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
onSelected: (value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'Export file':
|
||||||
|
// TODO(abd99): Implement exporting files.
|
||||||
|
break;
|
||||||
|
case 'Delete':
|
||||||
|
profilesStore.deleteProfile(profile);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
},
|
||||||
|
itemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
const PopupMenuItem(
|
||||||
|
child: Text('Export file'),
|
||||||
|
value: 'Export file',
|
||||||
|
),
|
||||||
|
const PopupMenuItem(
|
||||||
|
child: Text('Delete'),
|
||||||
|
value: 'Delete',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder: (context, index) => const SizedBox(height: 4),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(profilesStore.error ?? 'No saved profiles found.'),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16.0,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => profilesStore.fetchSavedProfiles(),
|
||||||
|
icon: const Icon(Icons.refresh),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ import 'package:linting_tool/model/rule.dart';
|
|||||||
|
|
||||||
class APIProvider {
|
class APIProvider {
|
||||||
final _baseURL = 'https://dart-lang.github.io/linter';
|
final _baseURL = 'https://dart-lang.github.io/linter';
|
||||||
|
final http.Client httpClient;
|
||||||
|
APIProvider(this.httpClient);
|
||||||
|
|
||||||
Future<List<Rule>> getRulesList() async {
|
Future<List<Rule>> getRulesList() async {
|
||||||
http.Response response =
|
http.Response response =
|
||||||
await http.get(Uri.parse('$_baseURL//lints/machine/rules.json'));
|
await httpClient.get(Uri.parse('$_baseURL//lints/machine/rules.json'));
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
List<Rule> rulesList = [];
|
List<Rule> rulesList = [];
|
||||||
|
|||||||
77
experimental/linting_tool/lib/repository/hive_service.dart
Normal file
77
experimental/linting_tool/lib/repository/hive_service.dart
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// 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 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
class HiveService {
|
||||||
|
static Future<bool> addBox<T>(T item, String boxName) async {
|
||||||
|
final openBox = await Hive.openLazyBox<T>(
|
||||||
|
boxName,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<T> existingProducts = await getBoxes(boxName);
|
||||||
|
|
||||||
|
if (!existingProducts.contains(item)) {
|
||||||
|
openBox.add(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future addBoxes<T>(List<T> items, String boxName) async {
|
||||||
|
final openBox = await Hive.openLazyBox<T>(
|
||||||
|
boxName,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<T> existingProducts = await getBoxes(boxName);
|
||||||
|
|
||||||
|
for (var item in items) {
|
||||||
|
if (!existingProducts.contains(item)) {
|
||||||
|
openBox.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future deleteBox<T>(T item, String boxName) async {
|
||||||
|
final openBox = await Hive.openLazyBox<T>(
|
||||||
|
boxName,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<T> boxes = await getBoxes(boxName);
|
||||||
|
|
||||||
|
for (var box in boxes) {
|
||||||
|
if (box == item) {
|
||||||
|
openBox.deleteAt(boxes.indexOf(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future updateBox<T>(T item, T newItem, String boxName) async {
|
||||||
|
final openBox = await Hive.openLazyBox<T>(
|
||||||
|
boxName,
|
||||||
|
);
|
||||||
|
|
||||||
|
List<T> boxes = await getBoxes(boxName);
|
||||||
|
|
||||||
|
for (var box in boxes) {
|
||||||
|
if (box == item) {
|
||||||
|
openBox.putAt(boxes.indexOf(item), newItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<T>> getBoxes<T>(String boxName, [String? query]) async {
|
||||||
|
List<T> boxList = [];
|
||||||
|
|
||||||
|
final openBox = await Hive.openLazyBox<T>(boxName);
|
||||||
|
|
||||||
|
int length = openBox.length;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
boxList.add(await openBox.getAt(i) as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
return boxList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,14 @@
|
|||||||
|
|
||||||
import 'package:linting_tool/model/rule.dart';
|
import 'package:linting_tool/model/rule.dart';
|
||||||
import 'package:linting_tool/repository/api_provider.dart';
|
import 'package:linting_tool/repository/api_provider.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class Repository {
|
class Repository {
|
||||||
final _apiProvider = APIProvider();
|
late final APIProvider _apiProvider;
|
||||||
|
|
||||||
|
Repository(http.Client httpClient) {
|
||||||
|
_apiProvider = APIProvider(httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<Rule>> getRulesList() => _apiProvider.getRulesList();
|
Future<List<Rule>> getRulesList() => _apiProvider.getRulesList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,19 @@ class _NavViewState extends State<_NavView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var textTheme = Theme.of(context).textTheme;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(
|
||||||
|
'Flutter Linting Tool',
|
||||||
|
style: textTheme.subtitle2!.copyWith(
|
||||||
|
color: textTheme.bodyText1!.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
toolbarHeight: 38.0,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
),
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:linting_tool/model/profile.dart';
|
||||||
|
import 'package:linting_tool/model/profiles_store.dart';
|
||||||
import 'package:linting_tool/model/rule.dart';
|
import 'package:linting_tool/model/rule.dart';
|
||||||
import 'package:linting_tool/theme/app_theme.dart';
|
import 'package:linting_tool/theme/app_theme.dart';
|
||||||
import 'package:linting_tool/theme/colors.dart';
|
import 'package:linting_tool/theme/colors.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class LintExpansionTile extends StatefulWidget {
|
class LintExpansionTile extends StatefulWidget {
|
||||||
final Rule rule;
|
final Rule rule;
|
||||||
@@ -127,8 +130,30 @@ class _LintExpansionTileState extends State<LintExpansionTile> {
|
|||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
child: const Text('Add to profile'),
|
child: const Text('Add to profile'),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
// TODO(abd99): Iplement adding to a profile.
|
ProfileType? destinationProfileType =
|
||||||
|
await showDialog<ProfileType>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return const _ProfileTypeDialog();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (destinationProfileType == ProfileType.newProfile) {
|
||||||
|
showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return _NewProfileDialog(rule: rule);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else if (destinationProfileType ==
|
||||||
|
ProfileType.existingProfile) {
|
||||||
|
showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return _ExistingProfileDialog(rule: rule);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -139,3 +164,145 @@ class _LintExpansionTileState extends State<LintExpansionTile> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ProfileType {
|
||||||
|
newProfile,
|
||||||
|
existingProfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfileTypeDialog extends StatelessWidget {
|
||||||
|
const _ProfileTypeDialog({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
actionsPadding: const EdgeInsets.only(
|
||||||
|
left: 16.0,
|
||||||
|
right: 16.0,
|
||||||
|
bottom: 16.0,
|
||||||
|
),
|
||||||
|
title: const Text('Select Profile Type'),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, ProfileType.existingProfile);
|
||||||
|
},
|
||||||
|
child: const Text('Existing Profile'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, ProfileType.newProfile);
|
||||||
|
},
|
||||||
|
child: const Text('Create new profile'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NewProfileDialog extends StatelessWidget {
|
||||||
|
final Rule rule;
|
||||||
|
const _NewProfileDialog({
|
||||||
|
required this.rule,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
String name = '';
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Create new lint profile'),
|
||||||
|
content: Form(
|
||||||
|
key: _formKey,
|
||||||
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text('Profile Name'),
|
||||||
|
TextFormField(
|
||||||
|
onChanged: (value) {
|
||||||
|
name = value;
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Name cannot be empty.';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actionsPadding: const EdgeInsets.all(16.0),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
var newProfile = RulesProfile(
|
||||||
|
name: name,
|
||||||
|
rules: [rule],
|
||||||
|
);
|
||||||
|
await Provider.of<ProfilesStore>(context, listen: false)
|
||||||
|
.addToNewProfile(newProfile);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('Save'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExistingProfileDialog extends StatelessWidget {
|
||||||
|
const _ExistingProfileDialog({
|
||||||
|
Key? key,
|
||||||
|
required this.rule,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Rule rule;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var profilesStore = Provider.of<ProfilesStore>(context);
|
||||||
|
var savedProfiles = profilesStore.savedProfiles;
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Select a lint profile'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: List.generate(
|
||||||
|
savedProfiles.length,
|
||||||
|
(index) => ListTile(
|
||||||
|
title: Text(savedProfiles[index].name),
|
||||||
|
onTap: () async {
|
||||||
|
await profilesStore.addToExistingProfile(
|
||||||
|
savedProfiles[index], rule);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actionsPadding: const EdgeInsets.all(16.0),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('Cancel'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
132
experimental/linting_tool/lib/widgets/saved_rule_tile.dart
Normal file
132
experimental/linting_tool/lib/widgets/saved_rule_tile.dart
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// 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 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:linting_tool/model/rule.dart';
|
||||||
|
import 'package:linting_tool/theme/app_theme.dart';
|
||||||
|
import 'package:linting_tool/theme/colors.dart';
|
||||||
|
|
||||||
|
class SavedRuleTile extends StatefulWidget {
|
||||||
|
final Rule rule;
|
||||||
|
const SavedRuleTile({
|
||||||
|
required this.rule,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_SavedRuleTileState createState() => _SavedRuleTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SavedRuleTileState extends State<SavedRuleTile> {
|
||||||
|
var isExpanded = false;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
var textTheme = theme.textTheme;
|
||||||
|
final rule = widget.rule;
|
||||||
|
final incompatibleString =
|
||||||
|
rule.incompatible.isNotEmpty ? rule.incompatible.join(', ') : 'none';
|
||||||
|
final setsString = rule.sets.isNotEmpty ? rule.sets.join(', ') : 'none';
|
||||||
|
|
||||||
|
// TODO(abd99): Add option to remove rule from profile.
|
||||||
|
// TODO(abd99): Add right click functionality.
|
||||||
|
return ExpansionTile(
|
||||||
|
collapsedBackgroundColor: AppColors.white50,
|
||||||
|
title: Text(
|
||||||
|
rule.name,
|
||||||
|
style: textTheme.subtitle1!.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
rule.description,
|
||||||
|
style: textTheme.caption!,
|
||||||
|
),
|
||||||
|
initiallyExpanded: isExpanded,
|
||||||
|
onExpansionChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
isExpanded = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
expandedAlignment: Alignment.centerLeft,
|
||||||
|
childrenPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
backgroundColor: AppColors.white50,
|
||||||
|
maintainState: true,
|
||||||
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Group:',
|
||||||
|
style: textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ' ${rule.group}',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Maturity:',
|
||||||
|
style: textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ' ${rule.maturity}',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Incompatible:',
|
||||||
|
style: textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ' $incompatibleString',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: 'Sets:',
|
||||||
|
style: textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: ' $setsString',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16.0,
|
||||||
|
),
|
||||||
|
MarkdownBody(
|
||||||
|
data: rule.details,
|
||||||
|
selectable: true,
|
||||||
|
styleSheet: AppTheme.buildMarkDownTheme(theme),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</customObject>
|
</customObject>
|
||||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="linting_tool" customModuleProvider="target">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
|
||||||
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
|
||||||
@@ -326,14 +326,15 @@
|
|||||||
</items>
|
</items>
|
||||||
<point key="canvasLocation" x="142" y="-258"/>
|
<point key="canvasLocation" x="142" y="-258"/>
|
||||||
</menu>
|
</menu>
|
||||||
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
|
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="linting_tool" customModuleProvider="target">
|
||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
|
||||||
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
|
||||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
|
||||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</view>
|
</view>
|
||||||
|
<point key="canvasLocation" x="7" y="-655"/>
|
||||||
</window>
|
</window>
|
||||||
</objects>
|
</objects>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -263,6 +263,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
hive:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
|
hive_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive_flutter
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
hive_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: hive_generator
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -354,6 +375,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
mockito:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mockito
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.13"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -492,6 +520,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ dependencies:
|
|||||||
equatable: ^2.0.3
|
equatable: ^2.0.3
|
||||||
flutter_markdown: ^0.6.2
|
flutter_markdown: ^0.6.2
|
||||||
google_fonts: ^2.1.0
|
google_fonts: ^2.1.0
|
||||||
|
hive: ^2.0.4
|
||||||
|
hive_flutter: ^1.1.0
|
||||||
http: ^0.13.3
|
http: ^0.13.3
|
||||||
json_annotation: ^4.0.1
|
json_annotation: ^4.0.1
|
||||||
|
mockito: ^5.0.13
|
||||||
provider: ^5.0.0
|
provider: ^5.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
@@ -23,6 +26,7 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.0.6
|
build_runner: ^2.0.6
|
||||||
flutter_lints: ^1.0.3
|
flutter_lints: ^1.0.3
|
||||||
|
hive_generator: ^1.1.0
|
||||||
json_serializable: ^4.1.4
|
json_serializable: ^4.1.4
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -1,20 +1,88 @@
|
|||||||
// This is a basic Flutter widget test.
|
// Copyright 2021 The Flutter team. All rights reserved.
|
||||||
//
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
// found in the LICENSE file.
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:linting_tool/app.dart';
|
import 'package:linting_tool/app.dart';
|
||||||
|
import 'package:linting_tool/model/profile.dart';
|
||||||
|
import 'package:linting_tool/model/profiles_store.dart';
|
||||||
|
import 'package:linting_tool/model/rule.dart';
|
||||||
|
import 'package:linting_tool/model/rules_store.dart';
|
||||||
import 'package:linting_tool/pages/default_lints_page.dart';
|
import 'package:linting_tool/pages/default_lints_page.dart';
|
||||||
import 'package:linting_tool/pages/home_page.dart';
|
import 'package:linting_tool/pages/home_page.dart';
|
||||||
import 'package:linting_tool/pages/saved_lints_page.dart';
|
import 'package:linting_tool/pages/saved_lints_page.dart';
|
||||||
|
import 'package:linting_tool/theme/app_theme.dart';
|
||||||
|
import 'package:linting_tool/widgets/adaptive_nav.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'widget_test.mocks.dart';
|
||||||
|
|
||||||
|
late MockClient _mockClient;
|
||||||
|
|
||||||
|
class _TestApp extends StatelessWidget {
|
||||||
|
const _TestApp({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider<RuleStore>(
|
||||||
|
create: (context) => RuleStore(_mockClient),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider<ProfilesStore>(
|
||||||
|
create: (context) => ProfilesStore(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
title: 'Flutter Linting Tool',
|
||||||
|
initialRoute: LintingTool.homeRoute,
|
||||||
|
theme: AppTheme.buildReplyLightTheme(context),
|
||||||
|
onGenerateRoute: (settings) {
|
||||||
|
switch (settings.name) {
|
||||||
|
case LintingTool.homeRoute:
|
||||||
|
return MaterialPageRoute<void>(
|
||||||
|
builder: (context) => const AdaptiveNav(),
|
||||||
|
settings: settings,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GenerateMocks([http.Client])
|
||||||
void main() {
|
void main() {
|
||||||
|
setUp(() async {
|
||||||
|
final tempDir = await Directory.systemTemp.createTemp();
|
||||||
|
Hive.init(tempDir.path);
|
||||||
|
Hive.registerAdapter(RuleAdapter());
|
||||||
|
Hive.registerAdapter(RulesProfileAdapter());
|
||||||
|
await Hive.openLazyBox<RulesProfile>('rules_profile');
|
||||||
|
_mockClient = MockClient();
|
||||||
|
});
|
||||||
testWidgets('NavigationRail smoke test', (tester) async {
|
testWidgets('NavigationRail smoke test', (tester) async {
|
||||||
await tester.pumpWidget(const LintingTool());
|
var responseBody =
|
||||||
|
'''[{"name": "always_use_package_imports","description": "Avoid relative imports for files in `lib/`.","group": "errors","maturity": "stable","incompatible": [],"sets": [],"details": "*DO* avoid relative imports for files in `lib/`.\n\nWhen mixing relative and absolute imports it's possible to create confusion\nwhere the same member gets imported in two different ways. One way to avoid\nthat is to ensure you consistently use absolute imports for files withing the\n`lib/` directory.\n\nThis is the opposite of 'prefer_relative_imports'.\nMight be used with 'avoid_relative_lib_imports' to avoid relative imports of\nfiles within `lib/` directory outside of it. (for example `test/`)\n\n**GOOD:**\n\n```dart\nimport 'package:foo/bar.dart';\n\nimport 'package:foo/baz.dart';\n\nimport 'package:foo/src/baz.dart';\n...\n```\n\n**BAD:**\n\n```dart\nimport 'baz.dart';\n\nimport 'src/bag.dart'\n\nimport '../lib/baz.dart';\n\n...\n```\n\n"}]''';
|
||||||
|
|
||||||
|
when(_mockClient.get(Uri.parse(
|
||||||
|
'https://dart-lang.github.io/linter//lints/machine/rules.json')))
|
||||||
|
.thenAnswer(
|
||||||
|
(_) async => http.Response(responseBody, 400),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(const _TestApp());
|
||||||
|
|
||||||
|
expect(find.byType(HomePage), findsOneWidget);
|
||||||
expect(find.byType(SavedLintsPage), findsNothing);
|
expect(find.byType(SavedLintsPage), findsNothing);
|
||||||
await tester.tap(find.text('Saved Profiles'));
|
await tester.tap(find.text('Saved Profiles'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|||||||
107
experimental/linting_tool/test/widget_test.mocks.dart
Normal file
107
experimental/linting_tool/test/widget_test.mocks.dart
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Mocks generated by Mockito 5.0.13 from annotations
|
||||||
|
// in linting_tool/test/widget_test.dart.
|
||||||
|
// Do not manually edit this file.
|
||||||
|
|
||||||
|
import 'dart:async' as _i5;
|
||||||
|
import 'dart:convert' as _i6;
|
||||||
|
import 'dart:typed_data' as _i7;
|
||||||
|
|
||||||
|
import 'package:http/src/base_request.dart' as _i8;
|
||||||
|
import 'package:http/src/client.dart' as _i4;
|
||||||
|
import 'package:http/src/response.dart' as _i2;
|
||||||
|
import 'package:http/src/streamed_response.dart' as _i3;
|
||||||
|
import 'package:mockito/mockito.dart' as _i1;
|
||||||
|
|
||||||
|
// ignore_for_file: avoid_redundant_argument_values
|
||||||
|
// ignore_for_file: avoid_setters_without_getters
|
||||||
|
// ignore_for_file: comment_references
|
||||||
|
// ignore_for_file: implementation_imports
|
||||||
|
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||||
|
// ignore_for_file: prefer_const_constructors
|
||||||
|
// ignore_for_file: unnecessary_parenthesis
|
||||||
|
|
||||||
|
class _FakeResponse extends _i1.Fake implements _i2.Response {}
|
||||||
|
|
||||||
|
class _FakeStreamedResponse extends _i1.Fake implements _i3.StreamedResponse {}
|
||||||
|
|
||||||
|
/// A class which mocks [Client].
|
||||||
|
///
|
||||||
|
/// See the documentation for Mockito's code generation for more information.
|
||||||
|
class MockClient extends _i1.Mock implements _i4.Client {
|
||||||
|
MockClient() {
|
||||||
|
_i1.throwOnMissingStub(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> head(Uri? url, {Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> get(Uri? url, {Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> post(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#post, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> put(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#put, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> patch(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#patch, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i2.Response> delete(Uri? url,
|
||||||
|
{Map<String, String>? headers,
|
||||||
|
Object? body,
|
||||||
|
_i6.Encoding? encoding}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#delete, [url],
|
||||||
|
{#headers: headers, #body: body, #encoding: encoding}),
|
||||||
|
returnValue: Future<_i2.Response>.value(_FakeResponse()))
|
||||||
|
as _i5.Future<_i2.Response>);
|
||||||
|
@override
|
||||||
|
_i5.Future<String> read(Uri? url, {Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<String>.value('')) as _i5.Future<String>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i7.Uint8List> readBytes(Uri? url,
|
||||||
|
{Map<String, String>? headers}) =>
|
||||||
|
(super.noSuchMethod(
|
||||||
|
Invocation.method(#readBytes, [url], {#headers: headers}),
|
||||||
|
returnValue: Future<_i7.Uint8List>.value(_i7.Uint8List(0)))
|
||||||
|
as _i5.Future<_i7.Uint8List>);
|
||||||
|
@override
|
||||||
|
_i5.Future<_i3.StreamedResponse> send(_i8.BaseRequest? request) =>
|
||||||
|
(super.noSuchMethod(Invocation.method(#send, [request]),
|
||||||
|
returnValue:
|
||||||
|
Future<_i3.StreamedResponse>.value(_FakeStreamedResponse()))
|
||||||
|
as _i5.Future<_i3.StreamedResponse>);
|
||||||
|
@override
|
||||||
|
void close() => super.noSuchMethod(Invocation.method(#close, []),
|
||||||
|
returnValueForMissingStub: null);
|
||||||
|
@override
|
||||||
|
String toString() => super.toString();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user