Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bf3b401
removed hive and shared pref
ShashwatXD May 28, 2026
fbd52e5
revived shared prefs
ShashwatXD May 28, 2026
040bd55
stable version with file system
ShashwatXD May 28, 2026
ee4b89a
removed save button and added auto save func
ShashwatXD May 28, 2026
e7baca1
added collections pane
ShashwatXD May 28, 2026
2c070e9
fixed pref bug
ShashwatXD May 28, 2026
1407e00
added lazy loading
ShashwatXD May 28, 2026
d33a85b
fixed response bodyBytes
ShashwatXD May 30, 2026
1096f88
fixed race conditions in collections
ShashwatXD May 30, 2026
59e75c7
fixed history loads
ShashwatXD May 30, 2026
5d46499
optimized history retention
ShashwatXD Jun 1, 2026
9a26817
added workspace selector
ShashwatXD Jun 1, 2026
bb498c2
added secret toggler in variables
ShashwatXD Jun 2, 2026
0d080b2
fixed provider error on workspace initialise
ShashwatXD Jun 2, 2026
c568a05
fixed text field style on obsecure
ShashwatXD Jun 2, 2026
25ec485
aoptimized collection read
ShashwatXD Jun 2, 2026
a42b17c
index.json stores all values to avoid multiple disk reads
ShashwatXD Jun 2, 2026
b1c3b03
added flutter secure storage for secret env variables
ShashwatXD Jun 5, 2026
97fe9c3
added human readable file names system
ShashwatXD Jun 5, 2026
05df53b
added dep- flutter secure storage
ShashwatXD Jun 6, 2026
2d029ab
fixed delete issue of secrets in macos
ShashwatXD Jun 22, 2026
9d67f50
shifted ai api keys to flutter secure storage
ShashwatXD Jun 22, 2026
6423065
updated tests
ShashwatXD Jun 24, 2026
b3e97e6
removed hive dep
ShashwatXD Jun 24, 2026
5d1a604
polished collections
ShashwatXD Jun 29, 2026
d33048c
fixed history files (made more readable)
ShashwatXD Jun 29, 2026
d3a0ecc
added proper names to requests and history
ShashwatXD Jun 30, 2026
a3bdfe7
removed rename func from global variables
ShashwatXD Jun 30, 2026
e72065a
polished secret variable ux
ShashwatXD Jun 30, 2026
4239aa0
updated tests
ShashwatXD Jun 30, 2026
2ee19c6
added save as files feature with tests
ShashwatXD Jun 30, 2026
6916394
use snake case
ShashwatXD Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ class _AppState extends ConsumerState<App> with WindowListener {
FilledButton(
child: const Text('Save'),
onPressed: () async {
ref.read(autoSaveNotifierProvider.notifier).cancelPending();
await ref
.read(collectionStateNotifierProvider.notifier)
.read(activeCollectionProvider.notifier)
.saveData();
Navigator.of(context).pop();
await windowManager.setPreventClose(false);
Expand Down Expand Up @@ -113,7 +114,8 @@ class DashApp extends ConsumerWidget {
ref.watch(settingsProvider.select((value) => value.isDark));
final workspaceFolderPath = ref
.watch(settingsProvider.select((value) => value.workspaceFolderPath));
final showWorkspaceSelector = kIsDesktop && (workspaceFolderPath == null);
final showWorkspaceSelector = kIsDesktop &&
(workspaceFolderPath == null || workspaceFolderPath.isEmpty);
final userOnboarded = ref.watch(userOnboardedProvider);
return Portal(
child: MaterialApp(
Expand All @@ -123,11 +125,8 @@ class DashApp extends ConsumerWidget {
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
home: showWorkspaceSelector
? WorkspaceSelector(
onContinue: (val) async {
await initHiveBoxes(kIsDesktop, val);
ref
.read(settingsProvider.notifier)
.update(workspaceFolderPath: val);
onContinue: (path) async {
await activateWorkspace(ref, path);
},
onCancel: () async {
try {
Expand Down
47 changes: 47 additions & 0 deletions lib/consts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,38 @@ final kIconRemoveLight = Icon(

const kCodePreviewLinesLimit = 500;

const kAutoSaveDebounceDuration = Duration(seconds: 1);

const kDefaultMobileWorkspaceSubpath = 'apidash/workspace';

const kMaxSavedWorkspaces = 10;

const kDefaultCollectionName = 'Collection 1';
const kWorkspaceCollectionsDir = 'collections';
const kWorkspaceCollectionsIndexFile = 'collection_index.json';
const kWorkspaceRequestFile = 'request.json';
const kWorkspaceResponseFile = 'response.json';
const kWorkspaceResponseBodyFilePrefix = 'response_body';
const kWorkspaceResponseBodyFileKey = 'bodyFile';
const kWorkspaceRequestIndexFile = 'request_index.json';
const kWorkspaceCollectionsIndexKey = 'collections';
const kWorkspaceCollectionIdKey = 'id';
const kWorkspaceCollectionNameKey = 'name';
const kWorkspaceEnvironmentsDir = 'environments';
const kWorkspaceEnvironmentIndexFile = 'environmentIndex.json';
const kWorkspaceHistoryDir = 'history';
const kWorkspaceHistoryIndexFile = 'history_index.json';
const kJsonFileExtension = '.json';

const kWorkspaceRequestsKey = 'requests';
const kWorkspaceEnvironmentIdsKey = 'environmentIds';
const kWorkspaceHistoryMetasKey = 'historyMeta';
const kWorkspaceActiveEnvironmentIdKey = 'activeEnvironmentId';
const kWorkspaceSaveResponsesKey = 'saveResponses';
const kWorkspaceHistoryRetentionPeriodKey = 'historyRetentionPeriod';

const kGlobalEnvironmentName = 'Global Variables';

enum HistoryRetentionPeriod {
oneWeek("1 Week", Icons.calendar_view_week_rounded),
oneMonth("1 Month", Icons.calendar_view_month_rounded),
Expand Down Expand Up @@ -431,6 +463,15 @@ const kRaiseIssue =

const kHintTextUrlCard = "Enter API endpoint like https://$kDefaultUri/";
const kLabelPlusNew = "+ New";
const kLabelNewCollection = "New collection";
const kLabelRenameCollection = "Rename collection";
const kLabelDeleteCollection = "Delete collection";
const kLabelCreateCollection = "Create collection";
const kMsgNoCollections = "No collections yet";
const kLabelCollectionName = "Collection name";
const kMsgCollectionNameInUse = "This collection name already exists";
const kMsgCollectionNameInvalidChars =
r'Name cannot contain / \ : * ? " < > |';
const kLabelMoreOptions = "More Options";
const kLabelSend = "Send";
const kLabelSending = "Sending..";
Expand Down Expand Up @@ -497,6 +538,9 @@ const kMsgNoContent = "No content";
const kMsgUnknowContentType = "Unknown Response Content-Type";
// Workspace Selector
const kMsgSelectWorkspace = "Create your workspace";
const kLabelSelectWorkspace = "Select workspace";
const kLabelOpenWorkspaceMenu = "Open workspace…";
const kMsgWorkspaceOpenFailed = 'Could not open that workspace folder.';
// History Page
const kTitleClearHistory = 'Clear History';
const kMsgClearHistory =
Expand Down Expand Up @@ -528,6 +572,9 @@ const kLabelDefaultLLM = "Default Large Language Model (LLM)";
const kLabelSaveResponses = "Save Responses";
const kLabelSaveResponsesSubtitle =
"Save disk space by not storing API responses";
const kLabelSaveMediaResponsesAsFiles = "Save Media Responses as Files";
const kLabelSaveMediaResponsesAsFilesSubtitle =
"Store image, audio, video & binary responses as standalone files ";
const kLabelShowSaveAlert = "Show Save Alert on App Close";
const kLabelShowSaveAlertSubtitle =
"Show a confirmation dialog to save workspace when the user closes the app";
Expand Down
2 changes: 1 addition & 1 deletion lib/dashbot/pages/dashbot_home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:apidash/providers/collection_providers.dart';
import 'package:apidash/providers/active_collection_providers.dart';
import 'package:apidash/screens/common_widgets/agentic_ui_features/ai_ui_designer/generate_ui_dialog.dart';
import 'package:apidash/screens/common_widgets/agentic_ui_features/tool_generation/generate_tool_dialog.dart';
import '../constants.dart';
Expand Down
8 changes: 4 additions & 4 deletions lib/dashbot/providers/chat_viewmodel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
}

Future<void> _applyOpenApi(ChatAction action) async {
final collection = _ref.read(collectionStateNotifierProvider.notifier);
final collection = _ref.read(activeCollectionProvider.notifier);
final payload = action.value is Map<String, dynamic>
? (action.value as Map<String, dynamic>)
: <String, dynamic>{};
Expand Down Expand Up @@ -452,7 +452,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
if (requestId == null) return;

final collectionNotifier = _ref.read(
collectionStateNotifierProvider.notifier,
activeCollectionProvider.notifier,
);
final testCode = action.value is String ? action.value as String : '';
final currentPostScript = _currentRequest?.postRequestScript ?? '';
Expand Down Expand Up @@ -813,7 +813,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
if (action.field == 'apply_to_selected') {
if (requestId == null) return;
_ref
.read(collectionStateNotifierProvider.notifier)
.read(activeCollectionProvider.notifier)
.update(
method: httpRequestModel.method,
url: withEnvUrl,
Expand All @@ -839,7 +839,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
} else if (action.field == 'apply_to_new') {
final model = httpRequestModel.copyWith(url: withEnvUrl);
_ref
.read(collectionStateNotifierProvider.notifier)
.read(activeCollectionProvider.notifier)
.addRequestModel(model, name: 'Imported cURL');
_appendSystem(
'Created a new request from the cURL.',
Expand Down
2 changes: 1 addition & 1 deletion lib/dashbot/providers/service_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final requestApplyServiceProvider = Provider<RequestApplyService>((ref) {
});

final autoFixServiceProvider = Provider<AutoFixService>((ref) {
final collection = ref.read(collectionStateNotifierProvider.notifier);
final collection = ref.read(activeCollectionProvider.notifier);
final urlEnv = ref.read(urlEnvServiceProvider);
return AutoFixService(
requestApply: ref.read(requestApplyServiceProvider),
Expand Down
2 changes: 1 addition & 1 deletion lib/dashbot/widgets/dashbot_task_buttons.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/collection_providers.dart';
import 'package:apidash/providers/active_collection_providers.dart';
import 'package:apidash/screens/common_widgets/agentic_ui_features/ai_ui_designer/generate_ui_dialog.dart';
import 'package:apidash/screens/common_widgets/agentic_ui_features/tool_generation/generate_tool_dialog.dart';
import '../constants.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/importer/import_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void importToCollectionPane(
} else {
for (var model in importedRequestModels.reversed) {
ref
.read(collectionStateNotifierProvider.notifier)
.read(activeCollectionProvider.notifier)
.addRequestModel(
model.$2,
name: model.$1,
Expand Down
8 changes: 4 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ Future<bool> initApp(
try {
debugPrint("initializeUsingPath: $initializeUsingPath");
debugPrint("workspaceFolderPath: ${settingsModel?.workspaceFolderPath}");
final openBoxesStatus = await initHiveBoxes(
final workspaceReady = await initWorkspaceStorage(
initializeUsingPath,
settingsModel?.workspaceFolderPath,
);
debugPrint("openBoxesStatus: $openBoxesStatus");
if (openBoxesStatus) {
debugPrint("workspaceReady: $workspaceReady");
if (workspaceReady) {
await autoClearHistory(settingsModel: settingsModel);
}
return openBoxesStatus;
return workspaceReady;
} catch (e) {
debugPrint("initApp failed due to $e");
return false;
Expand Down
55 changes: 55 additions & 0 deletions lib/models/collection_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:apidash/consts.dart';

import 'request_summary_model.dart';

class CollectionModel {
const CollectionModel({
required this.id,
required this.name,
this.requests = const [],
});

final String id;
final String name;
final List<RequestSummary> requests;

List<String> get requestIds => requests.map((r) => r.id).toList();

CollectionModel copyWith({
String? name,
List<RequestSummary>? requests,
}) {
return CollectionModel(
id: id,
name: name ?? this.name,
requests: requests ?? this.requests,
);
}

factory CollectionModel.fromJson(Map<String, Object?> json) {
final requestsJson = json[kWorkspaceRequestsKey];
final requests = <RequestSummary>[];
if (requestsJson is List) {
for (final item in requestsJson) {
if (item is Map) {
requests.add(
RequestSummary.fromJson(Map<String, Object?>.from(item)),
);
}
}
}
return CollectionModel(
id: json[kWorkspaceCollectionIdKey] as String? ?? '',
name: json[kWorkspaceCollectionNameKey] as String? ?? '',
requests: requests,
);
}

Map<String, Object?> toJson() {
return {
kWorkspaceCollectionIdKey: id,
kWorkspaceCollectionNameKey: name,
kWorkspaceRequestsKey: requests.map((r) => r.toJson()).toList(),
};
}
}
3 changes: 3 additions & 0 deletions lib/models/models.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export 'collection_model.dart';
export 'request_summary_model.dart';
export 'saved_workspace_entry.dart';
export 'history_meta_model.dart';
export 'history_request_model.dart';
export 'request_model.dart';
Expand Down
54 changes: 54 additions & 0 deletions lib/models/request_summary_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:apidash_core/apidash_core.dart';

import 'request_model.dart';

class RequestSummary {
const RequestSummary({
required this.id,
this.name = '',
this.apiType = APIType.rest,
this.method,
this.url = '',
});

final String id;
final String name;
final APIType apiType;
final HTTPVerb? method;
final String url;

factory RequestSummary.fromRequestModel(RequestModel model) {
final httpUrl = model.httpRequestModel?.url.trim();
final url = (httpUrl != null && httpUrl.isNotEmpty)
? httpUrl
: model.aiRequestModel?.url.trim() ?? '';
return RequestSummary(
id: model.id,
name: model.name,
apiType: model.apiType,
method: model.httpRequestModel?.method,
url: url,
);
}

factory RequestSummary.fromJson(Map<String, Object?> json) {
final methodName = json['method'] as String?;
return RequestSummary(
id: json['id'] as String,
name: json['name'] as String? ?? '',
apiType: APIType.values.byName(json['apiType'] as String? ?? 'rest'),
method: methodName == null
? null
: HTTPVerb.values.byName(methodName),
url: json['url'] as String? ?? '',
);
}

Map<String, Object?> toJson() => {
'id': id,
'name': name,
'apiType': apiType.name,
if (method != null) 'method': method!.name,
'url': url,
};
}
21 changes: 21 additions & 0 deletions lib/models/saved_workspace_entry.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class SavedWorkspaceEntry {
const SavedWorkspaceEntry({
required this.path,
required this.name,
});

final String path;
final String name;

factory SavedWorkspaceEntry.fromJson(Map<String, Object?> json) {
return SavedWorkspaceEntry(
path: json['path'] as String? ?? '',
name: json['name'] as String? ?? '',
);
}

Map<String, Object?> toJson() => {
'path': path,
'name': name,
};
}
Loading