Skip to content

Commit a49bcec

Browse files
authored
Merge pull request #227 from DenserMeerkat/add-feature-mobile-layout
feat: mobile support
2 parents f589155 + 3234d57 commit a49bcec

26 files changed

+895
-169
lines changed

lib/app.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:window_manager/window_manager.dart' hide WindowCaption;
66
import 'widgets/widgets.dart' show WindowCaption;
77
import 'providers/providers.dart';
88
import 'screens/screens.dart';
9+
import 'extensions/extensions.dart';
910
import 'consts.dart';
1011

1112
class App extends ConsumerStatefulWidget {
@@ -125,10 +126,9 @@ class DashApp extends ConsumerWidget {
125126
),
126127
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
127128
home: kIsMobile
128-
? const MobileDashboard(
129-
title: 'Requests',
130-
scaffoldBody: CollectionPane(),
131-
)
129+
? context.isLargeWidth
130+
? const Dashboard()
131+
: const MobileDashboard()
132132
: Stack(
133133
children: [
134134
kIsLinux ? const Dashboard() : const App(),

lib/consts.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const kMinWindowSize = Size(900, 600);
3939
const kMinInitialWindowWidth = 1200.0;
4040
const kMinInitialWindowHeight = 800.0;
4141
const kMinRequestEditorDetailsCardPaneSize = 300.0;
42+
const kLargeMobileWidth = 600.0;
4243

4344
const kColorSchemeSeed = Colors.blue;
4445
final kFontFamily = GoogleFonts.openSans().fontFamily;
@@ -123,6 +124,7 @@ const kVSpacer40 = SizedBox(height: 40);
123124

124125
const kTabAnimationDuration = Duration(milliseconds: 200);
125126
const kTabHeight = 32.0;
127+
const kMobileTabHeight = 40.0;
126128
const kHeaderHeight = 32.0;
127129
const kSegmentHeight = 24.0;
128130
const kTextButtonMinWidth = 44.0;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:apidash/consts.dart';
2+
import 'package:flutter/material.dart';
3+
4+
extension MediaQueryExtension on BuildContext {
5+
bool get isLargeWidth =>
6+
MediaQuery.of(this).size.width > kMinWindowSize.width;
7+
8+
bool get isMobile =>
9+
kIsMobile && MediaQuery.of(this).size.width < kMinWindowSize.width;
10+
}

lib/extensions/extensions.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export 'context_extensions.dart';
12
export 'string_extensions.dart';

lib/providers/ui_providers.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import 'package:flutter/widgets.dart';
22
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:inner_drawer/inner_drawer.dart';
34

5+
final mobileDrawerKeyProvider = StateProvider<GlobalKey<InnerDrawerState>>(
6+
(ref) => GlobalKey<InnerDrawerState>());
47
final navRailIndexStateProvider = StateProvider<int>((ref) => 0);
58
final selectedIdEditStateProvider = StateProvider<String?>((ref) => null);
69
final codePaneVisibleStateProvider = StateProvider<bool>((ref) => false);

lib/screens/home_page/collection_pane.dart

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:apidash/providers/providers.dart';
44
import 'package:apidash/widgets/widgets.dart';
55
import 'package:apidash/models/models.dart';
66
import 'package:apidash/consts.dart';
7+
import 'package:apidash/extensions/extensions.dart';
78

89
class CollectionPane extends ConsumerWidget {
910
const CollectionPane({
@@ -72,7 +73,6 @@ class CollectionPane extends ConsumerWidget {
7273
),
7374
kVSpacer10,
7475
Container(
75-
height: 30,
7676
margin: const EdgeInsets.only(right: 8),
7777
decoration: BoxDecoration(
7878
borderRadius: kBorderRadius8,
@@ -150,7 +150,12 @@ class _RequestListState extends ConsumerState<RequestList> {
150150
radius: const Radius.circular(12),
151151
child: filterQuery.isEmpty
152152
? ReorderableListView.builder(
153-
padding: kPe8,
153+
padding: context.isMobile
154+
? EdgeInsets.only(
155+
bottom: MediaQuery.paddingOf(context).bottom,
156+
right: 8,
157+
)
158+
: kPe8,
154159
scrollController: controller,
155160
buildDefaultDragHandles: false,
156161
itemCount: requestSequence.length,
@@ -166,6 +171,19 @@ class _RequestListState extends ConsumerState<RequestList> {
166171
},
167172
itemBuilder: (context, index) {
168173
var id = requestSequence[index];
174+
if (kIsMobile) {
175+
return ReorderableDelayedDragStartListener(
176+
key: ValueKey(id),
177+
index: index,
178+
child: Padding(
179+
padding: kP1,
180+
child: RequestItem(
181+
id: id,
182+
requestModel: requestItems[id]!,
183+
),
184+
),
185+
);
186+
}
169187
return ReorderableDragStartListener(
170188
key: ValueKey(id),
171189
index: index,
@@ -180,7 +198,12 @@ class _RequestListState extends ConsumerState<RequestList> {
180198
},
181199
)
182200
: ListView(
183-
padding: kPe8,
201+
padding: kIsMobile
202+
? EdgeInsets.only(
203+
bottom: MediaQuery.paddingOf(context).bottom,
204+
right: 8,
205+
)
206+
: kPe8,
184207
controller: controller,
185208
children: requestSequence.map((id) {
186209
var item = requestItems[id]!;
@@ -217,6 +240,7 @@ class RequestItem extends ConsumerWidget {
217240
Widget build(BuildContext context, WidgetRef ref) {
218241
final selectedId = ref.watch(selectedIdStateProvider);
219242
final editRequestId = ref.watch(selectedIdEditStateProvider);
243+
final mobileDrawerKey = ref.watch(mobileDrawerKeyProvider);
220244

221245
return SidebarRequestCard(
222246
id: id,
@@ -226,6 +250,7 @@ class RequestItem extends ConsumerWidget {
226250
selectedId: selectedId,
227251
editRequestId: editRequestId,
228252
onTap: () {
253+
mobileDrawerKey.currentState?.close();
229254
ref.read(selectedIdStateProvider.notifier).state = id;
230255
},
231256
// onDoubleTap: () {

lib/screens/home_page/editor_pane/editor_pane.dart

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import 'package:apidash/providers/providers.dart';
4-
import 'package:apidash/consts.dart';
54
import 'editor_default.dart';
65
import 'editor_request.dart';
76

@@ -16,10 +15,7 @@ class RequestEditorPane extends ConsumerWidget {
1615
if (selectedId == null) {
1716
return const RequestEditorDefault();
1817
} else {
19-
return Padding(
20-
padding: kIsMacOS || kIsWindows ? kPt24o8 : kP8,
21-
child: const RequestEditor(),
22-
);
18+
return const RequestEditor();
2319
}
2420
}
2521
}
Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,47 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import 'package:apidash/providers/providers.dart';
4+
import 'package:apidash/extensions/extensions.dart';
45
import 'package:apidash/consts.dart';
56
import 'details_card/details_card.dart';
7+
import 'details_card/request_pane/request_pane.dart';
68
import 'url_card.dart';
79

810
class RequestEditor extends StatelessWidget {
911
const RequestEditor({super.key});
1012

1113
@override
1214
Widget build(BuildContext context) {
13-
return const Column(
14-
children: [
15-
RequestEditorTopBar(),
16-
EditorPaneRequestURLCard(),
17-
kVSpacer10,
18-
Expanded(
19-
child: EditorPaneRequestDetailsCard(),
20-
),
21-
],
22-
);
15+
return context.isMobile
16+
? const Padding(
17+
padding: kPb10,
18+
child: Column(
19+
children: [
20+
kVSpacer5,
21+
Padding(
22+
padding: kPh8,
23+
child: EditorPaneRequestURLCard(),
24+
),
25+
kVSpacer10,
26+
Expanded(
27+
child: EditRequestPane(),
28+
),
29+
],
30+
),
31+
)
32+
: Padding(
33+
padding: kIsMacOS || kIsWindows ? kPt24o8 : kP8,
34+
child: const Column(
35+
children: [
36+
RequestEditorTopBar(),
37+
EditorPaneRequestURLCard(),
38+
kVSpacer10,
39+
Expanded(
40+
child: EditorPaneRequestDetailsCard(),
41+
),
42+
],
43+
),
44+
);
2345
}
2446
}
2547

@@ -59,41 +81,11 @@ class RequestEditorTopBar extends ConsumerWidget {
5981
padding: MaterialStatePropertyAll(EdgeInsets.zero),
6082
),
6183
onPressed: () {
62-
showDialog(
63-
context: context,
64-
builder: (context) {
65-
final controller =
66-
TextEditingController(text: name ?? "");
67-
controller.selection = TextSelection(
68-
baseOffset: 0, extentOffset: controller.text.length);
69-
return AlertDialog(
70-
title: const Text('Rename Request'),
71-
content: TextField(
72-
autofocus: true,
73-
controller: controller,
74-
decoration:
75-
const InputDecoration(hintText: "Enter new name"),
76-
),
77-
actions: <Widget>[
78-
OutlinedButton(
79-
onPressed: () {
80-
Navigator.pop(context);
81-
},
82-
child: const Text('CANCEL')),
83-
FilledButton(
84-
onPressed: () {
85-
final val = controller.text.trim();
86-
ref
87-
.read(collectionStateNotifierProvider
88-
.notifier)
89-
.update(id!, name: val);
90-
Navigator.pop(context);
91-
controller.dispose();
92-
},
93-
child: const Text('OK')),
94-
],
95-
);
96-
});
84+
showRenameDialog(context, name, (val) {
85+
ref
86+
.read(collectionStateNotifierProvider.notifier)
87+
.update(id!, name: val);
88+
});
9789
},
9890
icon: const Icon(
9991
Icons.edit,
@@ -110,3 +102,42 @@ class RequestEditorTopBar extends ConsumerWidget {
110102
);
111103
}
112104
}
105+
106+
showRenameDialog(
107+
BuildContext context,
108+
String? name,
109+
Function(String) onRename,
110+
) {
111+
showDialog(
112+
context: context,
113+
builder: (context) {
114+
final controller = TextEditingController(text: name ?? "");
115+
controller.selection =
116+
TextSelection(baseOffset: 0, extentOffset: controller.text.length);
117+
return AlertDialog(
118+
title: const Text('Rename Request'),
119+
content: TextField(
120+
autofocus: true,
121+
controller: controller,
122+
decoration: const InputDecoration(hintText: "Enter new name"),
123+
),
124+
actions: <Widget>[
125+
OutlinedButton(
126+
onPressed: () {
127+
Navigator.pop(context);
128+
},
129+
child: const Text('CANCEL')),
130+
FilledButton(
131+
onPressed: () {
132+
final val = controller.text.trim();
133+
onRename(val);
134+
Navigator.pop(context);
135+
Future.delayed(const Duration(milliseconds: 100), () {
136+
controller.dispose();
137+
});
138+
},
139+
child: const Text('OK')),
140+
],
141+
);
142+
});
143+
}

lib/screens/home_page/editor_pane/url_card.dart

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import 'package:apidash/providers/providers.dart';
44
import 'package:apidash/widgets/widgets.dart';
55
import 'package:apidash/consts.dart';
6+
import 'package:apidash/extensions/extensions.dart';
67

78
class EditorPaneRequestURLCard extends StatelessWidget {
89
const EditorPaneRequestURLCard({super.key});
@@ -17,25 +18,36 @@ class EditorPaneRequestURLCard extends StatelessWidget {
1718
),
1819
borderRadius: kBorderRadius12,
1920
),
20-
child: const Padding(
21+
child: Padding(
2122
padding: EdgeInsets.symmetric(
2223
vertical: 5,
23-
horizontal: 20,
24-
),
25-
child: Row(
26-
children: [
27-
DropdownButtonHTTPMethod(),
28-
kHSpacer20,
29-
Expanded(
30-
child: URLTextField(),
31-
),
32-
kHSpacer20,
33-
SizedBox(
34-
height: 36,
35-
child: SendButton(),
36-
),
37-
],
24+
horizontal: !context.isMobile ? 20 : 6,
3825
),
26+
child: context.isMobile
27+
? const Row(
28+
children: [
29+
DropdownButtonHTTPMethod(),
30+
kHSpacer5,
31+
Expanded(
32+
child: URLTextField(),
33+
),
34+
SizedBox.shrink(),
35+
],
36+
)
37+
: const Row(
38+
children: [
39+
DropdownButtonHTTPMethod(),
40+
kHSpacer20,
41+
Expanded(
42+
child: URLTextField(),
43+
),
44+
kHSpacer20,
45+
SizedBox(
46+
height: 36,
47+
child: SendButton(),
48+
)
49+
],
50+
),
3951
),
4052
);
4153
}
@@ -92,8 +104,10 @@ class URLTextField extends ConsumerWidget {
92104
}
93105

94106
class SendButton extends ConsumerWidget {
107+
final Function()? onTap;
95108
const SendButton({
96109
super.key,
110+
this.onTap,
97111
});
98112

99113
@override
@@ -105,6 +119,7 @@ class SendButton extends ConsumerWidget {
105119
return SendRequestButton(
106120
isWorking: isWorking ?? false,
107121
onTap: () {
122+
onTap?.call();
108123
ref
109124
.read(collectionStateNotifierProvider.notifier)
110125
.sendRequest(selectedId!);

0 commit comments

Comments
 (0)