State Management + UI Updates

This commit is contained in:
Ankit Mahato
2023-03-04 22:23:49 +05:30
parent 3ce391e2d5
commit 76e5fc006c
11 changed files with 365 additions and 122 deletions

View File

@ -1,17 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/models.dart';
import '../providers/providers.dart';
import '../consts.dart';
class CollectionPane extends StatefulWidget { class CollectionPane extends ConsumerStatefulWidget {
const CollectionPane({ const CollectionPane({
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
State<CollectionPane> createState() => _CollectionPaneState(); ConsumerState<CollectionPane> createState() => _CollectionPaneState();
} }
class _CollectionPaneState extends State<CollectionPane> { class _CollectionPaneState extends ConsumerState<CollectionPane> {
int len = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -29,9 +31,11 @@ class _CollectionPaneState extends State<CollectionPane> {
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
setState(() { String newId =
len += 1; ref.read(collectionStateNotifierProvider.notifier).add();
}); ref
.read(activeItemIdStateProvider.notifier)
.update((state) => newId);
}, },
child: const Text('+ New'), child: const Text('+ New'),
), ),
@ -40,9 +44,9 @@ class _CollectionPaneState extends State<CollectionPane> {
const SizedBox( const SizedBox(
height: 8, height: 8,
), ),
Expanded( const Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
child: RequestList(l: len), child: RequestList(),
), ),
), ),
], ],
@ -51,21 +55,16 @@ class _CollectionPaneState extends State<CollectionPane> {
} }
} }
class RequestList extends StatefulWidget { class RequestList extends ConsumerStatefulWidget {
const RequestList({ const RequestList({
Key? key, Key? key,
required this.l,
}) : super(key: key); }) : super(key: key);
final int l;
@override @override
State<RequestList> createState() => _RequestListState(); ConsumerState<RequestList> createState() => _RequestListState();
} }
class _RequestListState extends State<RequestList> { class _RequestListState extends ConsumerState<RequestList> {
List<String> requestItems = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -73,9 +72,7 @@ class _RequestListState extends State<RequestList> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (requestItems.length != widget.l) { final requestItems = ref.watch(collectionStateNotifierProvider);
requestItems = List.generate(widget.l, (index) => "request${index + 1}");
}
return ReorderableListView.builder( return ReorderableListView.builder(
buildDefaultDragHandles: false, buildDefaultDragHandles: false,
shrinkWrap: true, shrinkWrap: true,
@ -85,19 +82,17 @@ class _RequestListState extends State<RequestList> {
newIndex -= 1; newIndex -= 1;
} }
if (oldIndex != newIndex) { if (oldIndex != newIndex) {
var t = requestItems[oldIndex]; ref
requestItems[oldIndex] = requestItems[newIndex]; .read(collectionStateNotifierProvider.notifier)
requestItems[newIndex] = t; .reorder(oldIndex, newIndex);
setState(() {
requestItems = [...requestItems];
});
} }
}, },
itemBuilder: (context, index) { itemBuilder: (context, index) {
return ReorderableDragStartListener( return ReorderableDragStartListener(
key: Key(requestItems[index]), key: Key(requestItems[index].id),
index: index, index: index,
child: RequestItem(id: requestItems[index]), child: RequestItem(
id: requestItems[index].id, requestModel: requestItems[index]),
); );
}, },
); );
@ -106,19 +101,21 @@ class _RequestListState extends State<RequestList> {
enum RequestItemMenuOption { delete, duplicate } enum RequestItemMenuOption { delete, duplicate }
class RequestItem extends StatefulWidget { class RequestItem extends ConsumerStatefulWidget {
const RequestItem({ const RequestItem({
required this.id, required this.id,
required this.requestModel,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
final String id; final String id;
final RequestModel requestModel;
@override @override
State<RequestItem> createState() => _RequestItemState(); ConsumerState<RequestItem> createState() => _RequestItemState();
} }
class _RequestItemState extends State<RequestItem> { class _RequestItemState extends ConsumerState<RequestItem> {
late Color _color; late Color _color;
@override @override
@ -129,17 +126,23 @@ class _RequestItemState extends State<RequestItem> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final activeRequest = ref.watch(activeItemIdStateProvider);
bool isActiveId = activeRequest == widget.id;
return Material( return Material(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
elevation: 1, elevation: isActiveId ? 2 : 0,
color: Colors.grey.shade50, color: isActiveId ? Colors.grey.shade300 : _color,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
onTap: () {}, onTap: () {
ref
.read(activeItemIdStateProvider.notifier)
.update((state) => widget.id);
},
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 10, left: 10,
right: 0, right: isActiveId ? 0 : 20,
top: 5, top: 5,
bottom: 5, bottom: 5,
), ),
@ -147,30 +150,50 @@ class _RequestItemState extends State<RequestItem> {
height: 22, height: 22,
child: Row( child: Row(
children: [ children: [
MethodBox(), MethodBox(method: widget.requestModel.method),
const SizedBox( const SizedBox(
width: 5, width: 5,
), ),
Expanded( Expanded(
child: Text( child: Text(
widget.id, widget.requestModel.id,
softWrap: false,
overflow: TextOverflow.fade,
), ),
), ),
PopupMenuButton<RequestItemMenuOption>( Visibility(
padding: EdgeInsets.zero, visible: isActiveId,
splashRadius: 14, child: PopupMenuButton<RequestItemMenuOption>(
iconSize: 14, padding: EdgeInsets.zero,
itemBuilder: (BuildContext context) => splashRadius: 14,
<PopupMenuEntry<RequestItemMenuOption>>[ iconSize: 14,
const PopupMenuItem<RequestItemMenuOption>( onSelected: (RequestItemMenuOption item) {
value: RequestItemMenuOption.delete, if (item == RequestItemMenuOption.delete) {
child: Text('Delete'), ref
), .read(activeItemIdStateProvider.notifier)
const PopupMenuItem<RequestItemMenuOption>( .update((state) => null);
value: RequestItemMenuOption.duplicate, ref
child: Text('Duplicate'), .read(collectionStateNotifierProvider.notifier)
), .remove(widget.id);
], }
if (item == RequestItemMenuOption.duplicate) {
ref
.read(collectionStateNotifierProvider.notifier)
.duplicate(widget.id);
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<RequestItemMenuOption>>[
const PopupMenuItem<RequestItemMenuOption>(
value: RequestItemMenuOption.delete,
child: Text('Delete'),
),
const PopupMenuItem<RequestItemMenuOption>(
value: RequestItemMenuOption.duplicate,
child: Text('Duplicate'),
),
],
),
), ),
], ],
), ),
@ -182,11 +205,15 @@ class _RequestItemState extends State<RequestItem> {
} }
class MethodBox extends StatelessWidget { class MethodBox extends StatelessWidget {
const MethodBox({super.key}); const MethodBox({super.key, required this.method});
final HTTPVerb method;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String text = "get".toUpperCase(); String text = method.name.toUpperCase();
if (method == HTTPVerb.delete) {
text = "DEL";
}
return SizedBox( return SizedBox(
width: 25, width: 25,
child: Text( child: Text(

View File

@ -1 +1,2 @@
export 'editor_pane.dart';
export 'collection_pane.dart'; export 'collection_pane.dart';

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/providers.dart';
class RequestEditorPane extends ConsumerStatefulWidget {
const RequestEditorPane({
Key? key,
}) : super(key: key);
@override
ConsumerState<RequestEditorPane> createState() => _RequestEditorPaneState();
}
class _RequestEditorPaneState extends ConsumerState<RequestEditorPane> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final activeId = ref.watch(activeItemIdStateProvider);
if (activeId == null) {
return Text("Select One");
} else {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
children: [
Container(),
],
),
);
}
}
}

3
lib/consts.dart Normal file
View File

@ -0,0 +1,3 @@
enum HTTPVerb { get, head, post, put, patch, delete }
const DEFAULT_METHOD = HTTPVerb.get;

View File

@ -1,14 +1,20 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:desktop_window/desktop_window.dart'; import 'package:desktop_window/desktop_window.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'screens/screens.dart'; import 'screens/screens.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
Size size = await DesktopWindow.getWindowSize(); if (!kIsWeb) {
print(size); await DesktopWindow.setWindowSize(Size(1400, 800));
await DesktopWindow.setWindowSize(Size(1400, 800)); await DesktopWindow.setMinWindowSize(Size(1200, 800));
await DesktopWindow.setMinWindowSize(Size(1200, 800)); }
runApp(App()); runApp(
const ProviderScope(
child: App(),
),
);
} }
class App extends StatelessWidget { class App extends StatelessWidget {

46
lib/models/models.dart Normal file
View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import '../consts.dart';
@immutable
class RequestModel {
const RequestModel({
required this.id,
this.method = DEFAULT_METHOD,
this.url = "",
});
final String id;
final HTTPVerb method;
final String url;
RequestModel duplicate({
required String id,
}) {
return RequestModel(
id: id,
method: method,
url: url,
);
}
RequestModel copyWith({
String? id,
HTTPVerb? method,
String? url,
}) {
return RequestModel(
id: id ?? this.id,
method: method ?? this.method,
url: url ?? this.url,
);
}
@override
String toString() {
return [
id,
method.name,
"URL: $url",
].join("\n");
}
}

View File

@ -0,0 +1,74 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';
import '../models/models.dart';
import '../consts.dart';
const _uuid = Uuid();
final activeItemIdStateProvider = StateProvider<String?>((ref) => null);
final StateNotifierProvider<CollectionStateNotifier, List<RequestModel>>
collectionStateNotifierProvider =
StateNotifierProvider((ref) => CollectionStateNotifier());
class CollectionStateNotifier extends StateNotifier<List<RequestModel>> {
CollectionStateNotifier()
: super([
RequestModel(
id: _uuid.v1(),
),
]);
int idxOfId(String id) => state.indexWhere((element) => element.id == id);
RequestModel getRequestModel(String id) {
final idx = idxOfId(id);
return state[idx];
}
String add() {
final newRequestModel = RequestModel(
id: _uuid.v1(),
);
state = [newRequestModel, ...state];
return newRequestModel.id;
}
void reorder(int oldIdx, int newIdx) {
final item = state.removeAt(oldIdx);
state.insert(newIdx, item);
}
void remove(String id) {
state = [
for (final model in state)
if (model.id != id) model,
];
}
void duplicate(String id) {
final idx = idxOfId(id);
final newModel = state[idx].duplicate(
id: _uuid.v1(),
);
state = [...state.sublist(0, idx + 1), newModel, ...state.sublist(idx + 1)];
}
void update(
String id, {
HTTPVerb? method,
String? url,
int? requestTabIndex,
dynamic requestBody,
int? responseStatus,
Map<String, String>? responseHeaders,
String? responseBody,
}) {
final idx = idxOfId(id);
final newModel = state[idx].copyWith(
method: method,
url: url,
);
state = [...state.sublist(0, idx), newModel, ...state.sublist(idx + 1)];
}
}

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:multi_split_view/multi_split_view.dart';
import '../components/components.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(size: 300, minimalSize: 200),
Area(minimalWeight: 0.7),
],
);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 4,
dividerPainter: DividerPainters.background(
color: Colors.grey.shade200,
highlightedColor: Colors.grey.shade400,
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: const [
CollectionPane(),
RequestEditorPane(),
],
),
),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -1,60 +1 @@
import 'package:flutter/material.dart'; export "home_page.dart";
import 'package:multi_split_view/multi_split_view.dart';
import '../components/components.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
final MultiSplitViewController _controller = MultiSplitViewController(
areas: [
Area(size: 300, minimalSize: 200),
Area(minimalWeight: 0.7),
],
);
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title: const Text('Multi Split View Example')),
body: Column(
children: [
Expanded(
child: MultiSplitViewTheme(
data: MultiSplitViewThemeData(
dividerThickness: 4,
dividerPainter: DividerPainters.background(
color: Colors.grey.shade200,
highlightedColor: Colors.grey.shade400,
animationEnabled: false,
),
),
child: MultiSplitView(
controller: _controller,
children: [
CollectionPane(),
Container(),
],
),
),
),
],
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}

View File

@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
crypto:
dependency: transitive
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
url: "https://pub.dev"
source: hosted
version: "3.0.2"
desktop_window: desktop_window:
dependency: "direct main" dependency: "direct main"
description: description:
@ -70,6 +78,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "40c0d7d03ccd24fa32ea08dcfc4df9bb92c4c26c9a91938945da3be10ed8ca83"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -131,6 +147,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.2" version: "1.8.2"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: c5aea6c3fed340707f013a023a37ab388bf45611a8a4f7e76b5e9007eb76cd25
url: "https://pub.dev"
source: hosted
version: "2.3.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -152,6 +176,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.0"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289"
url: "https://pub.dev"
source: hosted
version: "0.7.2+1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -184,6 +216,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.16" version: "0.4.16"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
uuid:
dependency: "direct main"
description:
name: uuid
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
url: "https://pub.dev"
source: hosted
version: "3.0.7"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -194,4 +242,4 @@ packages:
version: "2.1.4" version: "2.1.4"
sdks: sdks:
dart: ">=2.19.2 <3.0.0" dart: ">=2.19.2 <3.0.0"
flutter: ">=1.20.0" flutter: ">=3.0.0"

View File

@ -11,7 +11,9 @@ dependencies:
sdk: flutter sdk: flutter
multi_split_view: ^2.4.0 multi_split_view: ^2.4.0
desktop_window: ^0.4.0 desktop_window: ^0.4.0
flutter_riverpod: ^2.1.3
uuid: ^3.0.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter