THATMobile/alice/lib/ui/widget/alice_call_response_widget.dart
2026-02-26 10:39:42 +07:00

265 lines
7.6 KiB
Dart

import 'package:alice/model/alice_http_call.dart';
import 'package:alice/utils/alice_constants.dart';
import 'package:alice/ui/widget/alice_base_call_details_widget.dart';
import 'package:flutter/material.dart';
class AliceCallResponseWidget extends StatefulWidget {
final AliceHttpCall call;
const AliceCallResponseWidget(this.call);
@override
State<StatefulWidget> createState() {
return _AliceCallResponseWidgetState();
}
}
class _AliceCallResponseWidgetState extends AliceBaseCallDetailsWidgetState<AliceCallResponseWidget> {
static const _imageContentType = "image";
static const _videoContentType = "video";
static const _jsonContentType = "json";
static const _xmlContentType = "xml";
static const _textContentType = "text";
static const _kLargeOutputSize = 100000;
bool _showLargeBody = false;
bool _showUnsupportedBody = false;
AliceHttpCall get _call => widget.call;
@override
Widget build(BuildContext context) {
final List<Widget> rows = [];
if (!_call.loading) {
rows.addAll(_buildGeneralDataRows());
rows.addAll(_buildHeadersRows());
rows.addAll(_buildBodyRows());
return Container(
padding: const EdgeInsets.all(6),
child: ListView(children: rows),
);
} else {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [CircularProgressIndicator(), Text("Awaiting response...")],
),
);
}
}
@override
void dispose() {
super.dispose();
}
List<Widget> _buildGeneralDataRows() {
final List<Widget> rows = [];
rows.add(getListRow("Received:", _call.response!.time.toString()));
rows.add(getListRow("Bytes received:", formatBytes(_call.response!.size)));
final status = _call.response!.status;
var statusText = "$status";
if (status == -1) {
statusText = "Error";
}
rows.add(getListRow("Status:", statusText));
return rows;
}
List<Widget> _buildHeadersRows() {
final List<Widget> rows = [];
final headers = _call.response!.headers;
var headersContent = "Headers are empty";
if (headers != null && headers.isNotEmpty) {
headersContent = "";
}
rows.add(getListRow("Headers: ", headersContent));
if (_call.response!.headers != null) {
_call.response!.headers!.forEach((header, value) {
rows.add(getListRow("$header:", value.toString()));
});
}
return rows;
}
List<Widget> _buildBodyRows() {
final List<Widget> rows = [];
if (_isImageResponse()) {
rows.addAll(_buildImageBodyRows());
} else if (_isVideoResponse()) {
rows.addAll(_buildVideoBodyRows());
} else if (_isTextResponse()) {
if (_isLargeResponseBody()) {
rows.addAll(_buildLargeBodyTextRows());
} else {
rows.addAll(_buildTextBodyRows());
}
} else {
rows.addAll(_buildUnknownBodyRows());
}
return rows;
}
List<Widget> _buildImageBodyRows() {
final List<Widget> rows = [];
rows.add(
Column(
children: [
Row(
children: const [
Text(
"Body: Image",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
const SizedBox(height: 8),
Image.network(
_call.uri,
fit: BoxFit.fill,
headers: _buildRequestHeaders(),
loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
const SizedBox(height: 8),
],
),
);
return rows;
}
List<Widget> _buildLargeBodyTextRows() {
final List<Widget> rows = [];
if (_showLargeBody) {
return _buildTextBodyRows();
} else {
rows.add(getListRow("Body:", "Too large to show (${_call.response!.body.toString().length} Bytes)"));
rows.add(const SizedBox(height: 8));
rows.add(
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(AliceConstants.lightRed),
),
onPressed: () {
setState(() {
_showLargeBody = true;
});
},
child: const Text("Show body"),
),
);
rows.add(const SizedBox(height: 8));
rows.add(const Text("Warning! It will take some time to render output."));
}
return rows;
}
List<Widget> _buildTextBodyRows() {
final List<Widget> rows = [];
final headers = _call.response!.headers;
final bodyContent = formatBody(_call.response!.body, getContentType(headers));
rows.add(getListRow("Body:", bodyContent));
return rows;
}
List<Widget> _buildVideoBodyRows() {
final List<Widget> rows = [];
rows.add(
Row(
children: const [
Text(
"Body: Video",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
);
rows.add(const SizedBox(height: 8));
rows.add(const SizedBox(height: 8));
return rows;
}
List<Widget> _buildUnknownBodyRows() {
final List<Widget> rows = [];
final headers = _call.response!.headers;
final contentType = getContentType(headers) ?? "<unknown>";
if (_showUnsupportedBody) {
final bodyContent = formatBody(_call.response!.body, getContentType(headers));
rows.add(getListRow("Body:", bodyContent));
} else {
rows.add(getListRow(
"Body:",
"Unsupported body. Alice can render video/image/text body. "
"Response has Content-Type: $contentType which can't be handled. "
"If you're feeling lucky you can try button below to try render body"
" as text, but it may fail."));
rows.add(
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(AliceConstants.lightRed),
),
onPressed: () {
setState(() {
_showUnsupportedBody = true;
});
},
child: const Text("Show unsupported body"),
),
);
}
return rows;
}
Map<String, String> _buildRequestHeaders() {
final Map<String, String> requestHeaders = {};
if (_call.request?.headers != null) {
requestHeaders.addAll(
_call.request!.headers.map(
(String key, dynamic value) {
return MapEntry(key, value.toString());
},
),
);
}
return requestHeaders;
}
bool _isImageResponse() {
return _getContentTypeOfResponse()!.toLowerCase().contains(_imageContentType);
}
bool _isVideoResponse() {
return _getContentTypeOfResponse()!.toLowerCase().contains(_videoContentType);
}
bool _isTextResponse() {
final String responseContentTypeLowerCase = _getContentTypeOfResponse()!.toLowerCase();
return responseContentTypeLowerCase.contains(_jsonContentType) ||
responseContentTypeLowerCase.contains(_xmlContentType) ||
responseContentTypeLowerCase.contains(_textContentType);
}
String? _getContentTypeOfResponse() {
return getContentType(_call.response!.headers);
}
bool _isLargeResponseBody() {
return _call.response!.body != null && _call.response!.body.toString().length > _kLargeOutputSize;
}
}