Merge branch 'master' of https://code.mnemo-cards.online/cinnabarflower/cards
This commit is contained in:
commit
43ce663b0b
21 changed files with 63 additions and 91 deletions
|
|
@ -1,15 +1,14 @@
|
||||||
{
|
{
|
||||||
"component": "web_v2",
|
"component": "web_v2",
|
||||||
"current_task_id": null,
|
"current_task_id": "WEB-00",
|
||||||
"iteration_count": 0,
|
"iteration_count": 1,
|
||||||
"max_iterations": 10,
|
"max_iterations": 10,
|
||||||
"started_at": null,
|
"started_at": "2025-11-21T00:31:28.302997+00:00",
|
||||||
"last_commit": null,
|
"last_commit": null,
|
||||||
"retry_count": 0,
|
"retry_count": 0,
|
||||||
"max_retries": 3,
|
"max_retries": 3,
|
||||||
"status": "idle",
|
"status": "in_progress",
|
||||||
"errors": [],
|
"errors": [],
|
||||||
"completed_tasks": [],
|
"completed_tasks": [],
|
||||||
"skipped_tasks": []
|
"skipped_tasks": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"id": "WEB-00",
|
"id": "WEB-00",
|
||||||
"title": "Fix linter errors",
|
"title": "Fix linter errors",
|
||||||
"priority": "high",
|
"priority": "high",
|
||||||
"status": "pending",
|
"status": "in_progress",
|
||||||
"estimated_hours": 3.0,
|
"estimated_hours": 3.0,
|
||||||
"description": "Fix linter errors in the project",
|
"description": "Fix linter errors in the project",
|
||||||
"acceptance_criteria": [
|
"acceptance_criteria": [
|
||||||
|
|
@ -62,7 +62,9 @@
|
||||||
"Button is properly styled and positioned",
|
"Button is properly styled and positioned",
|
||||||
"5+ widget tests pass covering button integration"
|
"5+ widget tests pass covering button integration"
|
||||||
],
|
],
|
||||||
"dependencies": ["WEB-001"],
|
"dependencies": [
|
||||||
|
"WEB-001"
|
||||||
|
],
|
||||||
"files_to_modify": [
|
"files_to_modify": [
|
||||||
"mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart",
|
"mnemo_cards_web_v2/lib/presentation/pages/pack_details/pack_details_page.dart",
|
||||||
"mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart",
|
"mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart",
|
||||||
|
|
@ -109,7 +111,9 @@
|
||||||
"Loading states and error handling are implemented",
|
"Loading states and error handling are implemented",
|
||||||
"10+ widget tests pass covering all UI interactions"
|
"10+ widget tests pass covering all UI interactions"
|
||||||
],
|
],
|
||||||
"dependencies": ["backend:API-007"],
|
"dependencies": [
|
||||||
|
"backend:API-007"
|
||||||
|
],
|
||||||
"files_to_modify": [
|
"files_to_modify": [
|
||||||
"mnemo_cards_web_v2/lib/presentation/pages/subscription/subscription_page.dart",
|
"mnemo_cards_web_v2/lib/presentation/pages/subscription/subscription_page.dart",
|
||||||
"mnemo_cards_web_v2/lib/presentation/router/app_router.dart",
|
"mnemo_cards_web_v2/lib/presentation/router/app_router.dart",
|
||||||
|
|
@ -131,7 +135,9 @@
|
||||||
"Status updates when subscription changes",
|
"Status updates when subscription changes",
|
||||||
"3+ widget tests pass covering status display"
|
"3+ widget tests pass covering status display"
|
||||||
],
|
],
|
||||||
"dependencies": ["WEB-004"],
|
"dependencies": [
|
||||||
|
"WEB-004"
|
||||||
|
],
|
||||||
"files_to_modify": [
|
"files_to_modify": [
|
||||||
"mnemo_cards_web_v2/lib/presentation/pages/profile/profile_page.dart",
|
"mnemo_cards_web_v2/lib/presentation/pages/profile/profile_page.dart",
|
||||||
"mnemo_cards_web_v2/test/presentation/pages/profile/profile_page_test.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",
|
"Error handling is tested for all edge cases",
|
||||||
"All tests pass with zero flaky tests"
|
"All tests pass with zero flaky tests"
|
||||||
],
|
],
|
||||||
"dependencies": ["WEB-003"],
|
"dependencies": [
|
||||||
|
"WEB-003"
|
||||||
|
],
|
||||||
"files_to_modify": [
|
"files_to_modify": [
|
||||||
"mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart",
|
"mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart",
|
||||||
"mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart",
|
"mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart",
|
||||||
|
|
@ -258,4 +266,4 @@
|
||||||
"component": "web_v2"
|
"component": "web_v2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -35,13 +35,13 @@ class AppScopeContainer extends ScopeContainer
|
||||||
init: (_) async {
|
init: (_) async {
|
||||||
_firebaseApp = await Firebase.initializeApp(
|
_firebaseApp = await Firebase.initializeApp(
|
||||||
options: const FirebaseOptions(
|
options: const FirebaseOptions(
|
||||||
apiKey: "AIzaSyCq0V8JebjtqSgGSueouoLiprBROgPRBQw",
|
apiKey: 'AIzaSyCq0V8JebjtqSgGSueouoLiprBROgPRBQw',
|
||||||
authDomain: "mnemo-cards.firebaseapp.com",
|
authDomain: 'mnemo-cards.firebaseapp.com',
|
||||||
projectId: "mnemo-cards",
|
projectId: 'mnemo-cards',
|
||||||
storageBucket: "mnemo-cards.appspot.com",
|
storageBucket: 'mnemo-cards.appspot.com',
|
||||||
messagingSenderId: "701767851968",
|
messagingSenderId: '701767851968',
|
||||||
appId: "1:701767851968:web:714eb4fb5e3fd1ab2f7225",
|
appId: '1:701767851968:web:714eb4fb5e3fd1ab2f7225',
|
||||||
measurementId: "G-C37MMWS6WC"
|
measurementId: 'G-C37MMWS6WC'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:yx_scope/yx_scope.dart';
|
import 'package:yx_scope/yx_scope.dart';
|
||||||
|
|
||||||
import '../../../domain/services/http_repository_v2.dart';
|
|
||||||
import '../../../domain/services/statistics_service.dart';
|
import '../../../domain/services/statistics_service.dart';
|
||||||
import '../../../domain/state/statistics_state_manager.dart';
|
import '../../../domain/state/statistics_state_manager.dart';
|
||||||
import '../user_scope_container.dart';
|
import '../user_scope_container.dart';
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ class ApiConfigV2 {
|
||||||
|
|
||||||
/// Build Telegram bot deep link with payload
|
/// Build Telegram bot deep link with payload
|
||||||
static String telegramBotDeepLink(String payload) {
|
static String telegramBotDeepLink(String payload) {
|
||||||
return '${telegramBotDeepLinkBase}?start=$payload';
|
return '$telegramBotDeepLinkBase?start=$payload';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Chat Endpoints ====================
|
// ==================== Chat Endpoints ====================
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
|
||||||
|
|
||||||
import '../models/game_question.dart';
|
import '../models/game_question.dart';
|
||||||
|
|
||||||
/// Manages active game session state and user interactions
|
/// Manages active game session state and user interactions
|
||||||
|
|
|
||||||
|
|
@ -1522,7 +1522,7 @@ class HttpRepositoryV2 {
|
||||||
/// Delete chat session
|
/// Delete chat session
|
||||||
Future<void> deleteChatSession(String sessionId) async {
|
Future<void> deleteChatSession(String sessionId) async {
|
||||||
try {
|
try {
|
||||||
await _dio.delete(ApiConfigV2.chatSessionDelete(sessionId));
|
await _dio.delete<void>(ApiConfigV2.chatSessionDelete(sessionId));
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.error is ApiException) {
|
if (e.error is ApiException) {
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
@ -1555,7 +1555,7 @@ class HttpRepositoryV2 {
|
||||||
if (offset != null) queryParams['offset'] = offset.toString();
|
if (offset != null) queryParams['offset'] = offset.toString();
|
||||||
|
|
||||||
final queryString = ApiConfigV2.buildQuery(queryParams);
|
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>;
|
final data = response.data as List<dynamic>;
|
||||||
return data.map((item) => Task.fromJson(item as Map<String, dynamic>)).toList();
|
return data.map((item) => Task.fromJson(item as Map<String, dynamic>)).toList();
|
||||||
|
|
@ -1573,7 +1573,7 @@ class HttpRepositoryV2 {
|
||||||
/// Get specific task by ID
|
/// Get specific task by ID
|
||||||
Future<Task> getTask(String taskId) async {
|
Future<Task> getTask(String taskId) async {
|
||||||
try {
|
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>);
|
return Task.fromJson(response.data as Map<String, dynamic>);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.error is ApiException) {
|
if (e.error is ApiException) {
|
||||||
|
|
@ -1589,7 +1589,7 @@ class HttpRepositoryV2 {
|
||||||
/// Start a task (mark as in progress)
|
/// Start a task (mark as in progress)
|
||||||
Future<Task> startTask(String taskId) async {
|
Future<Task> startTask(String taskId) async {
|
||||||
try {
|
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>);
|
return Task.fromJson(response.data as Map<String, dynamic>);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.error is ApiException) {
|
if (e.error is ApiException) {
|
||||||
|
|
@ -1613,7 +1613,7 @@ class HttpRepositoryV2 {
|
||||||
if (proofUrl != null) body['proofUrl'] = proofUrl;
|
if (proofUrl != null) body['proofUrl'] = proofUrl;
|
||||||
if (notes != null) body['notes'] = notes;
|
if (notes != null) body['notes'] = notes;
|
||||||
|
|
||||||
final response = await _dio.post(
|
final response = await _dio.post<Map<String, dynamic>>(
|
||||||
ApiConfigV2.taskComplete(taskId),
|
ApiConfigV2.taskComplete(taskId),
|
||||||
data: body,
|
data: body,
|
||||||
);
|
);
|
||||||
|
|
@ -1632,7 +1632,7 @@ class HttpRepositoryV2 {
|
||||||
/// Get user's task progress and statistics
|
/// Get user's task progress and statistics
|
||||||
Future<TaskProgress> getUserTaskProgress() async {
|
Future<TaskProgress> getUserTaskProgress() async {
|
||||||
try {
|
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>);
|
return TaskProgress.fromJson(response.data as Map<String, dynamic>);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.error is ApiException) {
|
if (e.error is ApiException) {
|
||||||
|
|
@ -1648,7 +1648,7 @@ class HttpRepositoryV2 {
|
||||||
/// Get task categories and available filters
|
/// Get task categories and available filters
|
||||||
Future<TaskCategories> getTaskCategories() async {
|
Future<TaskCategories> getTaskCategories() async {
|
||||||
try {
|
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>);
|
return TaskCategories.fromJson(response.data as Map<String, dynamic>);
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.error is ApiException) {
|
if (e.error is ApiException) {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ class ImageCacheService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode base64 string and cache with automatic key generation
|
/// Decode base64 string and cache with automatic key generation
|
||||||
/// Key format: 'pack_<packId>'
|
/// Key format: 'pack_`packId`'
|
||||||
MemoryImage? putPackImage(String packId, String base64String) {
|
MemoryImage? putPackImage(String packId, String base64String) {
|
||||||
return putBase64('pack_$packId', base64String);
|
return putBase64('pack_$packId', base64String);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class StatisticsService {
|
||||||
|
|
||||||
/// Legacy method for backward compatibility - returns basic statistics
|
/// Legacy method for backward compatibility - returns basic statistics
|
||||||
/// TODO: Remove when all components migrate to new detailed statistics
|
/// TODO: Remove when all components migrate to new detailed statistics
|
||||||
@deprecated
|
@Deprecated('Use getDetailedStatistics instead')
|
||||||
UserStatistics getStatistics(UserDto user) {
|
UserStatistics getStatistics(UserDto user) {
|
||||||
final learnedWords = _calculateLearnedWords(user);
|
final learnedWords = _calculateLearnedWords(user);
|
||||||
final testsCompleted = _calculateTestsCompleted(user);
|
final testsCompleted = _calculateTestsCompleted(user);
|
||||||
|
|
|
||||||
|
|
@ -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 ====================
|
// ==================== Caching Methods ====================
|
||||||
|
|
||||||
/// Cache tasks locally
|
/// 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
|
/// Get cached user progress
|
||||||
Future<TaskProgress?> _getCachedUserProgress(String userId) async {
|
Future<TaskProgress?> _getCachedUserProgress(String userId) async {
|
||||||
try {
|
try {
|
||||||
|
|
@ -262,8 +245,8 @@ class TasksRepository {
|
||||||
return [
|
return [
|
||||||
Task(
|
Task(
|
||||||
id: 'task_1',
|
id: 'task_1',
|
||||||
title: 'Пройди 3 теста сегодня',
|
title: '?????? 3 ????? ???????',
|
||||||
description: 'Выполни 3 теста по любым темам в приложении',
|
description: '??????? 3 ????? ?? ????? ????? ? ??????????',
|
||||||
type: TaskType.appInternal,
|
type: TaskType.appInternal,
|
||||||
difficulty: TaskDifficulty.easy,
|
difficulty: TaskDifficulty.easy,
|
||||||
rewards: [
|
rewards: [
|
||||||
|
|
@ -277,46 +260,46 @@ class TasksRepository {
|
||||||
),
|
),
|
||||||
Task(
|
Task(
|
||||||
id: 'task_2',
|
id: 'task_2',
|
||||||
title: 'Подпишись на канал в Telegram',
|
title: '????????? ?? ????? ? Telegram',
|
||||||
description: 'Подпишись на наш канал @mnemo_cards и пришли скриншот подписки',
|
description: '????????? ?? ??? ????? @mnemo_cards ? ?????? ???????? ????????',
|
||||||
type: TaskType.social,
|
type: TaskType.social,
|
||||||
difficulty: TaskDifficulty.easy,
|
difficulty: TaskDifficulty.easy,
|
||||||
rewards: [
|
rewards: [
|
||||||
TaskReward(type: RewardType.xp, amount: 25),
|
const TaskReward(type: RewardType.xp, amount: 25),
|
||||||
TaskReward(type: RewardType.coins, amount: 5),
|
const TaskReward(type: RewardType.coins, amount: 5),
|
||||||
],
|
],
|
||||||
status: TaskStatus.available,
|
status: TaskStatus.available,
|
||||||
createdAt: now.subtract(const Duration(days: 2)),
|
createdAt: now.subtract(const Duration(days: 2)),
|
||||||
expiresAt: now.add(const Duration(days: 14)),
|
expiresAt: now.add(const Duration(days: 14)),
|
||||||
instructions: 'Сделай скриншот страницы подписки и загрузи его как доказательство',
|
instructions: '?????? ???????? ???????? ???????? ? ??????? ??? ??? ??????????????',
|
||||||
tags: ['telegram', 'social'],
|
tags: ['telegram', 'social'],
|
||||||
),
|
),
|
||||||
Task(
|
Task(
|
||||||
id: 'task_3',
|
id: 'task_3',
|
||||||
title: 'Закажи еду на испанском',
|
title: '?????? ??? ?? ?????????',
|
||||||
description: 'Сделай заказ в ресторане или кафе, используя испанский язык, и запиши это на видео',
|
description: '?????? ????? ? ????????? ??? ????, ????????? ????????? ????, ? ?????? ??? ?? ?????',
|
||||||
type: TaskType.external,
|
type: TaskType.external,
|
||||||
difficulty: TaskDifficulty.hard,
|
difficulty: TaskDifficulty.hard,
|
||||||
rewards: [
|
rewards: [
|
||||||
TaskReward(type: RewardType.xp, amount: 200),
|
const TaskReward(type: RewardType.xp, amount: 200),
|
||||||
TaskReward(type: RewardType.coins, amount: 50),
|
const TaskReward(type: RewardType.coins, amount: 50),
|
||||||
TaskReward(type: RewardType.achievement, amount: 1, achievementId: 'real_world_speaker'),
|
const TaskReward(type: RewardType.achievement, amount: 1, achievementId: 'real_world_speaker'),
|
||||||
],
|
],
|
||||||
status: TaskStatus.available,
|
status: TaskStatus.available,
|
||||||
createdAt: now.subtract(const Duration(days: 3)),
|
createdAt: now.subtract(const Duration(days: 3)),
|
||||||
expiresAt: now.add(const Duration(days: 30)),
|
expiresAt: now.add(const Duration(days: 30)),
|
||||||
instructions: 'Запиши видео разговора на испанском языке и загрузи его',
|
instructions: '?????? ????? ????????? ?? ????????? ????? ? ??????? ???',
|
||||||
tags: ['spanish', 'speaking', 'real_world'],
|
tags: ['spanish', 'speaking', 'real_world'],
|
||||||
),
|
),
|
||||||
Task(
|
Task(
|
||||||
id: 'task_4',
|
id: 'task_4',
|
||||||
title: 'Изучи 10 новых слов',
|
title: '????? 10 ????? ????',
|
||||||
description: 'Добавь в свой словарь и изучи 10 новых слов',
|
description: '?????? ? ???? ??????? ? ????? 10 ????? ????',
|
||||||
type: TaskType.appInternal,
|
type: TaskType.appInternal,
|
||||||
difficulty: TaskDifficulty.medium,
|
difficulty: TaskDifficulty.medium,
|
||||||
rewards: [
|
rewards: [
|
||||||
TaskReward(type: RewardType.xp, amount: 75),
|
const TaskReward(type: RewardType.xp, amount: 75),
|
||||||
TaskReward(type: RewardType.coins, amount: 15),
|
const TaskReward(type: RewardType.coins, amount: 15),
|
||||||
],
|
],
|
||||||
status: TaskStatus.inProgress,
|
status: TaskStatus.inProgress,
|
||||||
createdAt: now.subtract(const Duration(days: 1)),
|
createdAt: now.subtract(const Duration(days: 1)),
|
||||||
|
|
|
||||||
|
|
@ -245,12 +245,12 @@ class StatisticsStateManager extends StateManager<StatisticsState> {
|
||||||
|
|
||||||
/// Get completed packs count
|
/// Get completed packs count
|
||||||
int get completedPacksCount {
|
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
|
/// Get in progress packs count
|
||||||
int get inProgressPacksCount {
|
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
|
/// Get unlocked achievements count
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,6 @@ class TestsStateManager extends StateManager<TestsState> {
|
||||||
final GameSoundService _gameSoundService;
|
final GameSoundService _gameSoundService;
|
||||||
String? _currentPackId;
|
String? _currentPackId;
|
||||||
TestDto? _currentTest;
|
TestDto? _currentTest;
|
||||||
List<GameQuestion>? _currentQuestions;
|
|
||||||
|
|
||||||
/// Load tests for a pack
|
/// Load tests for a pack
|
||||||
Future<void> loadPackTests(String packId) => handle((emit) async {
|
Future<void> loadPackTests(String packId) => handle((emit) async {
|
||||||
|
|
@ -111,7 +110,6 @@ class TestsStateManager extends StateManager<TestsState> {
|
||||||
|
|
||||||
// Convert TestDto questions to GameQuestion format
|
// Convert TestDto questions to GameQuestion format
|
||||||
final questions = _convertTestToGameQuestions(test);
|
final questions = _convertTestToGameQuestions(test);
|
||||||
_currentQuestions = questions;
|
|
||||||
|
|
||||||
// Prepare game session
|
// Prepare game session
|
||||||
emit(TestsState.gameSessionPreparing(
|
emit(TestsState.gameSessionPreparing(
|
||||||
|
|
@ -177,13 +175,13 @@ class TestsStateManager extends StateManager<TestsState> {
|
||||||
// Auto-advance after delay if correct, or allow manual navigation
|
// Auto-advance after delay if correct, or allow manual navigation
|
||||||
if (isCorrect) {
|
if (isCorrect) {
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 1500));
|
await Future<void>.delayed(const Duration(milliseconds: 1500));
|
||||||
await _nextQuestion(emit);
|
await _nextQuestion.call(emit);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Move to next question
|
/// Move to next question
|
||||||
Future<void> nextQuestion() => handle((emit) async {
|
Future<void> nextQuestion() => handle((emit) async {
|
||||||
await _nextQuestion(emit);
|
await _nextQuestion.call(emit);
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Move to previous question
|
/// Move to previous question
|
||||||
|
|
@ -240,7 +238,6 @@ class TestsStateManager extends StateManager<TestsState> {
|
||||||
log('Resetting game session', name: 'TestsStateManager');
|
log('Resetting game session', name: 'TestsStateManager');
|
||||||
_gameSessionManager.reset();
|
_gameSessionManager.reset();
|
||||||
_currentTest = null;
|
_currentTest = null;
|
||||||
_currentQuestions = null;
|
|
||||||
emit(const TestsState.loading());
|
emit(const TestsState.loading());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -314,7 +311,7 @@ class TestsStateManager extends StateManager<TestsState> {
|
||||||
audio: question.audio,
|
audio: question.audio,
|
||||||
options: options,
|
options: options,
|
||||||
correctAnswer: correctAnswer,
|
correctAnswer: correctAnswer,
|
||||||
word: question.word ?? '',
|
word: question.word,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
} else if (question is InputButtonsTestQuestionBody) {
|
} else if (question is InputButtonsTestQuestionBody) {
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,9 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
// Copy code to clipboard for convenience
|
// Copy code to clipboard for convenience
|
||||||
await Clipboard.setData(ClipboardData(text: status.code));
|
await Clipboard.setData(ClipboardData(text: status.code));
|
||||||
|
|
||||||
_startCodePolling(context, status.code);
|
if (mounted) {
|
||||||
|
_startCodePolling(context, status.code);
|
||||||
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
log(
|
log(
|
||||||
'Error generating Telegram code',
|
'Error generating Telegram code',
|
||||||
|
|
@ -270,7 +272,7 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
}
|
}
|
||||||
} else if (status.isConsumed) {
|
} else if (status.isConsumed) {
|
||||||
_stopCodePolling();
|
_stopCodePolling();
|
||||||
} else if (status.isReadyForLogin && !_autoLoginAttempted) {
|
} else if (status.isReadyForLogin && !_autoLoginAttempted && mounted) {
|
||||||
_autoLoginAttempted = true;
|
_autoLoginAttempted = true;
|
||||||
await _loginWithTelegram(context);
|
await _loginWithTelegram(context);
|
||||||
}
|
}
|
||||||
|
|
@ -476,7 +478,7 @@ class _AuthPageState extends State<AuthPage> {
|
||||||
// Use the Adsgram SDK to show ad with specific block ID
|
// Use the Adsgram SDK to show ad with specific block ID
|
||||||
await Adsgram.instance.showAdWithBlockId(id.toString());
|
await Adsgram.instance.showAdWithBlockId(id.toString());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Failed to show ad: $e');
|
log('Failed to show ad: $e', name: 'AuthPage');
|
||||||
// Fallback: show a simple alert
|
// Fallback: show a simple alert
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ import '../../../domain/state/tests_state_manager.dart';
|
||||||
import '../../../presentation/widgets/error_view.dart';
|
import '../../../presentation/widgets/error_view.dart';
|
||||||
import '../../../presentation/widgets/game/answer_options.dart';
|
import '../../../presentation/widgets/game/answer_options.dart';
|
||||||
import '../../../presentation/widgets/game/input_letters_widget.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/progress_indicator.dart';
|
||||||
import '../../../presentation/widgets/game/question_display.dart';
|
import '../../../presentation/widgets/game/question_display.dart';
|
||||||
import '../../../presentation/widgets/loading_view.dart';
|
import '../../../presentation/widgets/loading_view.dart';
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,6 @@ class _TasksPageState extends State<TasksPage> with TickerProviderStateMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('Задания'),
|
title: const Text('Задания'),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'dart:developer';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../utils/adsgram_stub.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_scope_flutter/yx_scope_flutter.dart';
|
||||||
import 'package:yx_state_flutter/yx_state_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/config/api_config_v2.dart';
|
||||||
import '../../domain/models/ads_reward_offer.dart';
|
import '../../domain/models/ads_reward_offer.dart';
|
||||||
import '../../domain/state/ads_reward_state_manager.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.
|
/// Button that allows users to unlock a pack by watching a rewarded ad.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,6 @@ class AnswerOptions extends StatelessWidget {
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
|
|
||||||
void _onAnswerSelected(BuildContext context, String option) {
|
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
|
// Note: Sound service access would be implemented through proper DI injection
|
||||||
// For now, we'll rely on the parent widget to handle sounds
|
// For now, we'll rely on the parent widget to handle sounds
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||||
|
|
||||||
import '../../../di/app_scope/app_scope_container.dart';
|
import '../../../di/app_scope/app_scope_container.dart';
|
||||||
import '../../../domain/models/game_question.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
|
/// Widget for input letters questions - user types letters to fill in blanks
|
||||||
class InputLettersWidget extends StatefulWidget {
|
class InputLettersWidget extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||||
|
|
||||||
import '../../../di/app_scope/app_scope_container.dart';
|
import '../../../di/app_scope/app_scope_container.dart';
|
||||||
import '../../../domain/models/game_question.dart';
|
import '../../../domain/models/game_question.dart';
|
||||||
import '../../../domain/state/tests_state_manager.dart';
|
|
||||||
|
|
||||||
/// Widget for match questions - user connects items from two columns
|
/// Widget for match questions - user connects items from two columns
|
||||||
class MatchWidget extends StatefulWidget {
|
class MatchWidget extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:yx_scope_flutter/yx_scope_flutter.dart';
|
||||||
|
|
||||||
import '../../../di/app_scope/app_scope_container.dart';
|
import '../../../di/app_scope/app_scope_container.dart';
|
||||||
import '../../../domain/models/game_question.dart';
|
import '../../../domain/models/game_question.dart';
|
||||||
import '../../../domain/state/tests_state_manager.dart';
|
|
||||||
|
|
||||||
/// Widget for matrix questions - user fills in a grid/table
|
/// Widget for matrix questions - user fills in a grid/table
|
||||||
class MatrixWidget extends StatefulWidget {
|
class MatrixWidget extends StatefulWidget {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ class TaskCard extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final isCompleted = task.status == TaskStatus.completed;
|
final isCompleted = task.status == TaskStatus.completed;
|
||||||
final isInProgress = task.status == TaskStatus.inProgress;
|
|
||||||
final isExpired = task.status == TaskStatus.expired;
|
final isExpired = task.status == TaskStatus.expired;
|
||||||
|
|
||||||
return Semantics(
|
return Semantics(
|
||||||
|
|
@ -155,8 +154,6 @@ class TaskCard extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Color _getIndicatorColor(BuildContext context) {
|
Color _getIndicatorColor(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
switch (task.type) {
|
switch (task.type) {
|
||||||
case TaskType.appInternal:
|
case TaskType.appInternal:
|
||||||
return Colors.blue;
|
return Colors.blue;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue