import 'dart:async'; import 'package:apidash_core/apidash_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/consts.dart'; import 'providers.dart'; import '../models/models.dart'; import '../services/services.dart'; import '../utils/utils.dart'; final selectedIdStateProvider = StateProvider((ref) => null); final selectedRequestModelProvider = StateProvider((ref) { final selectedId = ref.watch(selectedIdStateProvider); final collection = ref.watch(collectionStateNotifierProvider); if (selectedId == null || collection == null) { return null; } else { return collection[selectedId]; } }); final requestSequenceProvider = StateProvider>((ref) { var ids = hiveHandler.getIds(); return ids ?? []; }); final StateNotifierProvider?> collectionStateNotifierProvider = StateNotifierProvider((ref) => CollectionStateNotifier( ref, hiveHandler, )); class CollectionStateNotifier extends StateNotifier?> { CollectionStateNotifier( this.ref, this.hiveHandler, ) : super(null) { var status = loadData(); Future.microtask(() { if (status) { ref.read(requestSequenceProvider.notifier).state = [ state!.keys.first, ]; } ref.read(selectedIdStateProvider.notifier).state = ref.read(requestSequenceProvider)[0]; }); } final Ref ref; final HiveHandler hiveHandler; final baseHttpResponseModel = const HttpResponseModel(); bool hasId(String id) => state?.keys.contains(id) ?? false; RequestModel? getRequestModel(String id) { return state?[id]; } void unsave() { ref.read(hasUnsavedChangesProvider.notifier).state = true; } void add() { final id = getNewUuid(); final newRequestModel = RequestModel( id: id, httpRequestModel: const HttpRequestModel(), ); var map = {...state!}; map[id] = newRequestModel; state = map; ref .read(requestSequenceProvider.notifier) .update((state) => [id, ...state]); ref.read(selectedIdStateProvider.notifier).state = newRequestModel.id; unsave(); } void addRequestModel( HttpRequestModel httpRequestModel, { String? name, }) { final id = getNewUuid(); final newRequestModel = RequestModel( id: id, name: name ?? "", httpRequestModel: httpRequestModel, ); var map = {...state!}; map[id] = newRequestModel; state = map; ref .read(requestSequenceProvider.notifier) .update((state) => [id, ...state]); ref.read(selectedIdStateProvider.notifier).state = newRequestModel.id; unsave(); } void reorder(int oldIdx, int newIdx) { var itemIds = ref.read(requestSequenceProvider); final itemId = itemIds.removeAt(oldIdx); itemIds.insert(newIdx, itemId); ref.read(requestSequenceProvider.notifier).state = [...itemIds]; unsave(); } void remove({String? id}) { final rId = id ?? ref.read(selectedIdStateProvider); var itemIds = ref.read(requestSequenceProvider); int idx = itemIds.indexOf(rId!); cancelHttpRequest(rId); itemIds.remove(rId); ref.read(requestSequenceProvider.notifier).state = [...itemIds]; String? newId; if (idx == 0 && itemIds.isNotEmpty) { newId = itemIds[0]; } else if (itemIds.length > 1) { newId = itemIds[idx - 1]; } else { newId = null; } ref.read(selectedIdStateProvider.notifier).state = newId; var map = {...state!}; map.remove(rId); state = map; unsave(); } void clearResponse({String? id}) { final rId = id ?? ref.read(selectedIdStateProvider); if (rId == null || state?[rId] == null) return; var currentModel = state![rId]!; final newModel = currentModel.copyWith( responseStatus: null, message: null, httpResponseModel: null, isWorking: false, sendingTime: null, ); var map = {...state!}; map[rId] = newModel; state = map; unsave(); } void duplicate({String? id}) { final rId = id ?? ref.read(selectedIdStateProvider); final newId = getNewUuid(); var itemIds = ref.read(requestSequenceProvider); int idx = itemIds.indexOf(rId!); var currentModel = state![rId]!; final newModel = currentModel.copyWith( id: newId, name: "${currentModel.name} (copy)", requestTabIndex: 0, responseStatus: null, message: null, httpResponseModel: null, isWorking: false, sendingTime: null, ); itemIds.insert(idx + 1, newId); var map = {...state!}; map[newId] = newModel; state = map; ref.read(requestSequenceProvider.notifier).state = [...itemIds]; ref.read(selectedIdStateProvider.notifier).state = newId; unsave(); } void duplicateFromHistory(HistoryRequestModel historyRequestModel) { final newId = getNewUuid(); var itemIds = ref.read(requestSequenceProvider); var currentModel = historyRequestModel; final newModel = RequestModel( id: newId, name: "${currentModel.metaData.name} (history)", httpRequestModel: currentModel.httpRequestModel, responseStatus: currentModel.metaData.responseStatus, message: kResponseCodeReasons[currentModel.metaData.responseStatus], httpResponseModel: currentModel.httpResponseModel, isWorking: false, sendingTime: null, ); itemIds.insert(0, newId); var map = {...state!}; map[newId] = newModel; state = map; ref.read(requestSequenceProvider.notifier).state = [...itemIds]; ref.read(selectedIdStateProvider.notifier).state = newId; unsave(); } void update({ String? id, HTTPVerb? method, APIType? apiType, String? url, String? name, String? description, int? requestTabIndex, List? headers, List? params, List? isHeaderEnabledList, List? isParamEnabledList, ContentType? bodyContentType, String? body, String? query, List? formData, int? responseStatus, String? message, HttpResponseModel? httpResponseModel, String? preRequestScript, String? postRequestScript, }) { final rId = id ?? ref.read(selectedIdStateProvider); if (rId == null) { debugPrint("Unable to update as Request Id is null"); return; } var currentModel = state![rId]!; var currentHttpRequestModel = currentModel.httpRequestModel; final newModel = currentModel.copyWith( apiType: apiType ?? currentModel.apiType, name: name ?? currentModel.name, description: description ?? currentModel.description, requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex, httpRequestModel: currentHttpRequestModel?.copyWith( method: method ?? currentHttpRequestModel.method, url: url ?? currentHttpRequestModel.url, headers: headers ?? currentHttpRequestModel.headers, params: params ?? currentHttpRequestModel.params, isHeaderEnabledList: isHeaderEnabledList ?? currentHttpRequestModel.isHeaderEnabledList, isParamEnabledList: isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList, bodyContentType: bodyContentType ?? currentHttpRequestModel.bodyContentType, body: body ?? currentHttpRequestModel.body, query: query ?? currentHttpRequestModel.query, formData: formData ?? currentHttpRequestModel.formData, ), responseStatus: responseStatus ?? currentModel.responseStatus, message: message ?? currentModel.message, httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel, preRequestScript: preRequestScript ?? currentModel.preRequestScript, postRequestScript: postRequestScript ?? currentModel.postRequestScript, ); var map = {...state!}; map[rId] = newModel; state = map; unsave(); } Future sendRequest() async { final requestId = ref.read(selectedIdStateProvider); ref.read(codePaneVisibleStateProvider.notifier).state = false; if (requestId == null || state == null) return; RequestModel? requestModel = state![requestId]; if (requestModel?.httpRequestModel == null) return; final defaultUriScheme = ref.read(settingsProvider).defaultUriScheme; final EnvironmentModel? originalEnvironmentModel = ref.read(selectedEnvironmentModelProvider); if (requestModel != null && !requestModel.preRequestScript.isNullOrEmpty()) { requestModel = await handlePreRequestScript( requestModel, originalEnvironmentModel, (envModel, updatedValues) { ref .read(environmentsStateNotifierProvider.notifier) .updateEnvironment( envModel.id, name: envModel.name, values: updatedValues, ); }, ); } APIType apiType = requestModel!.apiType; HttpRequestModel substitutedHttpRequestModel = getSubstitutedHttpRequestModel(requestModel.httpRequestModel!); bool noSSL = ref.read(settingsProvider).isSSLDisabled; // Set model to working and streaming state = { ...state!, requestId: requestModel.copyWith( isWorking: true, isStreaming: true, sendingTime: DateTime.now(), ), }; final stream = await streamHttpRequest( requestId, apiType, substitutedHttpRequestModel, defaultUriScheme: defaultUriScheme, noSSL: noSSL, ); HttpResponseModel? respModel; HistoryRequestModel? historyM; RequestModel newRequestModel = requestModel; final completer = Completer<(Response?, Duration?, String?)>(); bool? isTextStream; StreamSubscription? sub; sub = stream.listen((d) async { if (d == null) return; final contentType = d.$1?.headers['content-type']?.toLowerCase(); if (isTextStream == null) { if (kStreamingResponseTypes.contains(contentType)) { isTextStream = true; } else { isTextStream = false; } } final response = d.$1; final duration = d.$2; final errorMessage = d.$3; if (isTextStream == false) { if (!completer.isCompleted) { completer.complete((response, duration, errorMessage)); } return; } respModel = respModel?.copyWith( time: duration, sseOutput: [ ...(respModel?.sseOutput ?? []), if (response != null) response.body, ], ); newRequestModel = newRequestModel.copyWith( httpResponseModel: respModel, isStreaming: true, ); state = { ...state!, requestId: newRequestModel, }; unsave(); if (historyM != null && respModel != null) { historyM = historyM!.copyWith(httpResponseModel: respModel!); ref .read(historyMetaStateNotifier.notifier) .editHistoryRequest(historyM!); } if (!completer.isCompleted) { completer.complete((response, duration, errorMessage)); } }, onDone: () { sub?.cancel(); state = { ...state!, requestId: newRequestModel.copyWith(isStreaming: false), }; unsave(); }, onError: (e) { print('Stream error: $e'); }); final (response, duration, errorMessage) = await completer.future; if (response == null) { newRequestModel = newRequestModel.copyWith( responseStatus: -1, message: errorMessage, isWorking: false, isStreaming: false, ); } else { final statusCode = response.statusCode; respModel = baseHttpResponseModel .fromResponse( response: response, time: duration, ) .copyWith( sseOutput: (isTextStream == true) ? [response.body] : [], ); newRequestModel = newRequestModel.copyWith( responseStatus: statusCode, message: kResponseCodeReasons[statusCode], httpResponseModel: respModel, isWorking: false, ); final historyId = getNewUuid(); historyM = HistoryRequestModel( historyId: historyId, metaData: HistoryMetaModel( historyId: historyId, requestId: requestId, apiType: requestModel.apiType, name: requestModel.name, url: substitutedHttpRequestModel.url, method: substitutedHttpRequestModel.method, responseStatus: statusCode, timeStamp: DateTime.now(), ), httpRequestModel: substitutedHttpRequestModel, httpResponseModel: respModel!, preRequestScript: requestModel.preRequestScript, postRequestScript: requestModel.postRequestScript, ); if (!requestModel.postRequestScript.isNullOrEmpty()) { newRequestModel = await handlePostResponseScript( newRequestModel, originalEnvironmentModel, (envModel, updatedValues) { ref .read(environmentsStateNotifierProvider.notifier) .updateEnvironment( envModel.id, name: envModel.name, values: updatedValues, ); }, ); } ref.read(historyMetaStateNotifier.notifier).addHistoryRequest(historyM!); } // Final state update state = { ...state!, requestId: newRequestModel, }; unsave(); } void cancelRequest() { final id = ref.read(selectedIdStateProvider); cancelHttpRequest(id); unsave(); } Future clearData() async { ref.read(clearDataStateProvider.notifier).state = true; ref.read(selectedIdStateProvider.notifier).state = null; await hiveHandler.clear(); ref.read(clearDataStateProvider.notifier).state = false; ref.read(requestSequenceProvider.notifier).state = []; state = {}; unsave(); } bool loadData() { var ids = hiveHandler.getIds(); if (ids == null || ids.length == 0) { String newId = getNewUuid(); state = { newId: RequestModel( id: newId, httpRequestModel: const HttpRequestModel(), ), }; return true; } else { Map data = {}; for (var id in ids) { var jsonModel = hiveHandler.getRequestModel(id); if (jsonModel != null) { var jsonMap = Map.from(jsonModel); var requestModel = RequestModel.fromJson(jsonMap); if (requestModel.httpRequestModel == null) { requestModel = requestModel.copyWith( httpRequestModel: const HttpRequestModel(), ); } data[id] = requestModel; } } state = data; return false; } } Future saveData() async { ref.read(saveDataStateProvider.notifier).state = true; final saveResponse = ref.read(settingsProvider).saveResponses; final ids = ref.read(requestSequenceProvider); await hiveHandler.setIds(ids); for (var id in ids) { await hiveHandler.setRequestModel( id, saveResponse ? (state?[id])?.toJson() : (state?[id]?.copyWith(httpResponseModel: null))?.toJson(), ); } await hiveHandler.removeUnused(); ref.read(saveDataStateProvider.notifier).state = false; ref.read(hasUnsavedChangesProvider.notifier).state = false; } Future> exportDataToHAR() async { var result = await collectionToHAR(state?.values.toList()); return result; // return { // "data": state!.map((e) => e.toJson(includeResponse: false)).toList() // }; } HttpRequestModel getSubstitutedHttpRequestModel( HttpRequestModel httpRequestModel) { var envMap = ref.read(availableEnvironmentVariablesStateProvider); var activeEnvId = ref.read(activeEnvironmentIdStateProvider); return substituteHttpRequestModel( httpRequestModel, envMap, activeEnvId, ); } }