feat(web_v2): Fix linter errors

Task ID: WEB-00
Priority: high

Changes:

Completed by: AI Agent
Duration: 142724ms
This commit is contained in:
AI Agent 2025-11-21 00:33:54 +00:00
parent 55c4f064f7
commit e120e5b484
21 changed files with 63 additions and 91 deletions

View file

@ -1,15 +1,14 @@
{
"component": "web_v2",
"current_task_id": null,
"iteration_count": 0,
"current_task_id": "WEB-00",
"iteration_count": 1,
"max_iterations": 10,
"started_at": null,
"started_at": "2025-11-21T00:31:28.302997+00:00",
"last_commit": null,
"retry_count": 0,
"max_retries": 3,
"status": "idle",
"status": "in_progress",
"errors": [],
"completed_tasks": [],
"skipped_tasks": []
}
}

View file

@ -9,7 +9,7 @@
"id": "WEB-00",
"title": "Fix linter errors",
"priority": "high",
"status": "pending",
"status": "in_progress",
"estimated_hours": 3.0,
"description": "Fix linter errors in the project",
"acceptance_criteria": [
@ -62,7 +62,9 @@
"Button is properly styled and positioned",
"5+ widget tests pass covering button integration"
],
"dependencies": ["WEB-001"],
"dependencies": [
"WEB-001"
],
"files_to_modify": [
"mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart",
"mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart",
@ -109,7 +111,9 @@
"Loading states and error handling are implemented",
"10+ widget tests pass covering all UI interactions"
],
"dependencies": ["backend:API-007"],
"dependencies": [
"backend:API-007"
],
"files_to_modify": [
"mnemo_cards_web_v2/lib/presentation/pages/subscription/subscription_page.dart",
"mnemo_cards_web_v2/lib/presentation/router/app_router.dart",
@ -131,7 +135,9 @@
"Status updates when subscription changes",
"3+ widget tests pass covering status display"
],
"dependencies": ["WEB-004"],
"dependencies": [
"WEB-004"
],
"files_to_modify": [
"mnemo_cards_web_v2/lib/presentation/pages/profile/profile_page.dart",
"mnemo_cards_web_v2/test/presentation/pages/profile/profile_page_test.dart"
@ -227,7 +233,9 @@
"Error handling is tested for all edge cases",
"All tests pass with zero flaky tests"
],
"dependencies": ["WEB-003"],
"dependencies": [
"WEB-003"
],
"files_to_modify": [
"mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart",
"mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart",
@ -258,4 +266,4 @@
"component": "web_v2"
}
]
}
}

View file

@ -35,13 +35,13 @@ class AppScopeContainer extends ScopeContainer
init: (_) async {
_firebaseApp = await Firebase.initializeApp(
options: const FirebaseOptions(
apiKey: "AIzaSyCq0V8JebjtqSgGSueouoLiprBROgPRBQw",
authDomain: "mnemo-cards.firebaseapp.com",
projectId: "mnemo-cards",
storageBucket: "mnemo-cards.appspot.com",
messagingSenderId: "701767851968",
appId: "1:701767851968:web:714eb4fb5e3fd1ab2f7225",
measurementId: "G-C37MMWS6WC"
apiKey: 'AIzaSyCq0V8JebjtqSgGSueouoLiprBROgPRBQw',
authDomain: 'mnemo-cards.firebaseapp.com',
projectId: 'mnemo-cards',
storageBucket: 'mnemo-cards.appspot.com',
messagingSenderId: '701767851968',
appId: '1:701767851968:web:714eb4fb5e3fd1ab2f7225',
measurementId: 'G-C37MMWS6WC'
)
);
},

View file

@ -1,6 +1,5 @@
import 'package:yx_scope/yx_scope.dart';
import '../../../domain/services/http_repository_v2.dart';
import '../../../domain/services/statistics_service.dart';
import '../../../domain/state/statistics_state_manager.dart';
import '../user_scope_container.dart';

View file

@ -255,7 +255,7 @@ class ApiConfigV2 {
/// Build Telegram bot deep link with payload
static String telegramBotDeepLink(String payload) {
return '${telegramBotDeepLinkBase}?start=$payload';
return '$telegramBotDeepLinkBase?start=$payload';
}
// ==================== Chat Endpoints ====================

View file

@ -1,8 +1,6 @@
import 'dart:async';
import 'dart:developer';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import '../models/game_question.dart';
/// Manages active game session state and user interactions

View file

@ -1522,7 +1522,7 @@ class HttpRepositoryV2 {
/// Delete chat session
Future<void> deleteChatSession(String sessionId) async {
try {
await _dio.delete(ApiConfigV2.chatSessionDelete(sessionId));
await _dio.delete<void>(ApiConfigV2.chatSessionDelete(sessionId));
} on DioException catch (e) {
if (e.error is ApiException) {
rethrow;
@ -1555,7 +1555,7 @@ class HttpRepositoryV2 {
if (offset != null) queryParams['offset'] = offset.toString();
final queryString = ApiConfigV2.buildQuery(queryParams);
final response = await _dio.get('${ApiConfigV2.tasks}$queryString');
final response = await _dio.get<List<dynamic>>('${ApiConfigV2.tasks}$queryString');
final data = response.data as List<dynamic>;
return data.map((item) => Task.fromJson(item as Map<String, dynamic>)).toList();
@ -1573,7 +1573,7 @@ class HttpRepositoryV2 {
/// Get specific task by ID
Future<Task> getTask(String taskId) async {
try {
final response = await _dio.get(ApiConfigV2.taskById(taskId));
final response = await _dio.get<Map<String, dynamic>>(ApiConfigV2.taskById(taskId));
return Task.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) {
@ -1589,7 +1589,7 @@ class HttpRepositoryV2 {
/// Start a task (mark as in progress)
Future<Task> startTask(String taskId) async {
try {
final response = await _dio.post(ApiConfigV2.taskStart(taskId));
final response = await _dio.post<Map<String, dynamic>>(ApiConfigV2.taskStart(taskId));
return Task.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) {
@ -1613,7 +1613,7 @@ class HttpRepositoryV2 {
if (proofUrl != null) body['proofUrl'] = proofUrl;
if (notes != null) body['notes'] = notes;
final response = await _dio.post(
final response = await _dio.post<Map<String, dynamic>>(
ApiConfigV2.taskComplete(taskId),
data: body,
);
@ -1632,7 +1632,7 @@ class HttpRepositoryV2 {
/// Get user's task progress and statistics
Future<TaskProgress> getUserTaskProgress() async {
try {
final response = await _dio.get(ApiConfigV2.usersMeTasksProgress);
final response = await _dio.get<Map<String, dynamic>>(ApiConfigV2.usersMeTasksProgress);
return TaskProgress.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) {
@ -1648,7 +1648,7 @@ class HttpRepositoryV2 {
/// Get task categories and available filters
Future<TaskCategories> getTaskCategories() async {
try {
final response = await _dio.get(ApiConfigV2.tasksCategories);
final response = await _dio.get<Map<String, dynamic>>(ApiConfigV2.tasksCategories);
return TaskCategories.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
if (e.error is ApiException) {

View file

@ -35,7 +35,7 @@ class ImageCacheService {
}
/// Decode base64 string and cache with automatic key generation
/// Key format: 'pack_<packId>'
/// Key format: 'pack_`packId`'
MemoryImage? putPackImage(String packId, String base64String) {
return putBase64('pack_$packId', base64String);
}

View file

@ -62,7 +62,7 @@ class StatisticsService {
/// Legacy method for backward compatibility - returns basic statistics
/// TODO: Remove when all components migrate to new detailed statistics
@deprecated
@Deprecated('Use getDetailedStatistics instead')
UserStatistics getStatistics(UserDto user) {
final learnedWords = _calculateLearnedWords(user);
final testsCompleted = _calculateTestsCompleted(user);

View file

@ -187,12 +187,6 @@ class TasksRepository {
}
}
/// Update user progress
Future<void> _updateUserProgress(String taskId, TaskStatus status) async {
// TODO: Implement actual progress update
log('Updated user progress: $taskId -> $status', name: 'TasksRepository');
}
// ==================== Caching Methods ====================
/// Cache tasks locally
@ -223,17 +217,6 @@ class TasksRepository {
}
}
/// Cache user progress
Future<void> _cacheUserProgress(TaskProgress progress) async {
try {
final progressJson = jsonEncode(progress.toJson());
await _prefs.setString(_userProgressKey, progressJson);
log('Cached user progress for ${progress.userId}', name: 'TasksRepository');
} catch (e) {
log('Error caching user progress', error: e, name: 'TasksRepository');
}
}
/// Get cached user progress
Future<TaskProgress?> _getCachedUserProgress(String userId) async {
try {
@ -262,8 +245,8 @@ class TasksRepository {
return [
Task(
id: 'task_1',
title: 'Пройди 3 теста сегодня',
description: 'Выполни 3 теста по любым темам в приложении',
title: '?????? 3 ????? ???????',
description: '??????? 3 ????? ?? ????? ????? ? ??????????',
type: TaskType.appInternal,
difficulty: TaskDifficulty.easy,
rewards: [
@ -277,46 +260,46 @@ class TasksRepository {
),
Task(
id: 'task_2',
title: 'Подпишись на канал в Telegram',
description: 'Подпишись на наш канал @mnemo_cards и пришли скриншот подписки',
title: '????????? ?? ????? ? Telegram',
description: '????????? ?? ??? ????? @mnemo_cards ? ?????? ???????? ????????',
type: TaskType.social,
difficulty: TaskDifficulty.easy,
rewards: [
TaskReward(type: RewardType.xp, amount: 25),
TaskReward(type: RewardType.coins, amount: 5),
const TaskReward(type: RewardType.xp, amount: 25),
const TaskReward(type: RewardType.coins, amount: 5),
],
status: TaskStatus.available,
createdAt: now.subtract(const Duration(days: 2)),
expiresAt: now.add(const Duration(days: 14)),
instructions: 'Сделай скриншот страницы подписки и загрузи его как доказательство',
instructions: '?????? ???????? ???????? ???????? ? ??????? ??? ??? ??????????????',
tags: ['telegram', 'social'],
),
Task(
id: 'task_3',
title: 'Закажи еду на испанском',
description: 'Сделай заказ в ресторане или кафе, используя испанский язык, и запиши это на видео',
title: '?????? ??? ?? ?????????',
description: '?????? ????? ? ????????? ??? ????, ????????? ????????? ????, ? ?????? ??? ?? ?????',
type: TaskType.external,
difficulty: TaskDifficulty.hard,
rewards: [
TaskReward(type: RewardType.xp, amount: 200),
TaskReward(type: RewardType.coins, amount: 50),
TaskReward(type: RewardType.achievement, amount: 1, achievementId: 'real_world_speaker'),
const TaskReward(type: RewardType.xp, amount: 200),
const TaskReward(type: RewardType.coins, amount: 50),
const TaskReward(type: RewardType.achievement, amount: 1, achievementId: 'real_world_speaker'),
],
status: TaskStatus.available,
createdAt: now.subtract(const Duration(days: 3)),
expiresAt: now.add(const Duration(days: 30)),
instructions: 'Запиши видео разговора на испанском языке и загрузи его',
instructions: '?????? ????? ????????? ?? ????????? ????? ? ??????? ???',
tags: ['spanish', 'speaking', 'real_world'],
),
Task(
id: 'task_4',
title: 'Изучи 10 новых слов',
description: 'Добавь в свой словарь и изучи 10 новых слов',
title: '????? 10 ????? ????',
description: '?????? ? ???? ??????? ? ????? 10 ????? ????',
type: TaskType.appInternal,
difficulty: TaskDifficulty.medium,
rewards: [
TaskReward(type: RewardType.xp, amount: 75),
TaskReward(type: RewardType.coins, amount: 15),
const TaskReward(type: RewardType.xp, amount: 75),
const TaskReward(type: RewardType.coins, amount: 15),
],
status: TaskStatus.inProgress,
createdAt: now.subtract(const Duration(days: 1)),

View file

@ -245,12 +245,12 @@ class StatisticsStateManager extends StateManager<StatisticsState> {
/// Get completed packs count
int get completedPacksCount {
return state.packsStats.where((pack) => (pack.progress ?? 0.0) >= 1.0).length;
return state.packsStats.where((pack) => pack.progress >= 1.0).length;
}
/// Get in progress packs count
int get inProgressPacksCount {
return state.packsStats.where((pack) => (pack.progress ?? 0.0) > 0.0 && (pack.progress ?? 0.0) < 1.0).length;
return state.packsStats.where((pack) => pack.progress > 0.0 && pack.progress < 1.0).length;
}
/// Get unlocked achievements count

View file

@ -60,7 +60,6 @@ class TestsStateManager extends StateManager<TestsState> {
final GameSoundService _gameSoundService;
String? _currentPackId;
TestDto? _currentTest;
List<GameQuestion>? _currentQuestions;
/// Load tests for a pack
Future<void> loadPackTests(String packId) => handle((emit) async {
@ -111,7 +110,6 @@ class TestsStateManager extends StateManager<TestsState> {
// Convert TestDto questions to GameQuestion format
final questions = _convertTestToGameQuestions(test);
_currentQuestions = questions;
// Prepare game session
emit(TestsState.gameSessionPreparing(
@ -177,13 +175,13 @@ class TestsStateManager extends StateManager<TestsState> {
// Auto-advance after delay if correct, or allow manual navigation
if (isCorrect) {
await Future<void>.delayed(const Duration(milliseconds: 1500));
await _nextQuestion(emit);
await _nextQuestion.call(emit);
}
});
/// Move to next question
Future<void> nextQuestion() => handle((emit) async {
await _nextQuestion(emit);
await _nextQuestion.call(emit);
});
/// Move to previous question
@ -240,7 +238,6 @@ class TestsStateManager extends StateManager<TestsState> {
log('Resetting game session', name: 'TestsStateManager');
_gameSessionManager.reset();
_currentTest = null;
_currentQuestions = null;
emit(const TestsState.loading());
});
@ -314,7 +311,7 @@ class TestsStateManager extends StateManager<TestsState> {
audio: question.audio,
options: options,
correctAnswer: correctAnswer,
word: question.word ?? '',
word: question.word,
),
));
} else if (question is InputButtonsTestQuestionBody) {

View file

@ -207,7 +207,9 @@ class _AuthPageState extends State<AuthPage> {
// Copy code to clipboard for convenience
await Clipboard.setData(ClipboardData(text: status.code));
_startCodePolling(context, status.code);
if (mounted) {
_startCodePolling(context, status.code);
}
} catch (e, s) {
log(
'Error generating Telegram code',
@ -270,7 +272,7 @@ class _AuthPageState extends State<AuthPage> {
}
} else if (status.isConsumed) {
_stopCodePolling();
} else if (status.isReadyForLogin && !_autoLoginAttempted) {
} else if (status.isReadyForLogin && !_autoLoginAttempted && mounted) {
_autoLoginAttempted = true;
await _loginWithTelegram(context);
}
@ -476,7 +478,7 @@ class _AuthPageState extends State<AuthPage> {
// Use the Adsgram SDK to show ad with specific block ID
await Adsgram.instance.showAdWithBlockId(id.toString());
} catch (e) {
print('Failed to show ad: $e');
log('Failed to show ad: $e', name: 'AuthPage');
// Fallback: show a simple alert
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(

View file

@ -14,8 +14,6 @@ import '../../../domain/state/tests_state_manager.dart';
import '../../../presentation/widgets/error_view.dart';
import '../../../presentation/widgets/game/answer_options.dart';
import '../../../presentation/widgets/game/input_letters_widget.dart';
import '../../../presentation/widgets/game/match_widget.dart';
import '../../../presentation/widgets/game/matrix_widget.dart';
import '../../../presentation/widgets/game/progress_indicator.dart';
import '../../../presentation/widgets/game/question_display.dart';
import '../../../presentation/widgets/loading_view.dart';

View file

@ -39,8 +39,6 @@ class _TasksPageState extends State<TasksPage> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Задания'),

View file

@ -3,7 +3,6 @@ import 'dart:developer';
import 'package:flutter/material.dart';
import '../../utils/adsgram_stub.dart';
import 'package:go_router/go_router.dart';
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import 'package:yx_state_flutter/yx_state_flutter.dart';
@ -13,7 +12,6 @@ import '../../di/app_scope/app_scope_container.dart';
import '../../domain/config/api_config_v2.dart';
import '../../domain/models/ads_reward_offer.dart';
import '../../domain/state/ads_reward_state_manager.dart';
import '../../utils/color_extension.dart';
/// Button that allows users to unlock a pack by watching a rewarded ad.
///

View file

@ -22,8 +22,6 @@ class AnswerOptions extends StatelessWidget {
final bool enabled;
void _onAnswerSelected(BuildContext context, String option) {
// Play button tap sound
final appScope = context.findAncestorWidgetOfExactType<InheritedWidget>();
// Note: Sound service access would be implemented through proper DI injection
// For now, we'll rely on the parent widget to handle sounds

View file

@ -4,7 +4,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import '../../../di/app_scope/app_scope_container.dart';
import '../../../domain/models/game_question.dart';
import '../../../domain/state/tests_state_manager.dart';
/// Widget for input letters questions - user types letters to fill in blanks
class InputLettersWidget extends StatefulWidget {

View file

@ -4,7 +4,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import '../../../di/app_scope/app_scope_container.dart';
import '../../../domain/models/game_question.dart';
import '../../../domain/state/tests_state_manager.dart';
/// Widget for match questions - user connects items from two columns
class MatchWidget extends StatefulWidget {

View file

@ -4,7 +4,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import '../../../di/app_scope/app_scope_container.dart';
import '../../../domain/models/game_question.dart';
import '../../../domain/state/tests_state_manager.dart';
/// Widget for matrix questions - user fills in a grid/table
class MatrixWidget extends StatefulWidget {

View file

@ -24,7 +24,6 @@ class TaskCard extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isCompleted = task.status == TaskStatus.completed;
final isInProgress = task.status == TaskStatus.inProgress;
final isExpired = task.status == TaskStatus.expired;
return Semantics(
@ -155,8 +154,6 @@ class TaskCard extends StatelessWidget {
}
Color _getIndicatorColor(BuildContext context) {
final theme = Theme.of(context);
switch (task.type) {
case TaskType.appInternal:
return Colors.blue;