return domain-specific models from choreo-related repos, use future builders on async data when possbile
This commit is contained in:
parent
4b9fd02baf
commit
4eebc4b820
12 changed files with 185 additions and 247 deletions
|
|
@ -108,11 +108,7 @@ class IgcController {
|
|||
}
|
||||
|
||||
if (!_isFetching) return;
|
||||
final response = res.result!;
|
||||
_igcTextData = IGCTextData(
|
||||
originalInput: response.originalInput,
|
||||
matches: response.matches,
|
||||
);
|
||||
_igcTextData = res.result!;
|
||||
_isFetching = false;
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +143,7 @@ class IgcController {
|
|||
throw response.error!;
|
||||
}
|
||||
|
||||
_igcTextData?.setSpanData(match, response.result!.span);
|
||||
_igcTextData?.setSpanData(match, response.result!);
|
||||
}
|
||||
|
||||
Future<void> fetchAllSpanDetails() async {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:http/http.dart';
|
|||
|
||||
import 'package:fluffychat/pangea/choreographer/igc/igc_request_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/igc_response_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/igc_text_data_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
|
|
@ -14,7 +15,7 @@ import '../../common/network/requests.dart';
|
|||
import '../../common/network/urls.dart';
|
||||
|
||||
class _IgcCacheItem {
|
||||
final Future<IGCResponseModel> data;
|
||||
final Future<IGCTextData> data;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _IgcCacheItem({
|
||||
|
|
@ -52,7 +53,7 @@ class IgcRepo {
|
|||
static final Map<String, _IgnoredMatchCacheItem> _ignoredMatchCache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<IGCResponseModel>> get(
|
||||
static Future<Result<IGCTextData>> get(
|
||||
String? accessToken,
|
||||
IGCRequestModel igcRequest,
|
||||
) {
|
||||
|
|
@ -69,7 +70,7 @@ class IgcRepo {
|
|||
return _getResult(igcRequest, future);
|
||||
}
|
||||
|
||||
static Future<IGCResponseModel> _fetch(
|
||||
static Future<IGCTextData> _fetch(
|
||||
String? accessToken, {
|
||||
required IGCRequestModel igcRequest,
|
||||
}) async {
|
||||
|
|
@ -91,12 +92,16 @@ class IgcRepo {
|
|||
final Map<String, dynamic> json =
|
||||
jsonDecode(utf8.decode(res.bodyBytes).toString());
|
||||
|
||||
return IGCResponseModel.fromJson(json);
|
||||
final respModel = IGCResponseModel.fromJson(json);
|
||||
return IGCTextData(
|
||||
originalInput: respModel.originalInput,
|
||||
matches: respModel.matches,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Result<IGCResponseModel>> _getResult(
|
||||
static Future<Result<IGCTextData>> _getResult(
|
||||
IGCRequestModel request,
|
||||
Future<IGCResponseModel> future,
|
||||
Future<IGCTextData> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
|
|
@ -112,7 +117,7 @@ class IgcRepo {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<IGCResponseModel>? _getCached(
|
||||
static Future<IGCTextData>? _getCached(
|
||||
IGCRequestModel request,
|
||||
) {
|
||||
final cacheKeys = [..._igcCache.keys];
|
||||
|
|
@ -129,7 +134,7 @@ class IgcRepo {
|
|||
|
||||
static void _setCached(
|
||||
IGCRequestModel request,
|
||||
Future<IGCResponseModel> response,
|
||||
Future<IGCTextData> response,
|
||||
) =>
|
||||
_igcCache[request.hashCode.toString()] = _IgcCacheItem(
|
||||
data: response,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import 'package:fluffychat/pangea/choreographer/choreographer.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/igc/pangea_match_state_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_choice_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/async_state.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/feedback_model.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../common/widgets/choice_array.dart';
|
||||
|
|
@ -32,7 +32,8 @@ class SpanCard extends StatefulWidget {
|
|||
|
||||
class SpanCardState extends State<SpanCard> {
|
||||
bool _loadingChoices = true;
|
||||
final _feedbackModel = FeedbackModel<String>();
|
||||
final ValueNotifier<AsyncState<String>> _feedbackState =
|
||||
ValueNotifier<AsyncState<String>>(const AsyncIdle<String>());
|
||||
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_feedbackModel.dispose();
|
||||
_feedbackState.dispose();
|
||||
scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
|
@ -62,19 +63,21 @@ class SpanCardState extends State<SpanCard> {
|
|||
return;
|
||||
}
|
||||
|
||||
setState(() => _loadingChoices = true);
|
||||
|
||||
try {
|
||||
setState(() => _loadingChoices = true);
|
||||
await widget.choreographer.igcController.fetchSpanDetails(
|
||||
match: widget.match,
|
||||
);
|
||||
} catch (e) {
|
||||
widget.choreographer.clearMatches(e);
|
||||
} finally {
|
||||
|
||||
if (_choices == null || _choices!.isEmpty) {
|
||||
widget.choreographer.clearMatches(
|
||||
'No choices available for span ${widget.match.updatedMatch.match.message}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
widget.choreographer.clearMatches(e);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _loadingChoices = false);
|
||||
}
|
||||
|
|
@ -83,29 +86,28 @@ class SpanCardState extends State<SpanCard> {
|
|||
|
||||
Future<void> _fetchFeedback() async {
|
||||
if (_selectedFeedback != null) {
|
||||
_feedbackModel.setState(FeedbackLoaded<String>(_selectedFeedback!));
|
||||
_feedbackState.value = AsyncLoaded<String>(_selectedFeedback!);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
_feedbackModel.setState(FeedbackLoading<String>());
|
||||
_feedbackState.value = const AsyncLoading<String>();
|
||||
await widget.choreographer.igcController.fetchSpanDetails(
|
||||
match: widget.match,
|
||||
force: true,
|
||||
);
|
||||
} finally {
|
||||
|
||||
if (!mounted) return;
|
||||
if (_selectedFeedback != null) {
|
||||
_feedbackState.value = AsyncLoaded<String>(_selectedFeedback!);
|
||||
} else {
|
||||
_feedbackState.value = AsyncError<String>(
|
||||
L10n.of(context).failedToLoadFeedback,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
if (_selectedFeedback == null) {
|
||||
_feedbackModel.setState(
|
||||
FeedbackError<String>(
|
||||
L10n.of(context).failedToLoadFeedback,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
_feedbackModel.setState(
|
||||
FeedbackLoaded<String>(_selectedFeedback!),
|
||||
);
|
||||
}
|
||||
_feedbackState.value = AsyncError<String>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -113,11 +115,11 @@ class SpanCardState extends State<SpanCard> {
|
|||
void _onChoiceSelect(int index) {
|
||||
final selected = _choices![index];
|
||||
widget.match.selectChoice(index);
|
||||
_feedbackModel.setState(
|
||||
selected.feedback != null
|
||||
? FeedbackLoaded<String>(selected.feedback!)
|
||||
: FeedbackIdle<String>(),
|
||||
);
|
||||
|
||||
_feedbackState.value = selected.feedback != null
|
||||
? AsyncLoaded<String>(selected.feedback!)
|
||||
: const AsyncIdle<String>();
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +187,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
_SpanCardFeedback(
|
||||
_selectedChoice != null,
|
||||
_fetchFeedback,
|
||||
_feedbackModel,
|
||||
_feedbackState,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -207,12 +209,12 @@ class SpanCardState extends State<SpanCard> {
|
|||
class _SpanCardFeedback extends StatelessWidget {
|
||||
final bool hasSelectedChoice;
|
||||
final VoidCallback fetchFeedback;
|
||||
final FeedbackModel<String> feedbackModel;
|
||||
final ValueNotifier<AsyncState<String>> feedbackState;
|
||||
|
||||
const _SpanCardFeedback(
|
||||
this.hasSelectedChoice,
|
||||
this.fetchFeedback,
|
||||
this.feedbackModel,
|
||||
this.feedbackState,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
@ -224,12 +226,11 @@ class _SpanCardFeedback extends StatelessWidget {
|
|||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ListenableBuilder(
|
||||
listenable: feedbackModel,
|
||||
builder: (context, _) {
|
||||
final state = feedbackModel.state;
|
||||
ValueListenableBuilder(
|
||||
valueListenable: feedbackState,
|
||||
builder: (context, state, __) {
|
||||
return switch (state) {
|
||||
FeedbackIdle<String>() => hasSelectedChoice
|
||||
AsyncIdle<String>() => hasSelectedChoice
|
||||
? IconButton(
|
||||
onPressed: fetchFeedback,
|
||||
icon: const Icon(Icons.lightbulb_outline, size: 24),
|
||||
|
|
@ -240,14 +241,14 @@ class _SpanCardFeedback extends StatelessWidget {
|
|||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
FeedbackLoading<String>() => const SizedBox(
|
||||
AsyncLoading<String>() => const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
FeedbackError<String>(:final error) =>
|
||||
AsyncError<String>(:final error) =>
|
||||
ErrorIndicator(message: error.toString()),
|
||||
FeedbackLoaded<String>(:final value) =>
|
||||
AsyncLoaded<String>(:final value) =>
|
||||
Text(value, style: BotStyle.text(context)),
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||
import 'package:async/async.dart';
|
||||
import 'package:http/http.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_request.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/igc/span_data_response.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
|
|
@ -11,7 +12,7 @@ import '../../common/network/requests.dart';
|
|||
import '../../common/network/urls.dart';
|
||||
|
||||
class _SpanDetailsCacheItem {
|
||||
final Future<SpanDetailsResponse> data;
|
||||
final Future<SpanData> data;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _SpanDetailsCacheItem({
|
||||
|
|
@ -24,7 +25,7 @@ class SpanDataRepo {
|
|||
static final Map<String, _SpanDetailsCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<SpanDetailsResponse>> get(
|
||||
static Future<Result<SpanData>> get(
|
||||
String? accessToken, {
|
||||
required SpanDetailsRequest request,
|
||||
}) async {
|
||||
|
|
@ -41,7 +42,7 @@ class SpanDataRepo {
|
|||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<SpanDetailsResponse> _fetch(
|
||||
static Future<SpanData> _fetch(
|
||||
String? accessToken, {
|
||||
required SpanDetailsRequest request,
|
||||
}) async {
|
||||
|
|
@ -59,14 +60,15 @@ class SpanDataRepo {
|
|||
throw Exception('Failed to load span details');
|
||||
}
|
||||
|
||||
return SpanDetailsResponse.fromJson(
|
||||
final respModel = SpanDetailsResponse.fromJson(
|
||||
jsonDecode(utf8.decode(res.bodyBytes)),
|
||||
);
|
||||
return respModel.span;
|
||||
}
|
||||
|
||||
static Future<Result<SpanDetailsResponse>> _getResult(
|
||||
static Future<Result<SpanData>> _getResult(
|
||||
SpanDetailsRequest request,
|
||||
Future<SpanDetailsResponse> future,
|
||||
Future<SpanData> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
|
|
@ -82,7 +84,7 @@ class SpanDataRepo {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<SpanDetailsResponse>? _getCached(
|
||||
static Future<SpanData>? _getCached(
|
||||
SpanDetailsRequest request,
|
||||
) {
|
||||
final cacheKeys = [..._cache.keys];
|
||||
|
|
@ -96,7 +98,7 @@ class SpanDataRepo {
|
|||
|
||||
static void _setCached(
|
||||
SpanDetailsRequest request,
|
||||
Future<SpanDetailsResponse> response,
|
||||
Future<SpanData> response,
|
||||
) {
|
||||
_cache[request.hashCode.toString()] = _SpanDetailsCacheItem(
|
||||
data: response,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import '../../common/network/requests.dart';
|
|||
import '../../common/network/urls.dart';
|
||||
|
||||
class ContextualDefinitionRepo {
|
||||
static final Map<String, Future<ContextualDefinitionResponseModel>> _cache =
|
||||
{};
|
||||
static final Map<String, Future<String>> _cache = {};
|
||||
|
||||
static Future<Result<ContextualDefinitionResponseModel>> get(
|
||||
static Future<Result<String>> get(
|
||||
String accessToken,
|
||||
ContextualDefinitionRequestModel request,
|
||||
) async {
|
||||
|
|
@ -38,7 +37,7 @@ class ContextualDefinitionRepo {
|
|||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<ContextualDefinitionResponseModel> _fetch(
|
||||
static Future<String> _fetch(
|
||||
String accessToken,
|
||||
ContextualDefinitionRequestModel request,
|
||||
) async {
|
||||
|
|
@ -77,12 +76,12 @@ class ContextualDefinitionRepo {
|
|||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
return response.text;
|
||||
}
|
||||
|
||||
static Future<Result<ContextualDefinitionResponseModel>> _getResult(
|
||||
static Future<Result<String>> _getResult(
|
||||
ContextualDefinitionRequestModel request,
|
||||
Future<ContextualDefinitionResponseModel> future,
|
||||
Future<String> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
|
|
@ -98,14 +97,14 @@ class ContextualDefinitionRepo {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<ContextualDefinitionResponseModel>? _getCached(
|
||||
static Future<String>? _getCached(
|
||||
ContextualDefinitionRequestModel request,
|
||||
) =>
|
||||
_cache[request.hashCode.toString()];
|
||||
|
||||
static void _setCached(
|
||||
ContextualDefinitionRequestModel request,
|
||||
Future<ContextualDefinitionResponseModel> response,
|
||||
Future<String> response,
|
||||
) =>
|
||||
_cache[request.hashCode.toString()] = response;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,9 +113,8 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
cardToShow: choiceFeedback == null
|
||||
? WordDataCard(
|
||||
word: text,
|
||||
wordLang: l2Code,
|
||||
langCode: l2Code,
|
||||
fullText: _sourceText.value ?? widget.choreographer.currentText,
|
||||
fullTextLang: l2Code,
|
||||
)
|
||||
: ITFeedbackCard(
|
||||
FullTextTranslationRequestModel(
|
||||
|
|
@ -129,7 +128,7 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
maxWidth: 300,
|
||||
borderColor: borderColor,
|
||||
transformTargetId: 'it_bar',
|
||||
isScrollable: choiceFeedback == null,
|
||||
isScrollable: false,
|
||||
overlayKey: "it_feedback_card",
|
||||
ignorePointer: true,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:async/async.dart';
|
|||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/feedback_model.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart';
|
||||
import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
|
@ -14,7 +13,7 @@ import '../../../widgets/matrix.dart';
|
|||
import '../../bot/utils/bot_style.dart';
|
||||
import '../../common/widgets/card_error_widget.dart';
|
||||
|
||||
class ITFeedbackCard extends StatefulWidget {
|
||||
class ITFeedbackCard extends StatelessWidget {
|
||||
final FullTextTranslationRequestModel req;
|
||||
|
||||
const ITFeedbackCard(
|
||||
|
|
@ -22,56 +21,22 @@ class ITFeedbackCard extends StatefulWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ITFeedbackCard> createState() => ITFeedbackCardController();
|
||||
}
|
||||
|
||||
class ITFeedbackCardController extends State<ITFeedbackCard> {
|
||||
final FeedbackModel<String> _feedbackModel = FeedbackModel<String>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getFeedback();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_feedbackModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _getFeedback() async {
|
||||
_feedbackModel.setState(FeedbackLoading());
|
||||
final result = await FullTextTranslationRepo.get(
|
||||
Future<Result<String>> _getFeedback() {
|
||||
return FullTextTranslationRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
widget.req,
|
||||
req,
|
||||
).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
return Result.error("Timeout getting translation");
|
||||
},
|
||||
onTimeout: () => Result.error("Timeout getting translation"),
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
if (result.isError) {
|
||||
_feedbackModel.setState(
|
||||
FeedbackError<String>(result.error.toString()),
|
||||
);
|
||||
} else {
|
||||
_feedbackModel.setState(
|
||||
FeedbackLoaded<String>(result.result!.bestTranslation),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: _feedbackModel,
|
||||
builder: (context, _) {
|
||||
final state = _feedbackModel.state;
|
||||
if (state is FeedbackError) {
|
||||
return FutureBuilder<Result<String>>(
|
||||
future: _getFeedback(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return CardErrorWidget(L10n.of(context).errorFetchingDefinition);
|
||||
}
|
||||
|
||||
|
|
@ -83,22 +48,22 @@ class ITFeedbackCardController extends State<ITFeedbackCard> {
|
|||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
widget.req.text,
|
||||
req.text,
|
||||
style: BotStyle.text(context),
|
||||
),
|
||||
Text(
|
||||
"≈",
|
||||
style: BotStyle.text(context),
|
||||
),
|
||||
_feedbackModel.state is FeedbackLoaded
|
||||
snapshot.hasData
|
||||
? Text(
|
||||
(state as FeedbackLoaded<String>).value,
|
||||
snapshot.data!.result!,
|
||||
style: BotStyle.text(context),
|
||||
)
|
||||
: TextLoadingShimmer(
|
||||
width: min(
|
||||
140,
|
||||
10.0 * widget.req.text.length,
|
||||
10.0 * req.text.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,120 +2,65 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:async/async.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_style.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/it/contextual_definition_request_model.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/feedback_model.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/toolbar_content_loading_indicator.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class WordDataCard extends StatefulWidget {
|
||||
class WordDataCard extends StatelessWidget {
|
||||
final String word;
|
||||
final String fullText;
|
||||
final String wordLang;
|
||||
final String fullTextLang;
|
||||
final String langCode;
|
||||
|
||||
const WordDataCard({
|
||||
super.key,
|
||||
required this.word,
|
||||
required this.fullText,
|
||||
required this.wordLang,
|
||||
required this.fullTextLang,
|
||||
required this.langCode,
|
||||
});
|
||||
|
||||
@override
|
||||
State<WordDataCard> createState() => WordDataCardController();
|
||||
}
|
||||
|
||||
class WordDataCardController extends State<WordDataCard> {
|
||||
final FeedbackModel<String> _feedbackModel = FeedbackModel<String>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_getContextualDefinition();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant WordDataCard oldWidget) {
|
||||
if (oldWidget.word != widget.word) {
|
||||
_getContextualDefinition();
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_feedbackModel.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
ContextualDefinitionRequestModel get _request =>
|
||||
ContextualDefinitionRequestModel(
|
||||
fullText: widget.fullText,
|
||||
word: widget.word,
|
||||
fullTextLang: widget.fullTextLang,
|
||||
wordLang: widget.wordLang,
|
||||
feedbackLang:
|
||||
MatrixState.pangeaController.languageController.activeL1Code() ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
|
||||
Future<void> _getContextualDefinition() async {
|
||||
_feedbackModel.setState(FeedbackLoading<String>());
|
||||
final resp = await ContextualDefinitionRepo.get(
|
||||
Future<Result<String>> _fetchDefinition() {
|
||||
final request = ContextualDefinitionRequestModel(
|
||||
fullText: fullText,
|
||||
word: word,
|
||||
fullTextLang: langCode,
|
||||
wordLang: langCode,
|
||||
feedbackLang:
|
||||
MatrixState.pangeaController.languageController.activeL1Code() ??
|
||||
LanguageKeys.defaultLanguage,
|
||||
);
|
||||
return ContextualDefinitionRepo.get(
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
_request,
|
||||
request,
|
||||
).timeout(
|
||||
const Duration(seconds: 10),
|
||||
onTimeout: () {
|
||||
return Result.error("Timeout getting definition");
|
||||
},
|
||||
onTimeout: () => Result.error("Timeout getting definition"),
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
if (resp.isError) {
|
||||
_feedbackModel.setState(
|
||||
const FeedbackError<String>("Error getting definition"),
|
||||
);
|
||||
} else {
|
||||
_feedbackModel.setState(FeedbackLoaded<String>(resp.result!.text));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: AppConfig.toolbarMinWidth,
|
||||
maxHeight: AppConfig.toolbarMaxHeight,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: ListenableBuilder(
|
||||
listenable: _feedbackModel,
|
||||
builder: (context, _) {
|
||||
final state = _feedbackModel.state;
|
||||
return switch (state) {
|
||||
FeedbackIdle<String>() => const SizedBox.shrink(),
|
||||
FeedbackLoading<String>() =>
|
||||
const ToolbarContentLoadingIndicator(),
|
||||
FeedbackError<String>() => Text(
|
||||
L10n.of(context).sorryNoResults,
|
||||
style: BotStyle.text(context),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
FeedbackLoaded<String>(:final value) =>
|
||||
Text(value, style: BotStyle.text(context)),
|
||||
};
|
||||
},
|
||||
),
|
||||
),
|
||||
return Center(
|
||||
child: FutureBuilder<Result<String>>(
|
||||
future: _fetchDefinition(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const ToolbarContentLoadingIndicator();
|
||||
}
|
||||
final result = snapshot.data!;
|
||||
if (result.isError) {
|
||||
return Text(
|
||||
L10n.of(context).sorryNoResults,
|
||||
style: BotStyle.text(context),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
return Text(result.result!, style: BotStyle.text(context));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
55
lib/pangea/common/utils/async_state.dart
Normal file
55
lib/pangea/common/utils/async_state.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/// A generic sealed class that represents the state of an asynchronous operation.
|
||||
sealed class AsyncState<T> {
|
||||
/// Base constructor for all asynchronous state variants.
|
||||
const AsyncState();
|
||||
|
||||
/// Represents an idle state before any asynchronous work has begun.
|
||||
const factory AsyncState.idle() = AsyncIdle<T>;
|
||||
|
||||
/// Represents an in-progress loading state.
|
||||
const factory AsyncState.loading() = AsyncLoading<T>;
|
||||
|
||||
/// Represents a completed asynchronous operation with a successful [value].
|
||||
const factory AsyncState.loaded(T value) = AsyncLoaded<T>;
|
||||
|
||||
/// Represents a failed asynchronous operation with an [error].
|
||||
const factory AsyncState.error(Object error) = AsyncError<T>;
|
||||
}
|
||||
|
||||
/// The idle state of an [AsyncState], indicating no active or completed work.
|
||||
///
|
||||
/// Use this as the initial state before triggering an async operation.
|
||||
class AsyncIdle<T> extends AsyncState<T> {
|
||||
/// Creates an idle [AsyncState].
|
||||
const AsyncIdle();
|
||||
}
|
||||
|
||||
/// The loading state of an [AsyncState], indicating that work is in progress.
|
||||
///
|
||||
/// This state is typically used to show a loading spinner or progress indicator.
|
||||
class AsyncLoading<T> extends AsyncState<T> {
|
||||
/// Creates a loading [AsyncState].
|
||||
const AsyncLoading();
|
||||
}
|
||||
|
||||
/// The success state of an [AsyncState], containing a completed [value].
|
||||
///
|
||||
/// This state indicates that the asynchronous work finished successfully.
|
||||
class AsyncLoaded<T> extends AsyncState<T> {
|
||||
/// The result of the successful asynchronous operation.
|
||||
final T value;
|
||||
|
||||
/// Creates a loaded [AsyncState] with a [value].
|
||||
const AsyncLoaded(this.value);
|
||||
}
|
||||
|
||||
/// The error state of an [AsyncState], containing an [error].
|
||||
///
|
||||
/// This state indicates that the asynchronous work failed.
|
||||
class AsyncError<T> extends AsyncState<T> {
|
||||
/// The error produced during the asynchronous operation.
|
||||
final Object error;
|
||||
|
||||
/// Creates an error [AsyncState] with an [error].
|
||||
const AsyncError(this.error);
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
sealed class FeedbackState<T> {
|
||||
const FeedbackState();
|
||||
}
|
||||
|
||||
class FeedbackIdle<T> extends FeedbackState<T> {}
|
||||
|
||||
class FeedbackLoading<T> extends FeedbackState<T> {}
|
||||
|
||||
class FeedbackLoaded<T> extends FeedbackState<T> {
|
||||
final T value;
|
||||
const FeedbackLoaded(this.value);
|
||||
}
|
||||
|
||||
class FeedbackError<T> extends FeedbackState<T> {
|
||||
final Object error;
|
||||
const FeedbackError(this.error);
|
||||
}
|
||||
|
||||
class FeedbackModel<T> extends ChangeNotifier {
|
||||
FeedbackState<T> _state = FeedbackIdle<T>();
|
||||
FeedbackState<T> get state => _state;
|
||||
|
||||
void setState(FeedbackState<T> newState) {
|
||||
_state = newState;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ class MessageDataController extends BaseController {
|
|||
|
||||
final rep = PangeaRepresentation(
|
||||
langCode: req.tgtLang,
|
||||
text: res.result!.bestTranslation,
|
||||
text: res.result!,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
);
|
||||
|
|
@ -131,7 +131,7 @@ class MessageDataController extends BaseController {
|
|||
|
||||
final rep = PangeaRepresentation(
|
||||
langCode: req.tgtLang,
|
||||
text: res.result!.bestTranslation,
|
||||
text: res.result!,
|
||||
originalSent: originalSent,
|
||||
originalWritten: false,
|
||||
);
|
||||
|
|
@ -180,7 +180,7 @@ class MessageDataController extends BaseController {
|
|||
}
|
||||
|
||||
final translation = SttTranslationModel(
|
||||
translation: res.result!.bestTranslation,
|
||||
translation: res.result!,
|
||||
langCode: req.tgtLang,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import '../common/network/requests.dart';
|
|||
import '../common/network/urls.dart';
|
||||
|
||||
class _TranslateCacheItem {
|
||||
final Future<FullTextTranslationResponseModel> response;
|
||||
final Future<String> response;
|
||||
final DateTime timestamp;
|
||||
|
||||
const _TranslateCacheItem({
|
||||
|
|
@ -27,7 +27,7 @@ class FullTextTranslationRepo {
|
|||
static final Map<String, _TranslateCacheItem> _cache = {};
|
||||
static const Duration _cacheDuration = Duration(minutes: 10);
|
||||
|
||||
static Future<Result<FullTextTranslationResponseModel>> get(
|
||||
static Future<Result<String>> get(
|
||||
String accessToken,
|
||||
FullTextTranslationRequestModel request,
|
||||
) {
|
||||
|
|
@ -41,7 +41,7 @@ class FullTextTranslationRepo {
|
|||
return _getResult(request, future);
|
||||
}
|
||||
|
||||
static Future<FullTextTranslationResponseModel> _fetch(
|
||||
static Future<String> _fetch(
|
||||
String accessToken,
|
||||
FullTextTranslationRequestModel request,
|
||||
) async {
|
||||
|
|
@ -63,12 +63,12 @@ class FullTextTranslationRepo {
|
|||
|
||||
return FullTextTranslationResponseModel.fromJson(
|
||||
jsonDecode(utf8.decode(res.bodyBytes)),
|
||||
);
|
||||
).bestTranslation;
|
||||
}
|
||||
|
||||
static Future<Result<FullTextTranslationResponseModel>> _getResult(
|
||||
static Future<Result<String>> _getResult(
|
||||
FullTextTranslationRequestModel request,
|
||||
Future<FullTextTranslationResponseModel> future,
|
||||
Future<String> future,
|
||||
) async {
|
||||
try {
|
||||
final res = await future;
|
||||
|
|
@ -84,7 +84,7 @@ class FullTextTranslationRepo {
|
|||
}
|
||||
}
|
||||
|
||||
static Future<FullTextTranslationResponseModel>? _getCached(
|
||||
static Future<String>? _getCached(
|
||||
FullTextTranslationRequestModel request,
|
||||
) {
|
||||
final cacheKeys = [..._cache.keys];
|
||||
|
|
@ -99,7 +99,7 @@ class FullTextTranslationRepo {
|
|||
|
||||
static void _setCached(
|
||||
FullTextTranslationRequestModel request,
|
||||
Future<FullTextTranslationResponseModel> response,
|
||||
Future<String> response,
|
||||
) =>
|
||||
_cache[request.hashCode.toString()] = _TranslateCacheItem(
|
||||
response: response,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue