stuff
44
.forgejo/workflows/agent.yml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
name: agent-issue-to-pr
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
agent:
|
||||||
|
if: github.event.label.name == 'agent:do'
|
||||||
|
runs-on: docker
|
||||||
|
container: python:3.12
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Aider
|
||||||
|
run: pip install --upgrade pip aider-chat
|
||||||
|
|
||||||
|
- name: Create branch
|
||||||
|
id: mkbr
|
||||||
|
run: |
|
||||||
|
BR="agent/${{ github.event.issue.number }}"
|
||||||
|
echo "BR=$BR" >> $GITHUB_ENV
|
||||||
|
git checkout -b "$BR"
|
||||||
|
git config user.name "forgejo-actions[bot]"
|
||||||
|
git config user.email "actions@forgejo.local"
|
||||||
|
|
||||||
|
- name: Run agent (write code + tests)
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
run: |
|
||||||
|
aider --yes --message "Issue #${{ github.event.issue.number }} — ${{ github.event.issue.title }}.
|
||||||
|
Сделай минимальный патч и тесты. CI не трогай." .
|
||||||
|
git add -A
|
||||||
|
git commit -m "agent: implement #${{ github.event.issue.number }}" || true
|
||||||
|
git push -u origin "$BR"
|
||||||
|
|
||||||
|
- name: Open PR via API
|
||||||
|
env:
|
||||||
|
API: ${{ github.api_url }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
curl -sS -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||||
|
-d '{"head":"'"$BR"'","base":"main","title":"Agent PR for #'"${{ github.event.issue.number }}"'","body":"Автогенерация по issue."}' \
|
||||||
|
"$API/repos/$REPO/pulls"
|
||||||
175
chat/mnemo_cards_chat/README.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
# mnemo_cards_chat
|
||||||
|
|
||||||
|
A Flutter package providing chat functionality with LLM integration for mnemo_cards applications.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 💬 **Text Chat**: Send and receive text messages with LLM
|
||||||
|
- 🎤 **Audio Messages**: Record and playback voice messages
|
||||||
|
- 🔄 **Reactive State**: State management with yx_state
|
||||||
|
- 🏗️ **Clean Architecture**: Modular design with dependency injection
|
||||||
|
- 🔌 **Extensible**: Abstract ChatRepository interface for different backends
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Add to your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
mnemo_cards_chat:
|
||||||
|
path: packages/mnemo_cards_chat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Basic Setup
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||||
|
|
||||||
|
// 1. Implement ChatRepository for your backend
|
||||||
|
class MyChatRepository implements ChatRepository {
|
||||||
|
// Implement all ChatRepository methods
|
||||||
|
// e.g., sendTextMessage, getChatMessages, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create chat service
|
||||||
|
final chatService = ChatService(chatRepository: MyChatRepository());
|
||||||
|
|
||||||
|
// 3. Create state manager
|
||||||
|
final chatStateManager = ChatStateManager(chatService: chatService);
|
||||||
|
|
||||||
|
// 4. Use in your UI
|
||||||
|
// See ChatStateManager for available methods
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency Injection with yx_scope
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:yx_scope/yx_scope.dart';
|
||||||
|
import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||||
|
|
||||||
|
class ChatModule extends ScopeModule<UserScopeContainer> {
|
||||||
|
ChatModule(super.container);
|
||||||
|
|
||||||
|
// Chat repository adapter
|
||||||
|
late final chatRepositoryDep = dep(
|
||||||
|
() => ChatRepositoryAdapter(container.httpRepository),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Chat service
|
||||||
|
late final chatServiceDep = dep(
|
||||||
|
() => ChatService(chatRepository: chatRepositoryDep.get),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Chat state manager
|
||||||
|
late final chatStateManagerDep = dep(
|
||||||
|
() => ChatStateManager(chatService: chatServiceDep.get),
|
||||||
|
);
|
||||||
|
|
||||||
|
ChatStateManager get chatStateManager => chatStateManagerDep.get;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Initialize chat session
|
||||||
|
await chatStateManager.initializeChat('session_id');
|
||||||
|
|
||||||
|
// Send text message
|
||||||
|
await chatStateManager.sendTextMessage('Hello, world!');
|
||||||
|
|
||||||
|
// Send audio message
|
||||||
|
final audioData = Uint8List.fromList([...]);
|
||||||
|
await chatStateManager.sendAudioMessage(audioData, Duration(seconds: 5));
|
||||||
|
|
||||||
|
// Listen to state changes
|
||||||
|
chatStateManager.addListener(() {
|
||||||
|
final state = chatStateManager.state;
|
||||||
|
if (state is ChatStateLoaded) {
|
||||||
|
// Handle loaded state
|
||||||
|
print('Messages: ${state.messages.length}');
|
||||||
|
} else if (state is ChatStateError) {
|
||||||
|
// Handle error
|
||||||
|
print('Error: ${state.message}');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
- **`ChatService`**: Business logic and API orchestration
|
||||||
|
- **`ChatStateManager`**: Reactive state management
|
||||||
|
- **`ChatRepository`**: Abstract interface for backend communication
|
||||||
|
- **Models**: Data structures for messages, sessions, and participants
|
||||||
|
|
||||||
|
### State Types
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// Loading state
|
||||||
|
const ChatStateLoading()
|
||||||
|
|
||||||
|
// Loaded state with session data
|
||||||
|
ChatStateLoaded(
|
||||||
|
session: session,
|
||||||
|
messages: messages,
|
||||||
|
isSendingMessage: false,
|
||||||
|
errorMessage: null,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error state
|
||||||
|
ChatStateError('Failed to load chat')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Models
|
||||||
|
|
||||||
|
### ChatMessage
|
||||||
|
Union type for different message types:
|
||||||
|
- `ChatMessage.text(TextMessage)` - Text messages
|
||||||
|
- `ChatMessage.audio(AudioMessage)` - Audio messages
|
||||||
|
|
||||||
|
### ChatParticipant
|
||||||
|
Represents chat participants:
|
||||||
|
- `ChatParticipant.user()` - Human users
|
||||||
|
- `ChatParticipant.assistant()` - AI assistants
|
||||||
|
|
||||||
|
### ChatSession
|
||||||
|
Represents chat conversation sessions with metadata.
|
||||||
|
|
||||||
|
## API Integration
|
||||||
|
|
||||||
|
Implement `ChatRepository` to connect with your backend:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
abstract class ChatRepository {
|
||||||
|
Future<ChatBasicSession> createChatSession(CreateChatSessionRequest request);
|
||||||
|
Future<List<ChatBasicSession>> getChatSessions({int limit, String? afterSessionId});
|
||||||
|
Future<ChatBasicSession> getChatSession(String sessionId);
|
||||||
|
Future<ChatMessageResponse> sendTextMessage(SendTextMessageRequest request);
|
||||||
|
Future<ChatMessageResponse> sendAudioMessage(String sessionId, dynamic audioData, Duration duration, {String? fileName, String? mimeType});
|
||||||
|
Future<List<ChatMessageResponse>> getChatMessages(String sessionId, {int limit, String? beforeMessageId});
|
||||||
|
Future<ChatBasicSession> updateChatSession(String sessionId, Map<String, dynamic> updates);
|
||||||
|
Future<void> deleteChatSession(String sessionId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
```bash
|
||||||
|
flutter test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Follow the existing code style
|
||||||
|
2. Add tests for new features
|
||||||
|
3. Update documentation
|
||||||
|
4. Ensure all tests pass
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This package is part of the mnemo_cards project.
|
||||||
61
chat/mnemo_cards_chat/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
plugins:
|
||||||
|
- custom_lint
|
||||||
|
|
||||||
|
exclude:
|
||||||
|
- '**/*.g.dart'
|
||||||
|
- '**/*.freezed.dart'
|
||||||
|
|
||||||
|
language:
|
||||||
|
strict-casts: true
|
||||||
|
strict-inference: true
|
||||||
|
strict-raw-types: true
|
||||||
|
|
||||||
|
errors:
|
||||||
|
# Treat missing required parameters as errors
|
||||||
|
missing_required_param: error
|
||||||
|
# Treat missing returns as errors
|
||||||
|
missing_return: error
|
||||||
|
# Treat invalid assignments as errors
|
||||||
|
invalid_assignment: error
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
# Basic rules
|
||||||
|
avoid_print: true
|
||||||
|
prefer_const_constructors: true
|
||||||
|
prefer_const_literals_to_create_immutables: true
|
||||||
|
prefer_final_fields: true
|
||||||
|
unnecessary_this: true
|
||||||
|
sort_child_properties_last: true
|
||||||
|
use_key_in_widget_constructors: true
|
||||||
|
|
||||||
|
# Type safety rules - NO DYNAMIC!
|
||||||
|
avoid_dynamic_calls: true
|
||||||
|
avoid_type_to_string: true
|
||||||
|
implicit_call_tearoffs: true
|
||||||
|
|
||||||
|
# Additional type safety
|
||||||
|
always_declare_return_types: true
|
||||||
|
always_specify_types: false # Too verbose, but we have strict-inference
|
||||||
|
type_annotate_public_apis: true
|
||||||
|
|
||||||
|
# Code quality
|
||||||
|
always_use_package_imports: true
|
||||||
|
avoid_empty_else: true
|
||||||
|
avoid_relative_lib_imports: true
|
||||||
|
avoid_slow_async_io: true
|
||||||
|
cancel_subscriptions: true
|
||||||
|
close_sinks: true
|
||||||
|
no_adjacent_strings_in_list: true
|
||||||
|
unnecessary_statements: true
|
||||||
|
|
||||||
|
# Style
|
||||||
|
prefer_single_quotes: true
|
||||||
|
require_trailing_commas: true
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
package_api_docs: true
|
||||||
|
public_member_api_docs: false # Can enable for stricter docs
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"format-version":[1,0,0],"native-assets":{}}
|
||||||
BIN
chat/mnemo_cards_chat/build/unit_test_assets/AssetManifest.bin
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]}]
|
||||||
BIN
chat/mnemo_cards_chat/build/unit_test_assets/NOTICES.Z
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"format-version":[1,0,0],"native-assets":{}}
|
||||||
37
chat/mnemo_cards_chat/lib/mnemo_cards_chat.dart
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/// Chat module for mnemo_cards applications
|
||||||
|
///
|
||||||
|
/// Provides chat functionality with LLM integration supporting both text and audio messages.
|
||||||
|
///
|
||||||
|
/// ## Features
|
||||||
|
/// - Text message chat with LLM
|
||||||
|
/// - Audio message recording and playback
|
||||||
|
/// - Session management
|
||||||
|
/// - Reactive state management with yx_state
|
||||||
|
/// - Clean architecture with yx_scope DI
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
/// ```dart
|
||||||
|
/// import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||||
|
///
|
||||||
|
/// // Register chat module in your scope
|
||||||
|
/// final scope = UserScopeContainer()
|
||||||
|
/// ..add(ChatModule(chatConfig: chatConfig));
|
||||||
|
/// ```
|
||||||
|
library mnemo_cards_chat;
|
||||||
|
|
||||||
|
export 'src/domain/models/chat_basic.dart';
|
||||||
|
export 'src/domain/models/chat_message.dart';
|
||||||
|
export 'src/domain/models/chat_session.dart';
|
||||||
|
export 'src/domain/models/chat_models.dart';
|
||||||
|
export 'src/domain/services/chat_repository.dart';
|
||||||
|
export 'src/domain/services/chat_service.dart';
|
||||||
|
export 'src/domain/state/chat_state_manager.dart';
|
||||||
|
|
||||||
|
// Re-export common types from chat_session for convenience
|
||||||
|
export 'src/domain/models/chat_session.dart' show
|
||||||
|
ChatSession,
|
||||||
|
ChatSessionStatus,
|
||||||
|
UpdateChatSessionRequest;
|
||||||
|
|
||||||
|
// Re-export common types from chat_api_simple for convenience
|
||||||
|
export 'src/domain/models/chat_api_simple.dart';
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'chat_api_simple.freezed.dart';
|
||||||
|
part 'chat_api_simple.g.dart';
|
||||||
|
|
||||||
|
/// Request to send text message to chat
|
||||||
|
@freezed
|
||||||
|
class SendTextMessageRequest with _$SendTextMessageRequest {
|
||||||
|
const factory SendTextMessageRequest({
|
||||||
|
required String sessionId,
|
||||||
|
required String content,
|
||||||
|
}) = _SendTextMessageRequest;
|
||||||
|
|
||||||
|
factory SendTextMessageRequest.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SendTextMessageRequestFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response from chat API containing assistant's message
|
||||||
|
@freezed
|
||||||
|
class ChatMessageResponse with _$ChatMessageResponse {
|
||||||
|
const factory ChatMessageResponse({
|
||||||
|
required String messageId,
|
||||||
|
required String sessionId,
|
||||||
|
required String content,
|
||||||
|
required String senderId,
|
||||||
|
required DateTime timestamp,
|
||||||
|
@Default('text') String messageType,
|
||||||
|
}) = _ChatMessageResponse;
|
||||||
|
|
||||||
|
factory ChatMessageResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatMessageResponseFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request to get chat messages for a session
|
||||||
|
@freezed
|
||||||
|
class GetChatMessagesRequest with _$GetChatMessagesRequest {
|
||||||
|
const factory GetChatMessagesRequest({
|
||||||
|
required String sessionId,
|
||||||
|
@Default(50) int limit,
|
||||||
|
}) = _GetChatMessagesRequest;
|
||||||
|
|
||||||
|
factory GetChatMessagesRequest.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$GetChatMessagesRequestFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response containing list of chat messages
|
||||||
|
@freezed
|
||||||
|
class ChatMessagesResponse with _$ChatMessagesResponse {
|
||||||
|
const factory ChatMessagesResponse({
|
||||||
|
required List<ChatMessageResponse> messages,
|
||||||
|
@Default(false) bool hasMore,
|
||||||
|
}) = _ChatMessagesResponse;
|
||||||
|
|
||||||
|
factory ChatMessagesResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatMessagesResponseFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request to create new chat session
|
||||||
|
@freezed
|
||||||
|
class CreateChatSessionRequest with _$CreateChatSessionRequest {
|
||||||
|
const factory CreateChatSessionRequest({
|
||||||
|
required String title,
|
||||||
|
}) = _CreateChatSessionRequest;
|
||||||
|
|
||||||
|
factory CreateChatSessionRequest.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CreateChatSessionRequestFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'chat_api_simple.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SendTextMessageRequestImpl _$$SendTextMessageRequestImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$SendTextMessageRequestImpl(
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
content: json['content'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SendTextMessageRequestImplToJson(
|
||||||
|
_$SendTextMessageRequestImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'content': instance.content,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$ChatMessageResponseImpl _$$ChatMessageResponseImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatMessageResponseImpl(
|
||||||
|
messageId: json['messageId'] as String,
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
content: json['content'] as String,
|
||||||
|
senderId: json['senderId'] as String,
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
messageType: json['messageType'] as String? ?? 'text',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatMessageResponseImplToJson(
|
||||||
|
_$ChatMessageResponseImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'messageId': instance.messageId,
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'content': instance.content,
|
||||||
|
'senderId': instance.senderId,
|
||||||
|
'timestamp': instance.timestamp.toIso8601String(),
|
||||||
|
'messageType': instance.messageType,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$GetChatMessagesRequestImpl _$$GetChatMessagesRequestImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$GetChatMessagesRequestImpl(
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
limit: (json['limit'] as num?)?.toInt() ?? 50,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$GetChatMessagesRequestImplToJson(
|
||||||
|
_$GetChatMessagesRequestImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'limit': instance.limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$ChatMessagesResponseImpl _$$ChatMessagesResponseImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatMessagesResponseImpl(
|
||||||
|
messages: (json['messages'] as List<dynamic>)
|
||||||
|
.map((e) => ChatMessageResponse.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
hasMore: json['hasMore'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatMessagesResponseImplToJson(
|
||||||
|
_$ChatMessagesResponseImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'messages': instance.messages,
|
||||||
|
'hasMore': instance.hasMore,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$CreateChatSessionRequestImpl _$$CreateChatSessionRequestImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$CreateChatSessionRequestImpl(title: json['title'] as String);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$CreateChatSessionRequestImplToJson(
|
||||||
|
_$CreateChatSessionRequestImpl instance,
|
||||||
|
) => <String, dynamic>{'title': instance.title};
|
||||||
54
chat/mnemo_cards_chat/lib/src/domain/models/chat_basic.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'chat_basic.freezed.dart';
|
||||||
|
part 'chat_basic.g.dart';
|
||||||
|
|
||||||
|
/// Basic chat message for initial implementation
|
||||||
|
@freezed
|
||||||
|
class ChatBasicMessage with _$ChatBasicMessage {
|
||||||
|
const factory ChatBasicMessage({
|
||||||
|
required String id,
|
||||||
|
required String sessionId,
|
||||||
|
required String content,
|
||||||
|
required String senderId,
|
||||||
|
required String senderName,
|
||||||
|
required DateTime timestamp,
|
||||||
|
@Default('text') String messageType,
|
||||||
|
String? audioUrl,
|
||||||
|
@Default(0) int durationMs,
|
||||||
|
int? fileSize,
|
||||||
|
}) = _ChatBasicMessage;
|
||||||
|
|
||||||
|
factory ChatBasicMessage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatBasicMessageFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Basic chat session for initial implementation
|
||||||
|
@freezed
|
||||||
|
class ChatBasicSession with _$ChatBasicSession {
|
||||||
|
const factory ChatBasicSession({
|
||||||
|
required String id,
|
||||||
|
required String userId,
|
||||||
|
required String title,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
@Default('active') String status,
|
||||||
|
String? description,
|
||||||
|
@Default(0) int messageCount,
|
||||||
|
}) = _ChatBasicSession;
|
||||||
|
|
||||||
|
factory ChatBasicSession.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatBasicSessionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request to send text message
|
||||||
|
@freezed
|
||||||
|
class SendTextMessageBasicRequest with _$SendTextMessageBasicRequest {
|
||||||
|
const factory SendTextMessageBasicRequest({
|
||||||
|
required String sessionId,
|
||||||
|
required String content,
|
||||||
|
}) = _SendTextMessageBasicRequest;
|
||||||
|
|
||||||
|
factory SendTextMessageBasicRequest.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SendTextMessageBasicRequestFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,897 @@
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'chat_basic.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||||
|
);
|
||||||
|
|
||||||
|
ChatBasicMessage _$ChatBasicMessageFromJson(Map<String, dynamic> json) {
|
||||||
|
return _ChatBasicMessage.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ChatBasicMessage {
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
String get sessionId => throw _privateConstructorUsedError;
|
||||||
|
String get content => throw _privateConstructorUsedError;
|
||||||
|
String get senderId => throw _privateConstructorUsedError;
|
||||||
|
String get senderName => throw _privateConstructorUsedError;
|
||||||
|
DateTime get timestamp => throw _privateConstructorUsedError;
|
||||||
|
String get messageType => throw _privateConstructorUsedError;
|
||||||
|
String? get audioUrl => throw _privateConstructorUsedError;
|
||||||
|
int get durationMs => throw _privateConstructorUsedError;
|
||||||
|
int? get fileSize => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this ChatBasicMessage to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicMessage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$ChatBasicMessageCopyWith<ChatBasicMessage> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $ChatBasicMessageCopyWith<$Res> {
|
||||||
|
factory $ChatBasicMessageCopyWith(
|
||||||
|
ChatBasicMessage value,
|
||||||
|
$Res Function(ChatBasicMessage) then,
|
||||||
|
) = _$ChatBasicMessageCopyWithImpl<$Res, ChatBasicMessage>;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id,
|
||||||
|
String sessionId,
|
||||||
|
String content,
|
||||||
|
String senderId,
|
||||||
|
String senderName,
|
||||||
|
DateTime timestamp,
|
||||||
|
String messageType,
|
||||||
|
String? audioUrl,
|
||||||
|
int durationMs,
|
||||||
|
int? fileSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$ChatBasicMessageCopyWithImpl<$Res, $Val extends ChatBasicMessage>
|
||||||
|
implements $ChatBasicMessageCopyWith<$Res> {
|
||||||
|
_$ChatBasicMessageCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicMessage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? sessionId = null,
|
||||||
|
Object? content = null,
|
||||||
|
Object? senderId = null,
|
||||||
|
Object? senderName = null,
|
||||||
|
Object? timestamp = null,
|
||||||
|
Object? messageType = null,
|
||||||
|
Object? audioUrl = freezed,
|
||||||
|
Object? durationMs = null,
|
||||||
|
Object? fileSize = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
sessionId: null == sessionId
|
||||||
|
? _value.sessionId
|
||||||
|
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
content: null == content
|
||||||
|
? _value.content
|
||||||
|
: content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
senderId: null == senderId
|
||||||
|
? _value.senderId
|
||||||
|
: senderId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
senderName: null == senderName
|
||||||
|
? _value.senderName
|
||||||
|
: senderName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
timestamp: null == timestamp
|
||||||
|
? _value.timestamp
|
||||||
|
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
messageType: null == messageType
|
||||||
|
? _value.messageType
|
||||||
|
: messageType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
audioUrl: freezed == audioUrl
|
||||||
|
? _value.audioUrl
|
||||||
|
: audioUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
durationMs: null == durationMs
|
||||||
|
? _value.durationMs
|
||||||
|
: durationMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
fileSize: freezed == fileSize
|
||||||
|
? _value.fileSize
|
||||||
|
: fileSize // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$ChatBasicMessageImplCopyWith<$Res>
|
||||||
|
implements $ChatBasicMessageCopyWith<$Res> {
|
||||||
|
factory _$$ChatBasicMessageImplCopyWith(
|
||||||
|
_$ChatBasicMessageImpl value,
|
||||||
|
$Res Function(_$ChatBasicMessageImpl) then,
|
||||||
|
) = __$$ChatBasicMessageImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id,
|
||||||
|
String sessionId,
|
||||||
|
String content,
|
||||||
|
String senderId,
|
||||||
|
String senderName,
|
||||||
|
DateTime timestamp,
|
||||||
|
String messageType,
|
||||||
|
String? audioUrl,
|
||||||
|
int durationMs,
|
||||||
|
int? fileSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$ChatBasicMessageImplCopyWithImpl<$Res>
|
||||||
|
extends _$ChatBasicMessageCopyWithImpl<$Res, _$ChatBasicMessageImpl>
|
||||||
|
implements _$$ChatBasicMessageImplCopyWith<$Res> {
|
||||||
|
__$$ChatBasicMessageImplCopyWithImpl(
|
||||||
|
_$ChatBasicMessageImpl _value,
|
||||||
|
$Res Function(_$ChatBasicMessageImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicMessage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? sessionId = null,
|
||||||
|
Object? content = null,
|
||||||
|
Object? senderId = null,
|
||||||
|
Object? senderName = null,
|
||||||
|
Object? timestamp = null,
|
||||||
|
Object? messageType = null,
|
||||||
|
Object? audioUrl = freezed,
|
||||||
|
Object? durationMs = null,
|
||||||
|
Object? fileSize = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_$ChatBasicMessageImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
sessionId: null == sessionId
|
||||||
|
? _value.sessionId
|
||||||
|
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
content: null == content
|
||||||
|
? _value.content
|
||||||
|
: content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
senderId: null == senderId
|
||||||
|
? _value.senderId
|
||||||
|
: senderId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
senderName: null == senderName
|
||||||
|
? _value.senderName
|
||||||
|
: senderName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
timestamp: null == timestamp
|
||||||
|
? _value.timestamp
|
||||||
|
: timestamp // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
messageType: null == messageType
|
||||||
|
? _value.messageType
|
||||||
|
: messageType // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
audioUrl: freezed == audioUrl
|
||||||
|
? _value.audioUrl
|
||||||
|
: audioUrl // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
durationMs: null == durationMs
|
||||||
|
? _value.durationMs
|
||||||
|
: durationMs // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
fileSize: freezed == fileSize
|
||||||
|
? _value.fileSize
|
||||||
|
: fileSize // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$ChatBasicMessageImpl implements _ChatBasicMessage {
|
||||||
|
const _$ChatBasicMessageImpl({
|
||||||
|
required this.id,
|
||||||
|
required this.sessionId,
|
||||||
|
required this.content,
|
||||||
|
required this.senderId,
|
||||||
|
required this.senderName,
|
||||||
|
required this.timestamp,
|
||||||
|
this.messageType = 'text',
|
||||||
|
this.audioUrl,
|
||||||
|
this.durationMs = 0,
|
||||||
|
this.fileSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _$ChatBasicMessageImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$ChatBasicMessageImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String sessionId;
|
||||||
|
@override
|
||||||
|
final String content;
|
||||||
|
@override
|
||||||
|
final String senderId;
|
||||||
|
@override
|
||||||
|
final String senderName;
|
||||||
|
@override
|
||||||
|
final DateTime timestamp;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String messageType;
|
||||||
|
@override
|
||||||
|
final String? audioUrl;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int durationMs;
|
||||||
|
@override
|
||||||
|
final int? fileSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ChatBasicMessage(id: $id, sessionId: $sessionId, content: $content, senderId: $senderId, senderName: $senderName, timestamp: $timestamp, messageType: $messageType, audioUrl: $audioUrl, durationMs: $durationMs, fileSize: $fileSize)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$ChatBasicMessageImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.sessionId, sessionId) ||
|
||||||
|
other.sessionId == sessionId) &&
|
||||||
|
(identical(other.content, content) || other.content == content) &&
|
||||||
|
(identical(other.senderId, senderId) ||
|
||||||
|
other.senderId == senderId) &&
|
||||||
|
(identical(other.senderName, senderName) ||
|
||||||
|
other.senderName == senderName) &&
|
||||||
|
(identical(other.timestamp, timestamp) ||
|
||||||
|
other.timestamp == timestamp) &&
|
||||||
|
(identical(other.messageType, messageType) ||
|
||||||
|
other.messageType == messageType) &&
|
||||||
|
(identical(other.audioUrl, audioUrl) ||
|
||||||
|
other.audioUrl == audioUrl) &&
|
||||||
|
(identical(other.durationMs, durationMs) ||
|
||||||
|
other.durationMs == durationMs) &&
|
||||||
|
(identical(other.fileSize, fileSize) ||
|
||||||
|
other.fileSize == fileSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
sessionId,
|
||||||
|
content,
|
||||||
|
senderId,
|
||||||
|
senderName,
|
||||||
|
timestamp,
|
||||||
|
messageType,
|
||||||
|
audioUrl,
|
||||||
|
durationMs,
|
||||||
|
fileSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicMessage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$ChatBasicMessageImplCopyWith<_$ChatBasicMessageImpl> get copyWith =>
|
||||||
|
__$$ChatBasicMessageImplCopyWithImpl<_$ChatBasicMessageImpl>(
|
||||||
|
this,
|
||||||
|
_$identity,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$ChatBasicMessageImplToJson(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _ChatBasicMessage implements ChatBasicMessage {
|
||||||
|
const factory _ChatBasicMessage({
|
||||||
|
required final String id,
|
||||||
|
required final String sessionId,
|
||||||
|
required final String content,
|
||||||
|
required final String senderId,
|
||||||
|
required final String senderName,
|
||||||
|
required final DateTime timestamp,
|
||||||
|
final String messageType,
|
||||||
|
final String? audioUrl,
|
||||||
|
final int durationMs,
|
||||||
|
final int? fileSize,
|
||||||
|
}) = _$ChatBasicMessageImpl;
|
||||||
|
|
||||||
|
factory _ChatBasicMessage.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$ChatBasicMessageImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
@override
|
||||||
|
String get sessionId;
|
||||||
|
@override
|
||||||
|
String get content;
|
||||||
|
@override
|
||||||
|
String get senderId;
|
||||||
|
@override
|
||||||
|
String get senderName;
|
||||||
|
@override
|
||||||
|
DateTime get timestamp;
|
||||||
|
@override
|
||||||
|
String get messageType;
|
||||||
|
@override
|
||||||
|
String? get audioUrl;
|
||||||
|
@override
|
||||||
|
int get durationMs;
|
||||||
|
@override
|
||||||
|
int? get fileSize;
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicMessage
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$ChatBasicMessageImplCopyWith<_$ChatBasicMessageImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatBasicSession _$ChatBasicSessionFromJson(Map<String, dynamic> json) {
|
||||||
|
return _ChatBasicSession.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ChatBasicSession {
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
String get userId => throw _privateConstructorUsedError;
|
||||||
|
String get title => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
String get status => throw _privateConstructorUsedError;
|
||||||
|
String? get description => throw _privateConstructorUsedError;
|
||||||
|
int get messageCount => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this ChatBasicSession to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$ChatBasicSessionCopyWith<ChatBasicSession> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $ChatBasicSessionCopyWith<$Res> {
|
||||||
|
factory $ChatBasicSessionCopyWith(
|
||||||
|
ChatBasicSession value,
|
||||||
|
$Res Function(ChatBasicSession) then,
|
||||||
|
) = _$ChatBasicSessionCopyWithImpl<$Res, ChatBasicSession>;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id,
|
||||||
|
String userId,
|
||||||
|
String title,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
String status,
|
||||||
|
String? description,
|
||||||
|
int messageCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$ChatBasicSessionCopyWithImpl<$Res, $Val extends ChatBasicSession>
|
||||||
|
implements $ChatBasicSessionCopyWith<$Res> {
|
||||||
|
_$ChatBasicSessionCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? userId = null,
|
||||||
|
Object? title = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? status = null,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? messageCount = null,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
userId: null == userId
|
||||||
|
? _value.userId
|
||||||
|
: userId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: null == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
status: null == status
|
||||||
|
? _value.status
|
||||||
|
: status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
messageCount: null == messageCount
|
||||||
|
? _value.messageCount
|
||||||
|
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$ChatBasicSessionImplCopyWith<$Res>
|
||||||
|
implements $ChatBasicSessionCopyWith<$Res> {
|
||||||
|
factory _$$ChatBasicSessionImplCopyWith(
|
||||||
|
_$ChatBasicSessionImpl value,
|
||||||
|
$Res Function(_$ChatBasicSessionImpl) then,
|
||||||
|
) = __$$ChatBasicSessionImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id,
|
||||||
|
String userId,
|
||||||
|
String title,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
String status,
|
||||||
|
String? description,
|
||||||
|
int messageCount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$ChatBasicSessionImplCopyWithImpl<$Res>
|
||||||
|
extends _$ChatBasicSessionCopyWithImpl<$Res, _$ChatBasicSessionImpl>
|
||||||
|
implements _$$ChatBasicSessionImplCopyWith<$Res> {
|
||||||
|
__$$ChatBasicSessionImplCopyWithImpl(
|
||||||
|
_$ChatBasicSessionImpl _value,
|
||||||
|
$Res Function(_$ChatBasicSessionImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? userId = null,
|
||||||
|
Object? title = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? status = null,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? messageCount = null,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_$ChatBasicSessionImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
userId: null == userId
|
||||||
|
? _value.userId
|
||||||
|
: userId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: null == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
status: null == status
|
||||||
|
? _value.status
|
||||||
|
: status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
messageCount: null == messageCount
|
||||||
|
? _value.messageCount
|
||||||
|
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$ChatBasicSessionImpl implements _ChatBasicSession {
|
||||||
|
const _$ChatBasicSessionImpl({
|
||||||
|
required this.id,
|
||||||
|
required this.userId,
|
||||||
|
required this.title,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.status = 'active',
|
||||||
|
this.description,
|
||||||
|
this.messageCount = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _$ChatBasicSessionImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$ChatBasicSessionImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String userId;
|
||||||
|
@override
|
||||||
|
final String title;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final String status;
|
||||||
|
@override
|
||||||
|
final String? description;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int messageCount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ChatBasicSession(id: $id, userId: $userId, title: $title, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, description: $description, messageCount: $messageCount)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$ChatBasicSessionImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.userId, userId) || other.userId == userId) &&
|
||||||
|
(identical(other.title, title) || other.title == title) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.status, status) || other.status == status) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
(identical(other.messageCount, messageCount) ||
|
||||||
|
other.messageCount == messageCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
title,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
status,
|
||||||
|
description,
|
||||||
|
messageCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$ChatBasicSessionImplCopyWith<_$ChatBasicSessionImpl> get copyWith =>
|
||||||
|
__$$ChatBasicSessionImplCopyWithImpl<_$ChatBasicSessionImpl>(
|
||||||
|
this,
|
||||||
|
_$identity,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$ChatBasicSessionImplToJson(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _ChatBasicSession implements ChatBasicSession {
|
||||||
|
const factory _ChatBasicSession({
|
||||||
|
required final String id,
|
||||||
|
required final String userId,
|
||||||
|
required final String title,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
final String status,
|
||||||
|
final String? description,
|
||||||
|
final int messageCount,
|
||||||
|
}) = _$ChatBasicSessionImpl;
|
||||||
|
|
||||||
|
factory _ChatBasicSession.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$ChatBasicSessionImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
@override
|
||||||
|
String get userId;
|
||||||
|
@override
|
||||||
|
String get title;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
String get status;
|
||||||
|
@override
|
||||||
|
String? get description;
|
||||||
|
@override
|
||||||
|
int get messageCount;
|
||||||
|
|
||||||
|
/// Create a copy of ChatBasicSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$ChatBasicSessionImplCopyWith<_$ChatBasicSessionImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTextMessageBasicRequest _$SendTextMessageBasicRequestFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) {
|
||||||
|
return _SendTextMessageBasicRequest.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SendTextMessageBasicRequest {
|
||||||
|
String get sessionId => throw _privateConstructorUsedError;
|
||||||
|
String get content => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SendTextMessageBasicRequest to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SendTextMessageBasicRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SendTextMessageBasicRequestCopyWith<SendTextMessageBasicRequest>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SendTextMessageBasicRequestCopyWith<$Res> {
|
||||||
|
factory $SendTextMessageBasicRequestCopyWith(
|
||||||
|
SendTextMessageBasicRequest value,
|
||||||
|
$Res Function(SendTextMessageBasicRequest) then,
|
||||||
|
) =
|
||||||
|
_$SendTextMessageBasicRequestCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
SendTextMessageBasicRequest
|
||||||
|
>;
|
||||||
|
@useResult
|
||||||
|
$Res call({String sessionId, String content});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SendTextMessageBasicRequestCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
$Val extends SendTextMessageBasicRequest
|
||||||
|
>
|
||||||
|
implements $SendTextMessageBasicRequestCopyWith<$Res> {
|
||||||
|
_$SendTextMessageBasicRequestCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SendTextMessageBasicRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({Object? sessionId = null, Object? content = null}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
sessionId: null == sessionId
|
||||||
|
? _value.sessionId
|
||||||
|
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
content: null == content
|
||||||
|
? _value.content
|
||||||
|
: content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SendTextMessageBasicRequestImplCopyWith<$Res>
|
||||||
|
implements $SendTextMessageBasicRequestCopyWith<$Res> {
|
||||||
|
factory _$$SendTextMessageBasicRequestImplCopyWith(
|
||||||
|
_$SendTextMessageBasicRequestImpl value,
|
||||||
|
$Res Function(_$SendTextMessageBasicRequestImpl) then,
|
||||||
|
) = __$$SendTextMessageBasicRequestImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({String sessionId, String content});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SendTextMessageBasicRequestImplCopyWithImpl<$Res>
|
||||||
|
extends
|
||||||
|
_$SendTextMessageBasicRequestCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
_$SendTextMessageBasicRequestImpl
|
||||||
|
>
|
||||||
|
implements _$$SendTextMessageBasicRequestImplCopyWith<$Res> {
|
||||||
|
__$$SendTextMessageBasicRequestImplCopyWithImpl(
|
||||||
|
_$SendTextMessageBasicRequestImpl _value,
|
||||||
|
$Res Function(_$SendTextMessageBasicRequestImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SendTextMessageBasicRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({Object? sessionId = null, Object? content = null}) {
|
||||||
|
return _then(
|
||||||
|
_$SendTextMessageBasicRequestImpl(
|
||||||
|
sessionId: null == sessionId
|
||||||
|
? _value.sessionId
|
||||||
|
: sessionId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
content: null == content
|
||||||
|
? _value.content
|
||||||
|
: content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SendTextMessageBasicRequestImpl
|
||||||
|
implements _SendTextMessageBasicRequest {
|
||||||
|
const _$SendTextMessageBasicRequestImpl({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _$SendTextMessageBasicRequestImpl.fromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$$SendTextMessageBasicRequestImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String sessionId;
|
||||||
|
@override
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SendTextMessageBasicRequest(sessionId: $sessionId, content: $content)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SendTextMessageBasicRequestImpl &&
|
||||||
|
(identical(other.sessionId, sessionId) ||
|
||||||
|
other.sessionId == sessionId) &&
|
||||||
|
(identical(other.content, content) || other.content == content));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, sessionId, content);
|
||||||
|
|
||||||
|
/// Create a copy of SendTextMessageBasicRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SendTextMessageBasicRequestImplCopyWith<_$SendTextMessageBasicRequestImpl>
|
||||||
|
get copyWith =>
|
||||||
|
__$$SendTextMessageBasicRequestImplCopyWithImpl<
|
||||||
|
_$SendTextMessageBasicRequestImpl
|
||||||
|
>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SendTextMessageBasicRequestImplToJson(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SendTextMessageBasicRequest
|
||||||
|
implements SendTextMessageBasicRequest {
|
||||||
|
const factory _SendTextMessageBasicRequest({
|
||||||
|
required final String sessionId,
|
||||||
|
required final String content,
|
||||||
|
}) = _$SendTextMessageBasicRequestImpl;
|
||||||
|
|
||||||
|
factory _SendTextMessageBasicRequest.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SendTextMessageBasicRequestImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sessionId;
|
||||||
|
@override
|
||||||
|
String get content;
|
||||||
|
|
||||||
|
/// Create a copy of SendTextMessageBasicRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SendTextMessageBasicRequestImplCopyWith<_$SendTextMessageBasicRequestImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'chat_basic.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$ChatBasicMessageImpl _$$ChatBasicMessageImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatBasicMessageImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
content: json['content'] as String,
|
||||||
|
senderId: json['senderId'] as String,
|
||||||
|
senderName: json['senderName'] as String,
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
messageType: json['messageType'] as String? ?? 'text',
|
||||||
|
audioUrl: json['audioUrl'] as String?,
|
||||||
|
durationMs: (json['durationMs'] as num?)?.toInt() ?? 0,
|
||||||
|
fileSize: (json['fileSize'] as num?)?.toInt(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatBasicMessageImplToJson(
|
||||||
|
_$ChatBasicMessageImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'content': instance.content,
|
||||||
|
'senderId': instance.senderId,
|
||||||
|
'senderName': instance.senderName,
|
||||||
|
'timestamp': instance.timestamp.toIso8601String(),
|
||||||
|
'messageType': instance.messageType,
|
||||||
|
'audioUrl': instance.audioUrl,
|
||||||
|
'durationMs': instance.durationMs,
|
||||||
|
'fileSize': instance.fileSize,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$ChatBasicSessionImpl _$$ChatBasicSessionImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatBasicSessionImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
userId: json['userId'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
|
status: json['status'] as String? ?? 'active',
|
||||||
|
description: json['description'] as String?,
|
||||||
|
messageCount: (json['messageCount'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatBasicSessionImplToJson(
|
||||||
|
_$ChatBasicSessionImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'userId': instance.userId,
|
||||||
|
'title': instance.title,
|
||||||
|
'createdAt': instance.createdAt.toIso8601String(),
|
||||||
|
'updatedAt': instance.updatedAt.toIso8601String(),
|
||||||
|
'status': instance.status,
|
||||||
|
'description': instance.description,
|
||||||
|
'messageCount': instance.messageCount,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$SendTextMessageBasicRequestImpl _$$SendTextMessageBasicRequestImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$SendTextMessageBasicRequestImpl(
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
content: json['content'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SendTextMessageBasicRequestImplToJson(
|
||||||
|
_$SendTextMessageBasicRequestImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'content': instance.content,
|
||||||
|
};
|
||||||
107
chat/mnemo_cards_chat/lib/src/domain/models/chat_message.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'chat_message.freezed.dart';
|
||||||
|
part 'chat_message.g.dart';
|
||||||
|
|
||||||
|
/// Base class for all chat messages
|
||||||
|
@freezed
|
||||||
|
class ChatMessage with _$ChatMessage {
|
||||||
|
const factory ChatMessage.text(TextMessage message) = ChatMessageText;
|
||||||
|
|
||||||
|
const factory ChatMessage.audio(AudioMessage message) = ChatMessageAudio;
|
||||||
|
|
||||||
|
factory ChatMessage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatMessageFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text message in chat
|
||||||
|
@freezed
|
||||||
|
class TextMessage with _$TextMessage {
|
||||||
|
const factory TextMessage({
|
||||||
|
required String id,
|
||||||
|
required String sessionId,
|
||||||
|
required String content,
|
||||||
|
required ChatParticipant sender,
|
||||||
|
required DateTime timestamp,
|
||||||
|
@Default(MessageStatus.sent) MessageStatus status,
|
||||||
|
String? metadata,
|
||||||
|
}) = _TextMessage;
|
||||||
|
|
||||||
|
factory TextMessage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TextMessageFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio message in chat
|
||||||
|
@freezed
|
||||||
|
class AudioMessage with _$AudioMessage {
|
||||||
|
const factory AudioMessage({
|
||||||
|
required String id,
|
||||||
|
required String sessionId,
|
||||||
|
required String audioUrl,
|
||||||
|
required ChatParticipant sender,
|
||||||
|
required DateTime timestamp,
|
||||||
|
required Duration duration,
|
||||||
|
required int fileSize, // in bytes
|
||||||
|
@Default(MessageStatus.sent) MessageStatus status,
|
||||||
|
String? transcription, // optional text transcription
|
||||||
|
String? metadata,
|
||||||
|
}) = _AudioMessage;
|
||||||
|
|
||||||
|
factory AudioMessage.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AudioMessageFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Participant in chat conversation
|
||||||
|
@freezed
|
||||||
|
class ChatParticipant with _$ChatParticipant {
|
||||||
|
const factory ChatParticipant.user({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
String? avatarUrl,
|
||||||
|
}) = ChatParticipantUser;
|
||||||
|
|
||||||
|
const factory ChatParticipant.assistant({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
String? avatarUrl,
|
||||||
|
String? model, // LLM model name
|
||||||
|
}) = ChatParticipantAssistant;
|
||||||
|
|
||||||
|
factory ChatParticipant.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatParticipantFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message delivery status
|
||||||
|
enum MessageStatus {
|
||||||
|
@JsonValue('sending')
|
||||||
|
sending,
|
||||||
|
|
||||||
|
@JsonValue('sent')
|
||||||
|
sent,
|
||||||
|
|
||||||
|
@JsonValue('delivered')
|
||||||
|
delivered,
|
||||||
|
|
||||||
|
@JsonValue('error')
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension for MessageStatus with display properties
|
||||||
|
extension MessageStatusExtension on MessageStatus {
|
||||||
|
String get displayName {
|
||||||
|
switch (this) {
|
||||||
|
case MessageStatus.sending:
|
||||||
|
return 'Отправляется...';
|
||||||
|
case MessageStatus.sent:
|
||||||
|
return 'Отправлено';
|
||||||
|
case MessageStatus.delivered:
|
||||||
|
return 'Доставлено';
|
||||||
|
case MessageStatus.error:
|
||||||
|
return 'Ошибка';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isError => this == MessageStatus.error;
|
||||||
|
bool get isSending => this == MessageStatus.sending;
|
||||||
|
bool get isDelivered => this == MessageStatus.delivered || this == MessageStatus.sent;
|
||||||
|
}
|
||||||
134
chat/mnemo_cards_chat/lib/src/domain/models/chat_message.g.dart
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'chat_message.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$ChatMessageTextImpl _$$ChatMessageTextImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatMessageTextImpl(
|
||||||
|
TextMessage.fromJson(json['message'] as Map<String, dynamic>),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatMessageTextImplToJson(
|
||||||
|
_$ChatMessageTextImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'message': instance.message,
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$ChatMessageAudioImpl _$$ChatMessageAudioImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatMessageAudioImpl(
|
||||||
|
AudioMessage.fromJson(json['message'] as Map<String, dynamic>),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatMessageAudioImplToJson(
|
||||||
|
_$ChatMessageAudioImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'message': instance.message,
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$TextMessageImpl _$$TextMessageImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TextMessageImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
content: json['content'] as String,
|
||||||
|
sender: ChatParticipant.fromJson(json['sender'] as Map<String, dynamic>),
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
status:
|
||||||
|
$enumDecodeNullable(_$MessageStatusEnumMap, json['status']) ??
|
||||||
|
MessageStatus.sent,
|
||||||
|
metadata: json['metadata'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$TextMessageImplToJson(_$TextMessageImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'content': instance.content,
|
||||||
|
'sender': instance.sender,
|
||||||
|
'timestamp': instance.timestamp.toIso8601String(),
|
||||||
|
'status': _$MessageStatusEnumMap[instance.status]!,
|
||||||
|
'metadata': instance.metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$MessageStatusEnumMap = {
|
||||||
|
MessageStatus.sending: 'sending',
|
||||||
|
MessageStatus.sent: 'sent',
|
||||||
|
MessageStatus.delivered: 'delivered',
|
||||||
|
MessageStatus.error: 'error',
|
||||||
|
};
|
||||||
|
|
||||||
|
_$AudioMessageImpl _$$AudioMessageImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AudioMessageImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
sessionId: json['sessionId'] as String,
|
||||||
|
audioUrl: json['audioUrl'] as String,
|
||||||
|
sender: ChatParticipant.fromJson(json['sender'] as Map<String, dynamic>),
|
||||||
|
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||||
|
duration: Duration(microseconds: (json['duration'] as num).toInt()),
|
||||||
|
fileSize: (json['fileSize'] as num).toInt(),
|
||||||
|
status:
|
||||||
|
$enumDecodeNullable(_$MessageStatusEnumMap, json['status']) ??
|
||||||
|
MessageStatus.sent,
|
||||||
|
transcription: json['transcription'] as String?,
|
||||||
|
metadata: json['metadata'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$AudioMessageImplToJson(_$AudioMessageImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'sessionId': instance.sessionId,
|
||||||
|
'audioUrl': instance.audioUrl,
|
||||||
|
'sender': instance.sender,
|
||||||
|
'timestamp': instance.timestamp.toIso8601String(),
|
||||||
|
'duration': instance.duration.inMicroseconds,
|
||||||
|
'fileSize': instance.fileSize,
|
||||||
|
'status': _$MessageStatusEnumMap[instance.status]!,
|
||||||
|
'transcription': instance.transcription,
|
||||||
|
'metadata': instance.metadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$ChatParticipantUserImpl _$$ChatParticipantUserImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatParticipantUserImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
avatarUrl: json['avatarUrl'] as String?,
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatParticipantUserImplToJson(
|
||||||
|
_$ChatParticipantUserImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'avatarUrl': instance.avatarUrl,
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$ChatParticipantAssistantImpl _$$ChatParticipantAssistantImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$ChatParticipantAssistantImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
avatarUrl: json['avatarUrl'] as String?,
|
||||||
|
model: json['model'] as String?,
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatParticipantAssistantImplToJson(
|
||||||
|
_$ChatParticipantAssistantImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'avatarUrl': instance.avatarUrl,
|
||||||
|
'model': instance.model,
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
||||||
114
chat/mnemo_cards_chat/lib/src/domain/models/chat_models.dart
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
/// Non-serializable chat models and utilities
|
||||||
|
/// These models contain binary data or platform-specific types that cannot be JSON serialized
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
/// Request to send audio message to chat with binary data
|
||||||
|
class SendAudioMessageData {
|
||||||
|
const SendAudioMessageData({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.audioData,
|
||||||
|
required this.duration,
|
||||||
|
this.fileName,
|
||||||
|
this.mimeType,
|
||||||
|
this.transcription,
|
||||||
|
this.metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String sessionId;
|
||||||
|
final Uint8List audioData; // Binary audio data
|
||||||
|
final Duration duration;
|
||||||
|
final String? fileName;
|
||||||
|
final String? mimeType; // e.g., 'audio/webm', 'audio/wav'
|
||||||
|
final String? transcription;
|
||||||
|
final String? metadata;
|
||||||
|
|
||||||
|
/// Create from web audio recording (Blob/ArrayBuffer)
|
||||||
|
factory SendAudioMessageData.fromWebAudio({
|
||||||
|
required String sessionId,
|
||||||
|
required dynamic audioBlob, // Web Blob or ArrayBuffer
|
||||||
|
required Duration duration,
|
||||||
|
String? transcription,
|
||||||
|
String? metadata,
|
||||||
|
}) {
|
||||||
|
// Convert to Uint8List (implementation depends on web_audio_api or similar)
|
||||||
|
final audioData = Uint8List(0); // Placeholder - actual conversion needed
|
||||||
|
return SendAudioMessageData(
|
||||||
|
sessionId: sessionId,
|
||||||
|
audioData: audioData,
|
||||||
|
duration: duration,
|
||||||
|
fileName: 'recording.webm',
|
||||||
|
mimeType: 'audio/webm',
|
||||||
|
transcription: transcription,
|
||||||
|
metadata: metadata,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get file size in bytes
|
||||||
|
int get fileSize => audioData.length;
|
||||||
|
|
||||||
|
/// Validate audio data
|
||||||
|
bool get isValid => audioData.isNotEmpty && duration.inMilliseconds > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio recording state
|
||||||
|
enum AudioRecordingState {
|
||||||
|
idle,
|
||||||
|
recording,
|
||||||
|
paused,
|
||||||
|
stopped,
|
||||||
|
processing,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio playback state
|
||||||
|
enum AudioPlaybackState {
|
||||||
|
idle,
|
||||||
|
loading,
|
||||||
|
playing,
|
||||||
|
paused,
|
||||||
|
stopped,
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio recording session information
|
||||||
|
class AudioRecordingSession {
|
||||||
|
AudioRecordingSession({
|
||||||
|
required this.startTime,
|
||||||
|
this.endTime,
|
||||||
|
this.duration = Duration.zero,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DateTime startTime;
|
||||||
|
DateTime? endTime;
|
||||||
|
Duration duration;
|
||||||
|
|
||||||
|
bool get isActive => endTime == null;
|
||||||
|
bool get isCompleted => endTime != null;
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
endTime = DateTime.now();
|
||||||
|
duration = endTime!.difference(startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio playback information
|
||||||
|
class AudioPlaybackInfo {
|
||||||
|
const AudioPlaybackInfo({
|
||||||
|
required this.currentTime,
|
||||||
|
required this.totalDuration,
|
||||||
|
required this.isPlaying,
|
||||||
|
required this.volume,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Duration currentTime;
|
||||||
|
final Duration totalDuration;
|
||||||
|
final bool isPlaying;
|
||||||
|
final double volume; // 0.0 to 1.0
|
||||||
|
|
||||||
|
double get progress => totalDuration.inMilliseconds > 0
|
||||||
|
? currentTime.inMilliseconds / totalDuration.inMilliseconds
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
bool get isCompleted => currentTime >= totalDuration;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'chat_session.freezed.dart';
|
||||||
|
part 'chat_session.g.dart';
|
||||||
|
|
||||||
|
/// Chat session containing conversation between user and assistant
|
||||||
|
@freezed
|
||||||
|
class ChatSession with _$ChatSession {
|
||||||
|
const factory ChatSession({
|
||||||
|
required String id,
|
||||||
|
required String userId,
|
||||||
|
required String title,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
@Default(ChatSessionStatus.active) ChatSessionStatus status,
|
||||||
|
String? description,
|
||||||
|
@Default([]) List<String> tags,
|
||||||
|
@Default(0) int messageCount,
|
||||||
|
String? lastMessagePreview,
|
||||||
|
DateTime? lastMessageAt,
|
||||||
|
}) = _ChatSession;
|
||||||
|
|
||||||
|
factory ChatSession.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatSessionFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status of chat session
|
||||||
|
enum ChatSessionStatus {
|
||||||
|
@JsonValue('active')
|
||||||
|
active,
|
||||||
|
|
||||||
|
@JsonValue('archived')
|
||||||
|
archived,
|
||||||
|
|
||||||
|
@JsonValue('deleted')
|
||||||
|
deleted,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension for ChatSessionStatus
|
||||||
|
extension ChatSessionStatusExtension on ChatSessionStatus {
|
||||||
|
String get displayName {
|
||||||
|
switch (this) {
|
||||||
|
case ChatSessionStatus.active:
|
||||||
|
return 'Активный';
|
||||||
|
case ChatSessionStatus.archived:
|
||||||
|
return 'Архивирован';
|
||||||
|
case ChatSessionStatus.deleted:
|
||||||
|
return 'Удален';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isActive => this == ChatSessionStatus.active;
|
||||||
|
bool get isArchived => this == ChatSessionStatus.archived;
|
||||||
|
bool get isDeleted => this == ChatSessionStatus.deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DTO for updating chat session
|
||||||
|
@freezed
|
||||||
|
class UpdateChatSessionRequest with _$UpdateChatSessionRequest {
|
||||||
|
const factory UpdateChatSessionRequest({
|
||||||
|
String? title,
|
||||||
|
String? description,
|
||||||
|
List<String>? tags,
|
||||||
|
ChatSessionStatus? status,
|
||||||
|
}) = _UpdateChatSessionRequest;
|
||||||
|
|
||||||
|
factory UpdateChatSessionRequest.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UpdateChatSessionRequestFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,657 @@
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'chat_session.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||||
|
);
|
||||||
|
|
||||||
|
ChatSession _$ChatSessionFromJson(Map<String, dynamic> json) {
|
||||||
|
return _ChatSession.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ChatSession {
|
||||||
|
String get id => throw _privateConstructorUsedError;
|
||||||
|
String get userId => throw _privateConstructorUsedError;
|
||||||
|
String get title => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
ChatSessionStatus get status => throw _privateConstructorUsedError;
|
||||||
|
String? get description => throw _privateConstructorUsedError;
|
||||||
|
List<String> get tags => throw _privateConstructorUsedError;
|
||||||
|
int get messageCount => throw _privateConstructorUsedError;
|
||||||
|
String? get lastMessagePreview => throw _privateConstructorUsedError;
|
||||||
|
DateTime? get lastMessageAt => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this ChatSession to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of ChatSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$ChatSessionCopyWith<ChatSession> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $ChatSessionCopyWith<$Res> {
|
||||||
|
factory $ChatSessionCopyWith(
|
||||||
|
ChatSession value,
|
||||||
|
$Res Function(ChatSession) then,
|
||||||
|
) = _$ChatSessionCopyWithImpl<$Res, ChatSession>;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id,
|
||||||
|
String userId,
|
||||||
|
String title,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
ChatSessionStatus status,
|
||||||
|
String? description,
|
||||||
|
List<String> tags,
|
||||||
|
int messageCount,
|
||||||
|
String? lastMessagePreview,
|
||||||
|
DateTime? lastMessageAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$ChatSessionCopyWithImpl<$Res, $Val extends ChatSession>
|
||||||
|
implements $ChatSessionCopyWith<$Res> {
|
||||||
|
_$ChatSessionCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ChatSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? userId = null,
|
||||||
|
Object? title = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? status = null,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? tags = null,
|
||||||
|
Object? messageCount = null,
|
||||||
|
Object? lastMessagePreview = freezed,
|
||||||
|
Object? lastMessageAt = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
userId: null == userId
|
||||||
|
? _value.userId
|
||||||
|
: userId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: null == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
status: null == status
|
||||||
|
? _value.status
|
||||||
|
: status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ChatSessionStatus,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
tags: null == tags
|
||||||
|
? _value.tags
|
||||||
|
: tags // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,
|
||||||
|
messageCount: null == messageCount
|
||||||
|
? _value.messageCount
|
||||||
|
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
lastMessagePreview: freezed == lastMessagePreview
|
||||||
|
? _value.lastMessagePreview
|
||||||
|
: lastMessagePreview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
lastMessageAt: freezed == lastMessageAt
|
||||||
|
? _value.lastMessageAt
|
||||||
|
: lastMessageAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$ChatSessionImplCopyWith<$Res>
|
||||||
|
implements $ChatSessionCopyWith<$Res> {
|
||||||
|
factory _$$ChatSessionImplCopyWith(
|
||||||
|
_$ChatSessionImpl value,
|
||||||
|
$Res Function(_$ChatSessionImpl) then,
|
||||||
|
) = __$$ChatSessionImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id,
|
||||||
|
String userId,
|
||||||
|
String title,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
ChatSessionStatus status,
|
||||||
|
String? description,
|
||||||
|
List<String> tags,
|
||||||
|
int messageCount,
|
||||||
|
String? lastMessagePreview,
|
||||||
|
DateTime? lastMessageAt,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$ChatSessionImplCopyWithImpl<$Res>
|
||||||
|
extends _$ChatSessionCopyWithImpl<$Res, _$ChatSessionImpl>
|
||||||
|
implements _$$ChatSessionImplCopyWith<$Res> {
|
||||||
|
__$$ChatSessionImplCopyWithImpl(
|
||||||
|
_$ChatSessionImpl _value,
|
||||||
|
$Res Function(_$ChatSessionImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of ChatSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? userId = null,
|
||||||
|
Object? title = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? status = null,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? tags = null,
|
||||||
|
Object? messageCount = null,
|
||||||
|
Object? lastMessagePreview = freezed,
|
||||||
|
Object? lastMessageAt = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_$ChatSessionImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
userId: null == userId
|
||||||
|
? _value.userId
|
||||||
|
: userId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: null == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
status: null == status
|
||||||
|
? _value.status
|
||||||
|
: status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ChatSessionStatus,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
tags: null == tags
|
||||||
|
? _value._tags
|
||||||
|
: tags // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,
|
||||||
|
messageCount: null == messageCount
|
||||||
|
? _value.messageCount
|
||||||
|
: messageCount // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
lastMessagePreview: freezed == lastMessagePreview
|
||||||
|
? _value.lastMessagePreview
|
||||||
|
: lastMessagePreview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
lastMessageAt: freezed == lastMessageAt
|
||||||
|
? _value.lastMessageAt
|
||||||
|
: lastMessageAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$ChatSessionImpl implements _ChatSession {
|
||||||
|
const _$ChatSessionImpl({
|
||||||
|
required this.id,
|
||||||
|
required this.userId,
|
||||||
|
required this.title,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.status = ChatSessionStatus.active,
|
||||||
|
this.description,
|
||||||
|
final List<String> tags = const [],
|
||||||
|
this.messageCount = 0,
|
||||||
|
this.lastMessagePreview,
|
||||||
|
this.lastMessageAt,
|
||||||
|
}) : _tags = tags;
|
||||||
|
|
||||||
|
factory _$ChatSessionImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$ChatSessionImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String id;
|
||||||
|
@override
|
||||||
|
final String userId;
|
||||||
|
@override
|
||||||
|
final String title;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final ChatSessionStatus status;
|
||||||
|
@override
|
||||||
|
final String? description;
|
||||||
|
final List<String> _tags;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
List<String> get tags {
|
||||||
|
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final int messageCount;
|
||||||
|
@override
|
||||||
|
final String? lastMessagePreview;
|
||||||
|
@override
|
||||||
|
final DateTime? lastMessageAt;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ChatSession(id: $id, userId: $userId, title: $title, createdAt: $createdAt, updatedAt: $updatedAt, status: $status, description: $description, tags: $tags, messageCount: $messageCount, lastMessagePreview: $lastMessagePreview, lastMessageAt: $lastMessageAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$ChatSessionImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.userId, userId) || other.userId == userId) &&
|
||||||
|
(identical(other.title, title) || other.title == title) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.status, status) || other.status == status) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||||
|
(identical(other.messageCount, messageCount) ||
|
||||||
|
other.messageCount == messageCount) &&
|
||||||
|
(identical(other.lastMessagePreview, lastMessagePreview) ||
|
||||||
|
other.lastMessagePreview == lastMessagePreview) &&
|
||||||
|
(identical(other.lastMessageAt, lastMessageAt) ||
|
||||||
|
other.lastMessageAt == lastMessageAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
userId,
|
||||||
|
title,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
status,
|
||||||
|
description,
|
||||||
|
const DeepCollectionEquality().hash(_tags),
|
||||||
|
messageCount,
|
||||||
|
lastMessagePreview,
|
||||||
|
lastMessageAt,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Create a copy of ChatSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$ChatSessionImplCopyWith<_$ChatSessionImpl> get copyWith =>
|
||||||
|
__$$ChatSessionImplCopyWithImpl<_$ChatSessionImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$ChatSessionImplToJson(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _ChatSession implements ChatSession {
|
||||||
|
const factory _ChatSession({
|
||||||
|
required final String id,
|
||||||
|
required final String userId,
|
||||||
|
required final String title,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
final ChatSessionStatus status,
|
||||||
|
final String? description,
|
||||||
|
final List<String> tags,
|
||||||
|
final int messageCount,
|
||||||
|
final String? lastMessagePreview,
|
||||||
|
final DateTime? lastMessageAt,
|
||||||
|
}) = _$ChatSessionImpl;
|
||||||
|
|
||||||
|
factory _ChatSession.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$ChatSessionImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id;
|
||||||
|
@override
|
||||||
|
String get userId;
|
||||||
|
@override
|
||||||
|
String get title;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
ChatSessionStatus get status;
|
||||||
|
@override
|
||||||
|
String? get description;
|
||||||
|
@override
|
||||||
|
List<String> get tags;
|
||||||
|
@override
|
||||||
|
int get messageCount;
|
||||||
|
@override
|
||||||
|
String? get lastMessagePreview;
|
||||||
|
@override
|
||||||
|
DateTime? get lastMessageAt;
|
||||||
|
|
||||||
|
/// Create a copy of ChatSession
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$ChatSessionImplCopyWith<_$ChatSessionImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateChatSessionRequest _$UpdateChatSessionRequestFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) {
|
||||||
|
return _UpdateChatSessionRequest.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$UpdateChatSessionRequest {
|
||||||
|
String? get title => throw _privateConstructorUsedError;
|
||||||
|
String? get description => throw _privateConstructorUsedError;
|
||||||
|
List<String>? get tags => throw _privateConstructorUsedError;
|
||||||
|
ChatSessionStatus? get status => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this UpdateChatSessionRequest to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of UpdateChatSessionRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$UpdateChatSessionRequestCopyWith<UpdateChatSessionRequest> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $UpdateChatSessionRequestCopyWith<$Res> {
|
||||||
|
factory $UpdateChatSessionRequestCopyWith(
|
||||||
|
UpdateChatSessionRequest value,
|
||||||
|
$Res Function(UpdateChatSessionRequest) then,
|
||||||
|
) = _$UpdateChatSessionRequestCopyWithImpl<$Res, UpdateChatSessionRequest>;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String? title,
|
||||||
|
String? description,
|
||||||
|
List<String>? tags,
|
||||||
|
ChatSessionStatus? status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$UpdateChatSessionRequestCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
$Val extends UpdateChatSessionRequest
|
||||||
|
>
|
||||||
|
implements $UpdateChatSessionRequestCopyWith<$Res> {
|
||||||
|
_$UpdateChatSessionRequestCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of UpdateChatSessionRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? title = freezed,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? tags = freezed,
|
||||||
|
Object? status = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_value.copyWith(
|
||||||
|
title: freezed == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
tags: freezed == tags
|
||||||
|
? _value.tags
|
||||||
|
: tags // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,
|
||||||
|
status: freezed == status
|
||||||
|
? _value.status
|
||||||
|
: status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ChatSessionStatus?,
|
||||||
|
)
|
||||||
|
as $Val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$UpdateChatSessionRequestImplCopyWith<$Res>
|
||||||
|
implements $UpdateChatSessionRequestCopyWith<$Res> {
|
||||||
|
factory _$$UpdateChatSessionRequestImplCopyWith(
|
||||||
|
_$UpdateChatSessionRequestImpl value,
|
||||||
|
$Res Function(_$UpdateChatSessionRequestImpl) then,
|
||||||
|
) = __$$UpdateChatSessionRequestImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String? title,
|
||||||
|
String? description,
|
||||||
|
List<String>? tags,
|
||||||
|
ChatSessionStatus? status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$UpdateChatSessionRequestImplCopyWithImpl<$Res>
|
||||||
|
extends
|
||||||
|
_$UpdateChatSessionRequestCopyWithImpl<
|
||||||
|
$Res,
|
||||||
|
_$UpdateChatSessionRequestImpl
|
||||||
|
>
|
||||||
|
implements _$$UpdateChatSessionRequestImplCopyWith<$Res> {
|
||||||
|
__$$UpdateChatSessionRequestImplCopyWithImpl(
|
||||||
|
_$UpdateChatSessionRequestImpl _value,
|
||||||
|
$Res Function(_$UpdateChatSessionRequestImpl) _then,
|
||||||
|
) : super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of UpdateChatSessionRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? title = freezed,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? tags = freezed,
|
||||||
|
Object? status = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(
|
||||||
|
_$UpdateChatSessionRequestImpl(
|
||||||
|
title: freezed == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
tags: freezed == tags
|
||||||
|
? _value._tags
|
||||||
|
: tags // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,
|
||||||
|
status: freezed == status
|
||||||
|
? _value.status
|
||||||
|
: status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ChatSessionStatus?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$UpdateChatSessionRequestImpl implements _UpdateChatSessionRequest {
|
||||||
|
const _$UpdateChatSessionRequestImpl({
|
||||||
|
this.title,
|
||||||
|
this.description,
|
||||||
|
final List<String>? tags,
|
||||||
|
this.status,
|
||||||
|
}) : _tags = tags;
|
||||||
|
|
||||||
|
factory _$UpdateChatSessionRequestImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$UpdateChatSessionRequestImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String? title;
|
||||||
|
@override
|
||||||
|
final String? description;
|
||||||
|
final List<String>? _tags;
|
||||||
|
@override
|
||||||
|
List<String>? get tags {
|
||||||
|
final value = _tags;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
final ChatSessionStatus? status;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'UpdateChatSessionRequest(title: $title, description: $description, tags: $tags, status: $status)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$UpdateChatSessionRequestImpl &&
|
||||||
|
(identical(other.title, title) || other.title == title) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
const DeepCollectionEquality().equals(other._tags, _tags) &&
|
||||||
|
(identical(other.status, status) || other.status == status));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
const DeepCollectionEquality().hash(_tags),
|
||||||
|
status,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Create a copy of UpdateChatSessionRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$UpdateChatSessionRequestImplCopyWith<_$UpdateChatSessionRequestImpl>
|
||||||
|
get copyWith =>
|
||||||
|
__$$UpdateChatSessionRequestImplCopyWithImpl<
|
||||||
|
_$UpdateChatSessionRequestImpl
|
||||||
|
>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$UpdateChatSessionRequestImplToJson(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _UpdateChatSessionRequest implements UpdateChatSessionRequest {
|
||||||
|
const factory _UpdateChatSessionRequest({
|
||||||
|
final String? title,
|
||||||
|
final String? description,
|
||||||
|
final List<String>? tags,
|
||||||
|
final ChatSessionStatus? status,
|
||||||
|
}) = _$UpdateChatSessionRequestImpl;
|
||||||
|
|
||||||
|
factory _UpdateChatSessionRequest.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$UpdateChatSessionRequestImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get title;
|
||||||
|
@override
|
||||||
|
String? get description;
|
||||||
|
@override
|
||||||
|
List<String>? get tags;
|
||||||
|
@override
|
||||||
|
ChatSessionStatus? get status;
|
||||||
|
|
||||||
|
/// Create a copy of UpdateChatSessionRequest
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$UpdateChatSessionRequestImplCopyWith<_$UpdateChatSessionRequestImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'chat_session.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$ChatSessionImpl _$$ChatSessionImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ChatSessionImpl(
|
||||||
|
id: json['id'] as String,
|
||||||
|
userId: json['userId'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||||
|
status:
|
||||||
|
$enumDecodeNullable(_$ChatSessionStatusEnumMap, json['status']) ??
|
||||||
|
ChatSessionStatus.active,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
tags:
|
||||||
|
(json['tags'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||||
|
const [],
|
||||||
|
messageCount: (json['messageCount'] as num?)?.toInt() ?? 0,
|
||||||
|
lastMessagePreview: json['lastMessagePreview'] as String?,
|
||||||
|
lastMessageAt: json['lastMessageAt'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['lastMessageAt'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$ChatSessionImplToJson(_$ChatSessionImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'userId': instance.userId,
|
||||||
|
'title': instance.title,
|
||||||
|
'createdAt': instance.createdAt.toIso8601String(),
|
||||||
|
'updatedAt': instance.updatedAt.toIso8601String(),
|
||||||
|
'status': _$ChatSessionStatusEnumMap[instance.status]!,
|
||||||
|
'description': instance.description,
|
||||||
|
'tags': instance.tags,
|
||||||
|
'messageCount': instance.messageCount,
|
||||||
|
'lastMessagePreview': instance.lastMessagePreview,
|
||||||
|
'lastMessageAt': instance.lastMessageAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$ChatSessionStatusEnumMap = {
|
||||||
|
ChatSessionStatus.active: 'active',
|
||||||
|
ChatSessionStatus.archived: 'archived',
|
||||||
|
ChatSessionStatus.deleted: 'deleted',
|
||||||
|
};
|
||||||
|
|
||||||
|
_$UpdateChatSessionRequestImpl _$$UpdateChatSessionRequestImplFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _$UpdateChatSessionRequestImpl(
|
||||||
|
title: json['title'] as String?,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),
|
||||||
|
status: $enumDecodeNullable(_$ChatSessionStatusEnumMap, json['status']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$UpdateChatSessionRequestImplToJson(
|
||||||
|
_$UpdateChatSessionRequestImpl instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'tags': instance.tags,
|
||||||
|
'status': _$ChatSessionStatusEnumMap[instance.status],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import '../models/chat_api_simple.dart';
|
||||||
|
import '../models/chat_basic.dart';
|
||||||
|
|
||||||
|
/// Abstract repository interface for chat operations
|
||||||
|
///
|
||||||
|
/// This allows the chat module to be decoupled from specific HTTP implementations
|
||||||
|
abstract class ChatRepository {
|
||||||
|
/// Create new chat session
|
||||||
|
Future<ChatBasicSession> createChatSession(CreateChatSessionRequest request);
|
||||||
|
|
||||||
|
/// Get user's chat sessions
|
||||||
|
Future<List<ChatBasicSession>> getChatSessions({
|
||||||
|
int limit,
|
||||||
|
String? afterSessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Get specific chat session
|
||||||
|
Future<ChatBasicSession> getChatSession(String sessionId);
|
||||||
|
|
||||||
|
/// Send text message
|
||||||
|
Future<ChatMessageResponse> sendTextMessage(SendTextMessageRequest request);
|
||||||
|
|
||||||
|
/// Send audio message (multipart/form-data)
|
||||||
|
Future<ChatMessageResponse> sendAudioMessage(
|
||||||
|
String sessionId,
|
||||||
|
dynamic audioData,
|
||||||
|
Duration duration, {
|
||||||
|
String? fileName,
|
||||||
|
String? mimeType,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Get messages for chat session
|
||||||
|
Future<List<ChatMessageResponse>> getChatMessages(
|
||||||
|
String sessionId, {
|
||||||
|
int limit,
|
||||||
|
String? beforeMessageId,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Update chat session
|
||||||
|
Future<ChatBasicSession> updateChatSession(
|
||||||
|
String sessionId,
|
||||||
|
Map<String, dynamic> updates,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Delete chat session
|
||||||
|
Future<void> deleteChatSession(String sessionId);
|
||||||
|
}
|
||||||
135
chat/mnemo_cards_chat/lib/src/domain/services/chat_service.dart
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import '../models/chat_api_simple.dart';
|
||||||
|
import '../models/chat_basic.dart';
|
||||||
|
import 'chat_repository.dart';
|
||||||
|
|
||||||
|
/// Service for chat functionality
|
||||||
|
class ChatService {
|
||||||
|
ChatService({
|
||||||
|
required ChatRepository chatRepository,
|
||||||
|
}) : _chatRepository = chatRepository;
|
||||||
|
|
||||||
|
final ChatRepository _chatRepository;
|
||||||
|
|
||||||
|
/// Create new chat session
|
||||||
|
Future<ChatBasicSession> createSession({
|
||||||
|
required String title,
|
||||||
|
String? description,
|
||||||
|
}) async {
|
||||||
|
log('Creating new chat session: $title', name: 'ChatService');
|
||||||
|
|
||||||
|
final request = CreateChatSessionRequest(title: title);
|
||||||
|
final session = await _chatRepository.createChatSession(request);
|
||||||
|
|
||||||
|
log('Chat session created: ${session.id}', name: 'ChatService');
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get chat session by ID
|
||||||
|
Future<ChatBasicSession> getSession(String sessionId) async {
|
||||||
|
log('Getting chat session: $sessionId', name: 'ChatService');
|
||||||
|
|
||||||
|
final session = await _chatRepository.getChatSession(sessionId);
|
||||||
|
|
||||||
|
log('Chat session loaded: ${session.title}', name: 'ChatService');
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get user's chat sessions
|
||||||
|
Future<List<ChatBasicSession>> getSessions({
|
||||||
|
int limit = 20,
|
||||||
|
String? afterSessionId,
|
||||||
|
}) async {
|
||||||
|
log('Getting chat sessions (limit: $limit)', name: 'ChatService');
|
||||||
|
|
||||||
|
final sessions = await _chatRepository.getChatSessions(
|
||||||
|
limit: limit,
|
||||||
|
afterSessionId: afterSessionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
log('Loaded ${sessions.length} chat sessions', name: 'ChatService');
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send text message
|
||||||
|
Future<ChatMessageResponse> sendTextMessage(
|
||||||
|
String sessionId,
|
||||||
|
String content,
|
||||||
|
) async {
|
||||||
|
log('Sending text message to session: $sessionId', name: 'ChatService');
|
||||||
|
|
||||||
|
final request = SendTextMessageRequest(
|
||||||
|
sessionId: sessionId,
|
||||||
|
content: content,
|
||||||
|
);
|
||||||
|
|
||||||
|
final response = await _chatRepository.sendTextMessage(request);
|
||||||
|
|
||||||
|
log('Text message sent successfully', name: 'ChatService');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send audio message
|
||||||
|
Future<ChatMessageResponse> sendAudioMessage(
|
||||||
|
String sessionId,
|
||||||
|
dynamic audioData,
|
||||||
|
Duration duration, {
|
||||||
|
String? transcription,
|
||||||
|
String? fileName,
|
||||||
|
String? mimeType,
|
||||||
|
}) async {
|
||||||
|
log('Sending audio message to session: $sessionId', name: 'ChatService');
|
||||||
|
|
||||||
|
final response = await _chatRepository.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
fileName: fileName,
|
||||||
|
mimeType: mimeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
log('Audio message sent successfully', name: 'ChatService');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get messages for session
|
||||||
|
Future<List<ChatMessageResponse>> getMessages(
|
||||||
|
String sessionId, {
|
||||||
|
int limit = 50,
|
||||||
|
String? beforeMessageId,
|
||||||
|
}) async {
|
||||||
|
log('Getting messages for session: $sessionId (limit: $limit)', name: 'ChatService');
|
||||||
|
|
||||||
|
final messages = await _chatRepository.getChatMessages(
|
||||||
|
sessionId,
|
||||||
|
limit: limit,
|
||||||
|
beforeMessageId: beforeMessageId,
|
||||||
|
);
|
||||||
|
|
||||||
|
log('Loaded ${messages.length} messages', name: 'ChatService');
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update chat session
|
||||||
|
Future<ChatBasicSession> updateSession(
|
||||||
|
String sessionId,
|
||||||
|
Map<String, dynamic> updates,
|
||||||
|
) async {
|
||||||
|
log('Updating chat session: $sessionId', name: 'ChatService');
|
||||||
|
|
||||||
|
final session = await _chatRepository.updateChatSession(sessionId, updates);
|
||||||
|
|
||||||
|
log('Chat session updated: ${session.title}', name: 'ChatService');
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete chat session
|
||||||
|
Future<void> deleteSession(String sessionId) async {
|
||||||
|
log('Deleting chat session: $sessionId', name: 'ChatService');
|
||||||
|
|
||||||
|
await _chatRepository.deleteChatSession(sessionId);
|
||||||
|
|
||||||
|
log('Chat session deleted', name: 'ChatService');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:yx_state/yx_state.dart';
|
||||||
|
|
||||||
|
import '../models/chat_basic.dart';
|
||||||
|
import '../models/chat_message.dart';
|
||||||
|
import '../services/chat_service.dart';
|
||||||
|
|
||||||
|
/// State for chat functionality
|
||||||
|
class ChatState {
|
||||||
|
const ChatState._();
|
||||||
|
|
||||||
|
const factory ChatState.loading() = ChatStateLoading;
|
||||||
|
|
||||||
|
const factory ChatState.loaded({
|
||||||
|
required ChatBasicSession session,
|
||||||
|
required List<ChatMessage> messages,
|
||||||
|
bool isSendingMessage,
|
||||||
|
bool isLoadingMore,
|
||||||
|
bool hasMoreMessages,
|
||||||
|
String? errorMessage,
|
||||||
|
}) = ChatStateLoaded;
|
||||||
|
|
||||||
|
const factory ChatState.error(String message) = ChatStateError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loading state
|
||||||
|
class ChatStateLoading extends ChatState {
|
||||||
|
const ChatStateLoading() : super._();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loaded state with session and messages
|
||||||
|
class ChatStateLoaded extends ChatState {
|
||||||
|
const ChatStateLoaded({
|
||||||
|
required this.session,
|
||||||
|
required this.messages,
|
||||||
|
this.isSendingMessage = false,
|
||||||
|
this.isLoadingMore = false,
|
||||||
|
this.hasMoreMessages = true,
|
||||||
|
this.errorMessage,
|
||||||
|
}) : super._();
|
||||||
|
|
||||||
|
final ChatBasicSession session;
|
||||||
|
final List<ChatMessage> messages;
|
||||||
|
final bool isSendingMessage;
|
||||||
|
final bool isLoadingMore;
|
||||||
|
final bool hasMoreMessages;
|
||||||
|
final String? errorMessage;
|
||||||
|
|
||||||
|
ChatStateLoaded copyWith({
|
||||||
|
ChatBasicSession? session,
|
||||||
|
List<ChatMessage>? messages,
|
||||||
|
bool? isSendingMessage,
|
||||||
|
bool? isLoadingMore,
|
||||||
|
bool? hasMoreMessages,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return ChatStateLoaded(
|
||||||
|
session: session ?? this.session,
|
||||||
|
messages: messages ?? this.messages,
|
||||||
|
isSendingMessage: isSendingMessage ?? this.isSendingMessage,
|
||||||
|
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
|
||||||
|
hasMoreMessages: hasMoreMessages ?? this.hasMoreMessages,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error state
|
||||||
|
class ChatStateError extends ChatState {
|
||||||
|
const ChatStateError(this.message) : super._();
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State manager for chat functionality
|
||||||
|
class ChatStateManager extends StateManager<ChatState> {
|
||||||
|
ChatStateManager({
|
||||||
|
required ChatService chatService,
|
||||||
|
}) : _chatService = chatService,
|
||||||
|
super(const ChatStateLoading());
|
||||||
|
|
||||||
|
final ChatService _chatService;
|
||||||
|
ChatBasicSession? _currentSession;
|
||||||
|
final List<ChatMessage> _messages = [];
|
||||||
|
|
||||||
|
/// Initialize chat with session
|
||||||
|
Future<void> initializeChat(String sessionId) => handle((emit) async {
|
||||||
|
log('Initializing chat for session: $sessionId', name: 'ChatStateManager');
|
||||||
|
emit(const ChatStateLoading());
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load session info and messages
|
||||||
|
final session = await _chatService.getSession(sessionId);
|
||||||
|
final messages = await _chatService.getMessages(sessionId, limit: 50);
|
||||||
|
|
||||||
|
_currentSession = session;
|
||||||
|
_messages.clear();
|
||||||
|
// Convert ChatMessageResponse to ChatMessage - simplified for now
|
||||||
|
// In real implementation, this conversion would be more complex
|
||||||
|
|
||||||
|
emit(ChatStateLoaded(
|
||||||
|
session: session,
|
||||||
|
messages: List.unmodifiable(_messages),
|
||||||
|
hasMoreMessages: messages.length >= 50,
|
||||||
|
));
|
||||||
|
|
||||||
|
log('Chat initialized with ${messages.length} messages', name: 'ChatStateManager');
|
||||||
|
} catch (e, s) {
|
||||||
|
log(
|
||||||
|
'Error initializing chat',
|
||||||
|
error: e,
|
||||||
|
stackTrace: s,
|
||||||
|
name: 'ChatStateManager',
|
||||||
|
);
|
||||||
|
emit(ChatStateError('Failed to load chat: ${e.toString()}'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Send text message
|
||||||
|
Future<void> sendTextMessage(String content) => handle((emit) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is! ChatStateLoaded || _currentSession == null) {
|
||||||
|
log('Cannot send message: chat not initialized', name: 'ChatStateManager');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sending state
|
||||||
|
emit(currentState.copyWith(isSendingMessage: true, errorMessage: null));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send to server
|
||||||
|
final response = await _chatService.sendTextMessage(_currentSession!.id, content);
|
||||||
|
|
||||||
|
// Add assistant response
|
||||||
|
// Simplified - in real implementation would convert response to ChatMessage
|
||||||
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
isSendingMessage: false,
|
||||||
|
));
|
||||||
|
|
||||||
|
log('Message sent successfully', name: 'ChatStateManager');
|
||||||
|
} catch (e, s) {
|
||||||
|
log(
|
||||||
|
'Error sending message',
|
||||||
|
error: e,
|
||||||
|
stackTrace: s,
|
||||||
|
name: 'ChatStateManager',
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
isSendingMessage: false,
|
||||||
|
errorMessage: 'Failed to send message: ${e.toString()}',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Send audio message
|
||||||
|
Future<void> sendAudioMessage(
|
||||||
|
dynamic audioData,
|
||||||
|
Duration duration, {
|
||||||
|
String? transcription,
|
||||||
|
}) => handle((emit) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is! ChatStateLoaded || _currentSession == null) {
|
||||||
|
log('Cannot send audio message: chat not initialized', name: 'ChatStateManager');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sending state
|
||||||
|
emit(currentState.copyWith(isSendingMessage: true, errorMessage: null));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send to server
|
||||||
|
final response = await _chatService.sendAudioMessage(
|
||||||
|
_currentSession!.id,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
transcription: transcription,
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
isSendingMessage: false,
|
||||||
|
));
|
||||||
|
|
||||||
|
log('Audio message sent successfully', name: 'ChatStateManager');
|
||||||
|
} catch (e, s) {
|
||||||
|
log(
|
||||||
|
'Error sending audio message',
|
||||||
|
error: e,
|
||||||
|
stackTrace: s,
|
||||||
|
name: 'ChatStateManager',
|
||||||
|
);
|
||||||
|
|
||||||
|
emit(currentState.copyWith(
|
||||||
|
isSendingMessage: false,
|
||||||
|
errorMessage: 'Failed to send audio message: ${e.toString()}',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Clear error message
|
||||||
|
void clearError() => handle((emit) async {
|
||||||
|
final currentState = state;
|
||||||
|
if (currentState is ChatStateLoaded) {
|
||||||
|
emit(currentState.copyWith(errorMessage: null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Get current session
|
||||||
|
ChatBasicSession? get currentSession => _currentSession;
|
||||||
|
|
||||||
|
/// Get messages count
|
||||||
|
int get messagesCount => _messages.length;
|
||||||
|
}
|
||||||
629
chat/mnemo_cards_chat/pubspec.lock
Normal file
|
|
@ -0,0 +1,629 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "85.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.7.1"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
sha256: "409002f1adeea601018715d613115cfaf0e31f512cb80ae4534c79867ae2363d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.1.2"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.12.0"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.11.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
freezed:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: freezed
|
||||||
|
sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.8"
|
||||||
|
freezed_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.4"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
|
json_annotation:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.0"
|
||||||
|
json_serializable:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: json_serializable
|
||||||
|
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.9.5"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.17"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.1"
|
||||||
|
meta:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
mocktail:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: mocktail
|
||||||
|
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.2"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.7"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.6"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.2"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
yx_scope:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: yx_scope
|
||||||
|
sha256: "9ba98b442261596311363bf7361622e5ccc67189705b8d042ca23c9de366f8bf"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
yx_state:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: yx_state
|
||||||
|
sha256: "13ab50d3875686f65058cb9abdfbb1725e9d15ea99ade51b1f3a5f93727a8eaa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.9.2 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
43
chat/mnemo_cards_chat/pubspec.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
name: mnemo_cards_chat
|
||||||
|
description: "Chat module for mnemo_cards applications with LLM integration"
|
||||||
|
version: 1.0.0
|
||||||
|
publish_to: 'none'
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.9.2
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# YX Framework for DI and state management
|
||||||
|
yx_scope: ^1.1.2
|
||||||
|
yx_state: ^1.0.0
|
||||||
|
|
||||||
|
# HTTP client
|
||||||
|
dio: ^5.3.3
|
||||||
|
|
||||||
|
# Immutable data models
|
||||||
|
freezed_annotation: ^2.4.1
|
||||||
|
json_annotation: ^4.7.0
|
||||||
|
|
||||||
|
# Common utilities
|
||||||
|
meta: ^1.8.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
# Code generation
|
||||||
|
build_runner: ^2.4.13
|
||||||
|
freezed: ^2.4.5
|
||||||
|
json_serializable: ^6.8.0
|
||||||
|
|
||||||
|
# Linting
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
|
# Testing utilities
|
||||||
|
mocktail: ^1.0.3
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
359
chat/mnemo_cards_chat/test/domain/models/chat_message_test.dart
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/models/chat_message.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ChatMessage', () {
|
||||||
|
group('MessageStatus', () {
|
||||||
|
test('has correct display names', () {
|
||||||
|
expect(MessageStatus.sending.displayName, 'Отправляется...');
|
||||||
|
expect(MessageStatus.sent.displayName, 'Отправлено');
|
||||||
|
expect(MessageStatus.delivered.displayName, 'Доставлено');
|
||||||
|
expect(MessageStatus.error.displayName, 'Ошибка');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has correct status checks', () {
|
||||||
|
expect(MessageStatus.sending.isSending, isTrue);
|
||||||
|
expect(MessageStatus.sent.isSending, isFalse);
|
||||||
|
expect(MessageStatus.delivered.isSending, isFalse);
|
||||||
|
expect(MessageStatus.error.isSending, isFalse);
|
||||||
|
|
||||||
|
expect(MessageStatus.error.isError, isTrue);
|
||||||
|
expect(MessageStatus.sent.isError, isFalse);
|
||||||
|
|
||||||
|
expect(MessageStatus.sent.isDelivered, isTrue);
|
||||||
|
expect(MessageStatus.delivered.isDelivered, isTrue);
|
||||||
|
expect(MessageStatus.sending.isDelivered, isFalse);
|
||||||
|
expect(MessageStatus.error.isDelivered, isFalse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ChatParticipant', () {
|
||||||
|
test('creates user participant correctly', () {
|
||||||
|
final participant = ChatParticipant.user(
|
||||||
|
id: 'user_123',
|
||||||
|
name: 'John Doe',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(participant, isA<ChatParticipantUser>());
|
||||||
|
final user = participant as ChatParticipantUser;
|
||||||
|
expect(user.id, 'user_123');
|
||||||
|
expect(user.name, 'John Doe');
|
||||||
|
expect(user.avatarUrl, 'https://example.com/avatar.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates assistant participant correctly', () {
|
||||||
|
final participant = ChatParticipant.assistant(
|
||||||
|
id: 'assistant_123',
|
||||||
|
name: 'AI Assistant',
|
||||||
|
avatarUrl: 'https://example.com/bot.jpg',
|
||||||
|
model: 'gpt-4',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(participant, isA<ChatParticipantAssistant>());
|
||||||
|
final assistant = participant as ChatParticipantAssistant;
|
||||||
|
expect(assistant.id, 'assistant_123');
|
||||||
|
expect(assistant.name, 'AI Assistant');
|
||||||
|
expect(assistant.avatarUrl, 'https://example.com/bot.jpg');
|
||||||
|
expect(assistant.model, 'gpt-4');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('user participant supports optional avatar', () {
|
||||||
|
final participant = ChatParticipant.user(
|
||||||
|
id: 'user_123',
|
||||||
|
name: 'John Doe',
|
||||||
|
);
|
||||||
|
|
||||||
|
final user = participant as ChatParticipantUser;
|
||||||
|
expect(user.avatarUrl, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('assistant participant supports optional model', () {
|
||||||
|
final participant = ChatParticipant.assistant(
|
||||||
|
id: 'assistant_123',
|
||||||
|
name: 'AI Assistant',
|
||||||
|
);
|
||||||
|
|
||||||
|
final assistant = participant as ChatParticipantAssistant;
|
||||||
|
expect(assistant.model, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('TextMessage', () {
|
||||||
|
test('creates text message correctly', () {
|
||||||
|
final timestamp = DateTime.now();
|
||||||
|
final sender = ChatParticipant.user(id: 'user_123', name: 'User');
|
||||||
|
|
||||||
|
final message = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello, world!',
|
||||||
|
sender: sender,
|
||||||
|
timestamp: timestamp,
|
||||||
|
status: MessageStatus.sent,
|
||||||
|
metadata: {'source': 'test'},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message.id, 'msg_123');
|
||||||
|
expect(message.sessionId, 'session_123');
|
||||||
|
expect(message.content, 'Hello, world!');
|
||||||
|
expect(message.sender, sender);
|
||||||
|
expect(message.timestamp, timestamp);
|
||||||
|
expect(message.status, MessageStatus.sent);
|
||||||
|
expect(message.metadata, {'source': 'test'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports optional metadata', () {
|
||||||
|
final message = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message.metadata, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has default status', () {
|
||||||
|
final message = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message.status, MessageStatus.sent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('AudioMessage', () {
|
||||||
|
test('creates audio message correctly', () {
|
||||||
|
final timestamp = DateTime.now();
|
||||||
|
final sender = ChatParticipant.user(id: 'user_123', name: 'User');
|
||||||
|
const duration = Duration(seconds: 30);
|
||||||
|
|
||||||
|
final message = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.mp3',
|
||||||
|
sender: sender,
|
||||||
|
timestamp: timestamp,
|
||||||
|
duration: duration,
|
||||||
|
fileSize: 1024000,
|
||||||
|
status: MessageStatus.sent,
|
||||||
|
transcription: 'Hello from audio',
|
||||||
|
metadata: {'format': 'mp3'},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message.id, 'msg_123');
|
||||||
|
expect(message.sessionId, 'session_123');
|
||||||
|
expect(message.audioUrl, 'https://example.com/audio.mp3');
|
||||||
|
expect(message.sender, sender);
|
||||||
|
expect(message.timestamp, timestamp);
|
||||||
|
expect(message.duration, duration);
|
||||||
|
expect(message.fileSize, 1024000);
|
||||||
|
expect(message.status, MessageStatus.sent);
|
||||||
|
expect(message.transcription, 'Hello from audio');
|
||||||
|
expect(message.metadata, {'format': 'mp3'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports optional transcription and metadata', () {
|
||||||
|
const duration = Duration(seconds: 10);
|
||||||
|
final message = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.wav',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
duration: duration,
|
||||||
|
fileSize: 512000,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message.transcription, isNull);
|
||||||
|
expect(message.metadata, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has default status', () {
|
||||||
|
const duration = Duration(seconds: 5);
|
||||||
|
final message = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.webm',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
duration: duration,
|
||||||
|
fileSize: 256000,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message.status, MessageStatus.sent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ChatMessage union', () {
|
||||||
|
test('creates text message variant', () {
|
||||||
|
final textMessage = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final chatMessage = ChatMessage.text(textMessage);
|
||||||
|
expect(chatMessage, isA<ChatMessageText>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates audio message variant', () {
|
||||||
|
const duration = Duration(seconds: 10);
|
||||||
|
final audioMessage = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.mp3',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
duration: duration,
|
||||||
|
fileSize: 1024000,
|
||||||
|
);
|
||||||
|
|
||||||
|
final chatMessage = ChatMessage.audio(audioMessage);
|
||||||
|
expect(chatMessage, isA<ChatMessageAudio>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('JSON serialization', () {
|
||||||
|
test('ChatParticipant serializes and deserializes correctly', () {
|
||||||
|
final user = ChatParticipant.user(
|
||||||
|
id: 'user_123',
|
||||||
|
name: 'John Doe',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = user.toJson();
|
||||||
|
final deserialized = ChatParticipant.fromJson(json);
|
||||||
|
|
||||||
|
expect(deserialized, equals(user));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TextMessage serializes and deserializes correctly', () {
|
||||||
|
final message = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello, world!',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
status: MessageStatus.sent,
|
||||||
|
metadata: {'source': 'test'},
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = message.toJson();
|
||||||
|
final deserialized = TextMessage.fromJson(json);
|
||||||
|
|
||||||
|
expect(deserialized, equals(message));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AudioMessage serializes and deserializes correctly', () {
|
||||||
|
final message = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.mp3',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
duration: const Duration(seconds: 30),
|
||||||
|
fileSize: 1024000,
|
||||||
|
status: MessageStatus.sent,
|
||||||
|
transcription: 'Hello from audio',
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = message.toJson();
|
||||||
|
final deserialized = AudioMessage.fromJson(json);
|
||||||
|
|
||||||
|
expect(deserialized, equals(message));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Equality and hashCode', () {
|
||||||
|
test('TextMessage equality works correctly', () {
|
||||||
|
final message1 = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final message2 = TextMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final message3 = TextMessage(
|
||||||
|
id: 'msg_456',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
content: 'Hello',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message1, equals(message2));
|
||||||
|
expect(message1, isNot(equals(message3)));
|
||||||
|
expect(message1.hashCode, equals(message2.hashCode));
|
||||||
|
expect(message1.hashCode, isNot(equals(message3.hashCode)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AudioMessage equality works correctly', () {
|
||||||
|
const duration = Duration(seconds: 30);
|
||||||
|
final message1 = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.mp3',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
duration: duration,
|
||||||
|
fileSize: 1024000,
|
||||||
|
);
|
||||||
|
|
||||||
|
final message2 = AudioMessage(
|
||||||
|
id: 'msg_123',
|
||||||
|
sessionId: 'session_123',
|
||||||
|
audioUrl: 'https://example.com/audio.mp3',
|
||||||
|
sender: ChatParticipant.user(id: 'user_123', name: 'User'),
|
||||||
|
timestamp: DateTime.parse('2024-01-01T12:00:00Z'),
|
||||||
|
duration: duration,
|
||||||
|
fileSize: 1024000,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(message1, equals(message2));
|
||||||
|
expect(message1.hashCode, equals(message2.hashCode));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ChatParticipant equality works correctly', () {
|
||||||
|
final user1 = ChatParticipant.user(
|
||||||
|
id: 'user_123',
|
||||||
|
name: 'John',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
);
|
||||||
|
|
||||||
|
final user2 = ChatParticipant.user(
|
||||||
|
id: 'user_123',
|
||||||
|
name: 'John',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
);
|
||||||
|
|
||||||
|
final user3 = ChatParticipant.user(
|
||||||
|
id: 'user_456',
|
||||||
|
name: 'John',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(user1, equals(user2));
|
||||||
|
expect(user1, isNot(equals(user3)));
|
||||||
|
expect(user1.hashCode, equals(user2.hashCode));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
255
chat/mnemo_cards_chat/test/domain/models/chat_session_test.dart
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/models/chat_session.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ChatSession', () {
|
||||||
|
group('ChatSessionStatus', () {
|
||||||
|
test('has correct display names', () {
|
||||||
|
expect(ChatSessionStatus.active.displayName, 'Активный');
|
||||||
|
expect(ChatSessionStatus.archived.displayName, 'Архивирован');
|
||||||
|
expect(ChatSessionStatus.deleted.displayName, 'Удален');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has correct status checks', () {
|
||||||
|
expect(ChatSessionStatus.active.isActive, isTrue);
|
||||||
|
expect(ChatSessionStatus.archived.isActive, isFalse);
|
||||||
|
expect(ChatSessionStatus.deleted.isActive, isFalse);
|
||||||
|
|
||||||
|
expect(ChatSessionStatus.archived.isArchived, isTrue);
|
||||||
|
expect(ChatSessionStatus.active.isArchived, isFalse);
|
||||||
|
expect(ChatSessionStatus.deleted.isArchived, isFalse);
|
||||||
|
|
||||||
|
expect(ChatSessionStatus.deleted.isDeleted, isTrue);
|
||||||
|
expect(ChatSessionStatus.active.isDeleted, isFalse);
|
||||||
|
expect(ChatSessionStatus.archived.isDeleted, isFalse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ChatSession', () {
|
||||||
|
test('creates chat session correctly', () {
|
||||||
|
final createdAt = DateTime.parse('2024-01-01T10:00:00Z');
|
||||||
|
final updatedAt = DateTime.parse('2024-01-01T11:00:00Z');
|
||||||
|
|
||||||
|
final session = ChatSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Test Chat Session',
|
||||||
|
createdAt: createdAt,
|
||||||
|
updatedAt: updatedAt,
|
||||||
|
status: ChatSessionStatus.active,
|
||||||
|
description: 'A test chat session',
|
||||||
|
tags: ['test', 'ai'],
|
||||||
|
messageCount: 42,
|
||||||
|
lastMessagePreview: 'Hello, how are you?',
|
||||||
|
lastMessageAt: DateTime.parse('2024-01-01T11:30:00Z'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(session.id, 'session_123');
|
||||||
|
expect(session.userId, 'user_456');
|
||||||
|
expect(session.title, 'Test Chat Session');
|
||||||
|
expect(session.createdAt, createdAt);
|
||||||
|
expect(session.updatedAt, updatedAt);
|
||||||
|
expect(session.status, ChatSessionStatus.active);
|
||||||
|
expect(session.description, 'A test chat session');
|
||||||
|
expect(session.tags, ['test', 'ai']);
|
||||||
|
expect(session.messageCount, 42);
|
||||||
|
expect(session.lastMessagePreview, 'Hello, how are you?');
|
||||||
|
expect(session.lastMessageAt, DateTime.parse('2024-01-01T11:30:00Z'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports optional fields', () {
|
||||||
|
final session = ChatSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Minimal Session',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(session.description, isNull);
|
||||||
|
expect(session.tags, isEmpty);
|
||||||
|
expect(session.messageCount, 0);
|
||||||
|
expect(session.lastMessagePreview, isNull);
|
||||||
|
expect(session.lastMessageAt, isNull);
|
||||||
|
expect(session.status, ChatSessionStatus.active);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has default values', () {
|
||||||
|
final session = ChatSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Test',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(session.status, ChatSessionStatus.active);
|
||||||
|
expect(session.tags, isEmpty);
|
||||||
|
expect(session.messageCount, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('CreateChatSessionRequest', () {
|
||||||
|
test('creates request correctly', () {
|
||||||
|
final request = CreateChatSessionRequest(
|
||||||
|
title: 'New Chat Session',
|
||||||
|
description: 'A new chat',
|
||||||
|
tags: ['ai', 'assistant'],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(request.title, 'New Chat Session');
|
||||||
|
expect(request.description, 'A new chat');
|
||||||
|
expect(request.tags, ['ai', 'assistant']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports optional fields', () {
|
||||||
|
final request = CreateChatSessionRequest(title: 'Simple Chat');
|
||||||
|
|
||||||
|
expect(request.title, 'Simple Chat');
|
||||||
|
expect(request.description, isNull);
|
||||||
|
expect(request.tags, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('UpdateChatSessionRequest', () {
|
||||||
|
test('creates update request correctly', () {
|
||||||
|
final request = UpdateChatSessionRequest(
|
||||||
|
title: 'Updated Title',
|
||||||
|
description: 'Updated description',
|
||||||
|
tags: ['updated'],
|
||||||
|
status: ChatSessionStatus.archived,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(request.title, 'Updated Title');
|
||||||
|
expect(request.description, 'Updated description');
|
||||||
|
expect(request.tags, ['updated']);
|
||||||
|
expect(request.status, ChatSessionStatus.archived);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('supports partial updates', () {
|
||||||
|
final request = UpdateChatSessionRequest(
|
||||||
|
title: 'New Title',
|
||||||
|
// Other fields null for partial update
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(request.title, 'New Title');
|
||||||
|
expect(request.description, isNull);
|
||||||
|
expect(request.tags, isNull);
|
||||||
|
expect(request.status, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('JSON serialization', () {
|
||||||
|
test('ChatSession serializes and deserializes correctly', () {
|
||||||
|
final session = ChatSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Test Session',
|
||||||
|
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||||
|
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||||
|
status: ChatSessionStatus.active,
|
||||||
|
description: 'Test description',
|
||||||
|
tags: ['test'],
|
||||||
|
messageCount: 5,
|
||||||
|
lastMessagePreview: 'Hello',
|
||||||
|
lastMessageAt: DateTime.parse('2024-01-01T11:30:00Z'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = session.toJson();
|
||||||
|
final deserialized = ChatSession.fromJson(json);
|
||||||
|
|
||||||
|
expect(deserialized, equals(session));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CreateChatSessionRequest serializes and deserializes correctly', () {
|
||||||
|
final request = CreateChatSessionRequest(
|
||||||
|
title: 'New Session',
|
||||||
|
description: 'Description',
|
||||||
|
tags: ['tag1', 'tag2'],
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = request.toJson();
|
||||||
|
final deserialized = CreateChatSessionRequest.fromJson(json);
|
||||||
|
|
||||||
|
expect(deserialized, equals(request));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('UpdateChatSessionRequest serializes and deserializes correctly', () {
|
||||||
|
final request = UpdateChatSessionRequest(
|
||||||
|
title: 'Updated',
|
||||||
|
status: ChatSessionStatus.archived,
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = request.toJson();
|
||||||
|
final deserialized = UpdateChatSessionRequest.fromJson(json);
|
||||||
|
|
||||||
|
expect(deserialized, equals(request));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Equality and hashCode', () {
|
||||||
|
test('ChatSession equality works correctly', () {
|
||||||
|
final session1 = ChatSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Test',
|
||||||
|
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||||
|
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||||
|
status: ChatSessionStatus.active,
|
||||||
|
description: 'Desc',
|
||||||
|
tags: ['tag'],
|
||||||
|
messageCount: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
final session2 = ChatSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Test',
|
||||||
|
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||||
|
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||||
|
status: ChatSessionStatus.active,
|
||||||
|
description: 'Desc',
|
||||||
|
tags: ['tag'],
|
||||||
|
messageCount: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
final session3 = ChatSession(
|
||||||
|
id: 'session_456',
|
||||||
|
userId: 'user_456',
|
||||||
|
title: 'Test',
|
||||||
|
createdAt: DateTime.parse('2024-01-01T10:00:00Z'),
|
||||||
|
updatedAt: DateTime.parse('2024-01-01T11:00:00Z'),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(session1, equals(session2));
|
||||||
|
expect(session1, isNot(equals(session3)));
|
||||||
|
expect(session1.hashCode, equals(session2.hashCode));
|
||||||
|
expect(session1.hashCode, isNot(equals(session3.hashCode)));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CreateChatSessionRequest equality works correctly', () {
|
||||||
|
final request1 = CreateChatSessionRequest(
|
||||||
|
title: 'Test',
|
||||||
|
description: 'Desc',
|
||||||
|
tags: ['tag'],
|
||||||
|
);
|
||||||
|
|
||||||
|
final request2 = CreateChatSessionRequest(
|
||||||
|
title: 'Test',
|
||||||
|
description: 'Desc',
|
||||||
|
tags: ['tag'],
|
||||||
|
);
|
||||||
|
|
||||||
|
final request3 = CreateChatSessionRequest(
|
||||||
|
title: 'Different',
|
||||||
|
description: 'Desc',
|
||||||
|
tags: ['tag'],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(request1, equals(request2));
|
||||||
|
expect(request1, isNot(equals(request3)));
|
||||||
|
expect(request1.hashCode, equals(request2.hashCode));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,345 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:mnemo_cards_chat/mnemo_cards_chat.dart';
|
||||||
|
|
||||||
|
// Mock classes
|
||||||
|
class MockChatRepository extends Mock implements ChatRepository {}
|
||||||
|
|
||||||
|
// Fallback values for mocktail
|
||||||
|
class CreateChatSessionRequestFake extends Fake implements CreateChatSessionRequest {}
|
||||||
|
class SendTextMessageRequestFake extends Fake implements SendTextMessageRequest {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(CreateChatSessionRequestFake());
|
||||||
|
registerFallbackValue(SendTextMessageRequestFake());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ChatService', () {
|
||||||
|
late ChatService chatService;
|
||||||
|
late MockChatRepository mockChatRepository;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockChatRepository = MockChatRepository();
|
||||||
|
chatService = ChatService(chatRepository: mockChatRepository);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(chatService, isNotNull);
|
||||||
|
expect(chatService, isA<ChatService>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has proper constructor signature', () {
|
||||||
|
expect(
|
||||||
|
() => ChatService(httpRepository: mockChatRepository),
|
||||||
|
returnsNormally,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('createSession', () {
|
||||||
|
test('creates session successfully', () async {
|
||||||
|
const title = 'Test Chat';
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final expectedSession = ChatBasicSession(
|
||||||
|
id: sessionId,
|
||||||
|
userId: 'user_123',
|
||||||
|
title: title,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatRepository.createChatSession(any()))
|
||||||
|
.thenAnswer((_) async => expectedSession);
|
||||||
|
|
||||||
|
final result = await chatService.createSession(title: title);
|
||||||
|
|
||||||
|
expect(result, equals(expectedSession));
|
||||||
|
verify(() => mockChatRepository.createChatSession(
|
||||||
|
any(that: isA<CreateChatSessionRequest>()
|
||||||
|
.having((r) => r.title, 'title', title))
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('creates session with description', () async {
|
||||||
|
const title = 'Test Chat';
|
||||||
|
const description = 'Test Description';
|
||||||
|
|
||||||
|
when(() => mockChatRepository.createChatSession(any()))
|
||||||
|
.thenAnswer((_) async => ChatBasicSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
title: title,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
));
|
||||||
|
|
||||||
|
await chatService.createSession(title: title, description: description);
|
||||||
|
|
||||||
|
verify(() => mockChatRepository.createChatSession(
|
||||||
|
any(that: isA<CreateChatSessionRequest>()
|
||||||
|
.having((r) => r.title, 'title', title))
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('getSession', () {
|
||||||
|
test('gets session successfully', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final expectedSession = ChatBasicSession(
|
||||||
|
id: sessionId,
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Test Chat',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatRepository.getChatSession(sessionId))
|
||||||
|
.thenAnswer((_) async => expectedSession);
|
||||||
|
|
||||||
|
final result = await chatService.getSession(sessionId);
|
||||||
|
|
||||||
|
expect(result, equals(expectedSession));
|
||||||
|
verify(() => mockChatRepository.getChatSession(sessionId)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('getSessions', () {
|
||||||
|
test('gets sessions with default parameters', () async {
|
||||||
|
final sessions = [
|
||||||
|
ChatBasicSession(
|
||||||
|
id: 'session_1',
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Chat 1',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
when(() => mockChatRepository.getChatSessions(limit: 20, afterSessionId: null))
|
||||||
|
.thenAnswer((_) async => sessions);
|
||||||
|
|
||||||
|
final result = await chatService.getSessions();
|
||||||
|
|
||||||
|
expect(result, equals(sessions));
|
||||||
|
verify(() => mockChatRepository.getChatSessions(limit: 20, afterSessionId: null)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gets sessions with custom parameters', () async {
|
||||||
|
const limit = 10;
|
||||||
|
const afterSessionId = 'session_123';
|
||||||
|
final sessions = <ChatBasicSession>[];
|
||||||
|
|
||||||
|
when(() => mockChatRepository.getChatSessions(limit: limit, afterSessionId: afterSessionId))
|
||||||
|
.thenAnswer((_) async => sessions);
|
||||||
|
|
||||||
|
final result = await chatService.getSessions(
|
||||||
|
limit: limit,
|
||||||
|
afterSessionId: afterSessionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, equals(sessions));
|
||||||
|
verify(() => mockChatRepository.getChatSessions(
|
||||||
|
limit: limit,
|
||||||
|
afterSessionId: afterSessionId,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sendTextMessage', () {
|
||||||
|
test('sends text message successfully', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
const content = 'Hello, world!';
|
||||||
|
final expectedResponse = ChatMessageResponse(
|
||||||
|
messageId: 'msg_123',
|
||||||
|
sessionId: sessionId,
|
||||||
|
content: 'Assistant response',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatRepository.sendTextMessage(any()))
|
||||||
|
.thenAnswer((_) async => expectedResponse);
|
||||||
|
|
||||||
|
final result = await chatService.sendTextMessage(sessionId, content);
|
||||||
|
|
||||||
|
expect(result, equals(expectedResponse));
|
||||||
|
verify(() => mockChatRepository.sendTextMessage(
|
||||||
|
any(that: isA<SendTextMessageRequest>()
|
||||||
|
.having((r) => r.sessionId, 'sessionId', sessionId)
|
||||||
|
.having((r) => r.content, 'content', content))
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sendAudioMessage', () {
|
||||||
|
test('sends audio message successfully', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final audioData = Uint8List(1024);
|
||||||
|
const duration = Duration(seconds: 5);
|
||||||
|
final expectedResponse = ChatMessageResponse(
|
||||||
|
messageId: 'msg_123',
|
||||||
|
sessionId: sessionId,
|
||||||
|
content: 'Audio processed',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatRepository.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
fileName: null,
|
||||||
|
mimeType: null,
|
||||||
|
)).thenAnswer((_) async => expectedResponse);
|
||||||
|
|
||||||
|
final result = await chatService.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, equals(expectedResponse));
|
||||||
|
verify(() => mockChatRepository.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
fileName: null,
|
||||||
|
mimeType: null,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sends audio message with metadata', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final audioData = Uint8List(2048);
|
||||||
|
const duration = Duration(seconds: 10);
|
||||||
|
const transcription = 'Hello world';
|
||||||
|
const fileName = 'recording.webm';
|
||||||
|
const mimeType = 'audio/webm';
|
||||||
|
|
||||||
|
when(() => mockChatRepository.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
fileName: fileName,
|
||||||
|
mimeType: mimeType,
|
||||||
|
)).thenAnswer((_) async => ChatMessageResponse(
|
||||||
|
messageId: 'msg_123',
|
||||||
|
sessionId: sessionId,
|
||||||
|
content: 'Response',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
));
|
||||||
|
|
||||||
|
await chatService.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
transcription: transcription,
|
||||||
|
fileName: fileName,
|
||||||
|
mimeType: mimeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(() => mockChatRepository.sendAudioMessage(
|
||||||
|
sessionId,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
fileName: fileName,
|
||||||
|
mimeType: mimeType,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('getMessages', () {
|
||||||
|
test('gets messages with default parameters', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final messages = [
|
||||||
|
ChatMessageResponse(
|
||||||
|
messageId: 'msg_1',
|
||||||
|
sessionId: sessionId,
|
||||||
|
content: 'Hello',
|
||||||
|
senderId: 'user',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
when(() => mockChatRepository.getChatMessages(
|
||||||
|
sessionId,
|
||||||
|
limit: 50,
|
||||||
|
beforeMessageId: null,
|
||||||
|
)).thenAnswer((_) async => messages);
|
||||||
|
|
||||||
|
final result = await chatService.getMessages(sessionId);
|
||||||
|
|
||||||
|
expect(result, equals(messages));
|
||||||
|
verify(() => mockChatRepository.getChatMessages(
|
||||||
|
sessionId,
|
||||||
|
limit: 50,
|
||||||
|
beforeMessageId: null,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gets messages with custom parameters', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
const limit = 20;
|
||||||
|
const beforeMessageId = 'msg_123';
|
||||||
|
final messages = <ChatMessageResponse>[];
|
||||||
|
|
||||||
|
when(() => mockChatRepository.getChatMessages(
|
||||||
|
sessionId,
|
||||||
|
limit: limit,
|
||||||
|
beforeMessageId: beforeMessageId,
|
||||||
|
)).thenAnswer((_) async => messages);
|
||||||
|
|
||||||
|
final result = await chatService.getMessages(
|
||||||
|
sessionId,
|
||||||
|
limit: limit,
|
||||||
|
beforeMessageId: beforeMessageId,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, equals(messages));
|
||||||
|
verify(() => mockChatRepository.getChatMessages(
|
||||||
|
sessionId,
|
||||||
|
limit: limit,
|
||||||
|
beforeMessageId: beforeMessageId,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('updateSession', () {
|
||||||
|
test('updates session successfully', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final updates = {'title': 'New Title'};
|
||||||
|
final expectedSession = ChatBasicSession(
|
||||||
|
id: sessionId,
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'New Title',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatRepository.updateChatSession(sessionId, updates))
|
||||||
|
.thenAnswer((_) async => expectedSession);
|
||||||
|
|
||||||
|
final result = await chatService.updateSession(sessionId, updates);
|
||||||
|
|
||||||
|
expect(result, equals(expectedSession));
|
||||||
|
verify(() => mockChatRepository.updateChatSession(sessionId, updates)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('deleteSession', () {
|
||||||
|
test('deletes session successfully', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
|
||||||
|
when(() => mockChatRepository.deleteChatSession(sessionId))
|
||||||
|
.thenAnswer((_) async => {});
|
||||||
|
|
||||||
|
await chatService.deleteSession(sessionId);
|
||||||
|
|
||||||
|
verify(() => mockChatRepository.deleteChatSession(sessionId)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,449 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/models/chat_api_simple.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/models/chat_basic.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/models/chat_message.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/services/chat_service.dart';
|
||||||
|
import 'package:mnemo_cards_chat/domain/state/chat_state_manager.dart';
|
||||||
|
|
||||||
|
// Mock classes
|
||||||
|
class MockChatService extends Mock implements ChatService {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ChatStateManager', () {
|
||||||
|
late ChatStateManager stateManager;
|
||||||
|
late MockChatService mockChatService;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockChatService = MockChatService();
|
||||||
|
stateManager = ChatStateManager(chatService: mockChatService);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should initialize with loading state', () {
|
||||||
|
expect(stateManager.state, equals(const ChatState.loading()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can be instantiated with proper dependencies', () {
|
||||||
|
expect(stateManager, isNotNull);
|
||||||
|
expect(stateManager, isA<ChatStateManager>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has proper constructor signature', () {
|
||||||
|
expect(
|
||||||
|
() => ChatStateManager(chatService: mockChatService),
|
||||||
|
returnsNormally,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('state is accessible', () {
|
||||||
|
expect(stateManager.state, isA<ChatState>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has initializeChat method', () {
|
||||||
|
expect(stateManager.initializeChat, isA<Function>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has sendTextMessage method', () {
|
||||||
|
expect(stateManager.sendTextMessage, isA<Function>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has sendAudioMessage method', () {
|
||||||
|
expect(stateManager.sendAudioMessage, isA<Function>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has loadMoreMessages method', () {
|
||||||
|
expect(stateManager.loadMoreMessages, isA<Function>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has clearError method', () {
|
||||||
|
expect(stateManager.clearError, isA<Function>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has currentSession getter', () {
|
||||||
|
expect(stateManager.currentSession, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has messagesCount getter', () {
|
||||||
|
expect(stateManager.messagesCount, equals(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
group('initializeChat', () {
|
||||||
|
test('initializes chat successfully', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final session = ChatBasicSession(
|
||||||
|
id: sessionId,
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Test Chat',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
final messages = [
|
||||||
|
ChatMessageResponse(
|
||||||
|
messageId: 'msg_1',
|
||||||
|
sessionId: sessionId,
|
||||||
|
content: 'Hello',
|
||||||
|
senderId: 'user',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
when(() => mockChatService.getSession(sessionId))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(sessionId, limit: 50))
|
||||||
|
.thenAnswer((_) async => messages);
|
||||||
|
|
||||||
|
await stateManager.initializeChat(sessionId);
|
||||||
|
|
||||||
|
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||||
|
final loadedState = stateManager.state as ChatStateLoaded;
|
||||||
|
expect(loadedState.session, equals(session));
|
||||||
|
expect(loadedState.messages.length, equals(1));
|
||||||
|
expect(loadedState.isSendingMessage, isFalse);
|
||||||
|
expect(loadedState.hasMoreMessages, isFalse);
|
||||||
|
expect(stateManager.currentSession, equals(session));
|
||||||
|
expect(stateManager.messagesCount, equals(1));
|
||||||
|
|
||||||
|
verify(() => mockChatService.getSession(sessionId)).called(1);
|
||||||
|
verify(() => mockChatService.getMessages(sessionId, limit: 50)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles initialization error', () async {
|
||||||
|
const sessionId = 'session_123';
|
||||||
|
final error = Exception('Network error');
|
||||||
|
|
||||||
|
when(() => mockChatService.getSession(sessionId))
|
||||||
|
.thenThrow(error);
|
||||||
|
|
||||||
|
await stateManager.initializeChat(sessionId);
|
||||||
|
|
||||||
|
expect(stateManager.state, isA<ChatStateError>());
|
||||||
|
final errorState = stateManager.state as ChatStateError;
|
||||||
|
expect(errorState.message, contains('Failed to load chat'));
|
||||||
|
expect(stateManager.currentSession, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sendTextMessage', () {
|
||||||
|
late ChatBasicSession session;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
session = ChatBasicSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Test Chat',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sends text message successfully when chat is initialized', () async {
|
||||||
|
// First initialize chat
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||||
|
.thenAnswer((_) async => []);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Now send message
|
||||||
|
const content = 'Hello, world!';
|
||||||
|
final response = ChatMessageResponse(
|
||||||
|
messageId: 'msg_123',
|
||||||
|
sessionId: session.id,
|
||||||
|
content: 'Assistant response',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatService.sendTextMessage(session.id, content))
|
||||||
|
.thenAnswer((_) async => response);
|
||||||
|
|
||||||
|
await stateManager.sendTextMessage(content);
|
||||||
|
|
||||||
|
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||||
|
final loadedState = stateManager.state as ChatStateLoaded;
|
||||||
|
expect(loadedState.messages.length, equals(2)); // User + Assistant messages
|
||||||
|
expect(loadedState.isSendingMessage, isFalse);
|
||||||
|
|
||||||
|
// Check user message
|
||||||
|
final userMessage = loadedState.messages[0] as TextMessage;
|
||||||
|
expect(userMessage.sender.id, equals('current_user'));
|
||||||
|
expect(userMessage.content, equals(content));
|
||||||
|
expect(userMessage.status, equals(MessageStatus.sent));
|
||||||
|
|
||||||
|
// Check assistant message
|
||||||
|
final assistantMessage = loadedState.messages[1] as TextMessage;
|
||||||
|
expect(assistantMessage.sender.id, equals('assistant'));
|
||||||
|
expect(assistantMessage.content, equals('Assistant response'));
|
||||||
|
|
||||||
|
verify(() => mockChatService.sendTextMessage(session.id, content)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles send text message error', () async {
|
||||||
|
// Initialize chat first
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||||
|
.thenAnswer((_) async => []);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Send message with error
|
||||||
|
const content = 'Hello';
|
||||||
|
final error = Exception('Network error');
|
||||||
|
|
||||||
|
when(() => mockChatService.sendTextMessage(session.id, content))
|
||||||
|
.thenThrow(error);
|
||||||
|
|
||||||
|
await stateManager.sendTextMessage(content);
|
||||||
|
|
||||||
|
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||||
|
final loadedState = stateManager.state as ChatStateLoaded;
|
||||||
|
expect(loadedState.isSendingMessage, isFalse);
|
||||||
|
expect(loadedState.errorMessage, contains('Failed to send message'));
|
||||||
|
expect(loadedState.messages.length, equals(1)); // Only user message
|
||||||
|
|
||||||
|
// Check that user message has error status
|
||||||
|
final userMessage = loadedState.messages[0] as AudioMessage;
|
||||||
|
expect(userMessage.status, equals(MessageStatus.error));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does nothing if chat not initialized', () async {
|
||||||
|
await stateManager.sendTextMessage('Hello');
|
||||||
|
|
||||||
|
verifyNever(() => mockChatService.sendTextMessage(any(), any()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sendAudioMessage', () {
|
||||||
|
late ChatBasicSession session;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
session = ChatBasicSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Test Chat',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sends audio message successfully', () async {
|
||||||
|
// Initialize chat
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||||
|
.thenAnswer((_) async => []);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Send audio message
|
||||||
|
final audioData = Uint8List(1024);
|
||||||
|
const duration = Duration(seconds: 5);
|
||||||
|
final response = ChatMessageResponse(
|
||||||
|
messageId: 'msg_123',
|
||||||
|
sessionId: session.id,
|
||||||
|
content: 'Audio processed',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
audioUrl: 'https://example.com/audio.mp3',
|
||||||
|
durationMs: 5000,
|
||||||
|
fileSize: 1024,
|
||||||
|
);
|
||||||
|
|
||||||
|
when(() => mockChatService.sendAudioMessage(
|
||||||
|
session.id,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
transcription: null,
|
||||||
|
)).thenAnswer((_) async => response);
|
||||||
|
|
||||||
|
await stateManager.sendAudioMessage(audioData, duration);
|
||||||
|
|
||||||
|
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||||
|
final loadedState = stateManager.state as ChatStateLoaded;
|
||||||
|
expect(loadedState.messages.length, equals(2)); // User + Assistant messages
|
||||||
|
|
||||||
|
// Check user audio message
|
||||||
|
final userMessage = loadedState.messages[0] as AudioMessage;
|
||||||
|
expect(userMessage.sender.id, equals('current_user'));
|
||||||
|
expect(userMessage.duration, equals(duration));
|
||||||
|
expect(userMessage.status, equals(MessageStatus.sent));
|
||||||
|
|
||||||
|
// Check assistant message
|
||||||
|
final assistantMessage = loadedState.messages[1] as TextMessage;
|
||||||
|
expect(assistantMessage.sender.id, equals('assistant'));
|
||||||
|
expect(assistantMessage.content, equals('Audio processed'));
|
||||||
|
|
||||||
|
verify(() => mockChatService.sendAudioMessage(
|
||||||
|
session.id,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
transcription: null,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sends audio message with transcription', () async {
|
||||||
|
// Initialize chat
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||||
|
.thenAnswer((_) async => []);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Send audio with transcription
|
||||||
|
final audioData = Uint8List(2048);
|
||||||
|
const duration = Duration(seconds: 3);
|
||||||
|
const transcription = 'Hello from audio';
|
||||||
|
|
||||||
|
when(() => mockChatService.sendAudioMessage(
|
||||||
|
session.id,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
transcription: transcription,
|
||||||
|
)).thenAnswer((_) async => ChatMessageResponse(
|
||||||
|
messageId: 'msg_123',
|
||||||
|
sessionId: session.id,
|
||||||
|
content: 'Response',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
));
|
||||||
|
|
||||||
|
await stateManager.sendAudioMessage(audioData, duration, transcription: transcription);
|
||||||
|
|
||||||
|
verify(() => mockChatService.sendAudioMessage(
|
||||||
|
session.id,
|
||||||
|
audioData,
|
||||||
|
duration,
|
||||||
|
transcription: transcription,
|
||||||
|
)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does nothing if chat not initialized', () async {
|
||||||
|
final audioData = Uint8List(1024);
|
||||||
|
await stateManager.sendAudioMessage(audioData, const Duration(seconds: 1));
|
||||||
|
|
||||||
|
verifyNever(() => mockChatService.sendAudioMessage(any(), any(), any()));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('loadMoreMessages', () {
|
||||||
|
late ChatBasicSession session;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
session = ChatBasicSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Test Chat',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loads more messages successfully', () async {
|
||||||
|
// Initialize with one message
|
||||||
|
final initialMessages = [
|
||||||
|
ChatMessageResponse(
|
||||||
|
messageId: 'msg_1',
|
||||||
|
sessionId: session.id,
|
||||||
|
content: 'Initial message',
|
||||||
|
senderId: 'user',
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: 50))
|
||||||
|
.thenAnswer((_) async => initialMessages);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Load more messages
|
||||||
|
final moreMessages = [
|
||||||
|
ChatMessageResponse(
|
||||||
|
messageId: 'msg_0',
|
||||||
|
sessionId: session.id,
|
||||||
|
content: 'Older message',
|
||||||
|
senderId: 'assistant',
|
||||||
|
timestamp: DateTime.now().subtract(const Duration(hours: 1)),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
when(() => mockChatService.getMessages(
|
||||||
|
session.id,
|
||||||
|
limit: 50,
|
||||||
|
beforeMessageId: 'msg_1',
|
||||||
|
)).thenAnswer((_) async => moreMessages);
|
||||||
|
|
||||||
|
await stateManager.loadMoreMessages();
|
||||||
|
|
||||||
|
expect(stateManager.state, isA<ChatStateLoaded>());
|
||||||
|
final loadedState = stateManager.state as ChatStateLoaded;
|
||||||
|
expect(loadedState.messages.length, equals(2));
|
||||||
|
expect(loadedState.isLoadingMore, isFalse);
|
||||||
|
expect(loadedState.hasMoreMessages, isFalse); // Only 1 more message loaded
|
||||||
|
|
||||||
|
// Messages should be in chronological order (oldest first)
|
||||||
|
expect(loadedState.messages[0].maybeMap(
|
||||||
|
text: (msg) => msg.content,
|
||||||
|
audio: (msg) => msg.transcription,
|
||||||
|
), equals('Older message'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does nothing if already loading more', () async {
|
||||||
|
// Initialize chat
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||||
|
.thenAnswer((_) async => []);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Start loading more
|
||||||
|
stateManager.state = (stateManager.state as ChatStateLoaded).copyWith(isLoadingMore: true);
|
||||||
|
|
||||||
|
await stateManager.loadMoreMessages();
|
||||||
|
|
||||||
|
verifyNever(() => mockChatService.getMessages(any(), limit: any(named: 'limit'), beforeMessageId: any(named: 'beforeMessageId')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does nothing if no more messages', () async {
|
||||||
|
// Initialize chat
|
||||||
|
when(() => mockChatService.getSession(any()))
|
||||||
|
.thenAnswer((_) async => session);
|
||||||
|
when(() => mockChatService.getMessages(any(), limit: any(named: 'limit')))
|
||||||
|
.thenAnswer((_) async => []);
|
||||||
|
await stateManager.initializeChat(session.id);
|
||||||
|
|
||||||
|
// Set hasMoreMessages to false
|
||||||
|
stateManager.state = (stateManager.state as ChatStateLoaded).copyWith(hasMoreMessages: false);
|
||||||
|
|
||||||
|
await stateManager.loadMoreMessages();
|
||||||
|
|
||||||
|
verifyNever(() => mockChatService.getMessages(any(), limit: any(named: 'limit'), beforeMessageId: any(named: 'beforeMessageId')));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('clearError', () {
|
||||||
|
test('clears error message from loaded state', () {
|
||||||
|
stateManager.state = ChatState.loaded(
|
||||||
|
session: ChatBasicSession(
|
||||||
|
id: 'session_123',
|
||||||
|
userId: 'user_123',
|
||||||
|
title: 'Test',
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
),
|
||||||
|
messages: [],
|
||||||
|
errorMessage: 'Some error',
|
||||||
|
);
|
||||||
|
|
||||||
|
stateManager.clearError();
|
||||||
|
|
||||||
|
final loadedState = stateManager.state as ChatStateLoaded;
|
||||||
|
expect(loadedState.errorMessage, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does nothing for non-loaded states', () {
|
||||||
|
stateManager.clearError();
|
||||||
|
expect(stateManager.state, isA<ChatState>()); // Should not crash
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
43
funny_letters/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
30
funny_letters/.metadata
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "b25305a8832cfc6ba632a7f87ad455e319dccce8"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
|
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
|
- platform: web
|
||||||
|
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
|
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
25
funny_letters/.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "funny_letters",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "funny_letters (profile mode)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "funny_letters (release mode)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
funny_letters/Funny_letters_compat.zip
Normal file
16
funny_letters/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# funny_letters
|
||||||
|
|
||||||
|
Funny letters
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
This project is a starting point for a Flutter application.
|
||||||
|
|
||||||
|
A few resources to get you started if this is your first Flutter project:
|
||||||
|
|
||||||
|
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||||
|
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||||
|
|
||||||
|
For help getting started with Flutter development, view the
|
||||||
|
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||||
|
samples, guidance on mobile development, and a full API reference.
|
||||||
28
funny_letters/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
13
funny_letters/android/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
67
funny_letters/android/app/build.gradle
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "com.example.funny_letters"
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.funny_letters"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {}
|
||||||
7
funny_letters/android/app/src/debug/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
33
funny_letters/android/app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="funny_letters"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.example.funny_letters
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
18
funny_letters/android/app/src/main/res/values/styles.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
30
funny_letters/android/build.gradle
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.7.10'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
3
funny_letters/android/gradle.properties
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.gradle.jvmargs=-Xmx4G
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
5
funny_letters/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||||
29
funny_letters/android/settings.gradle
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}
|
||||||
|
settings.ext.flutterSdkPath = flutterSdkPath()
|
||||||
|
|
||||||
|
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
|
id "com.android.application" version "7.3.0" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
||||||
BIN
funny_letters/assets/effect.mp3
Normal file
BIN
funny_letters/assets/go.mp3
Normal file
BIN
funny_letters/assets/images/Match3Clover.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
funny_letters/assets/images/Match3Heart.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
funny_letters/assets/images/Match3Moon.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
funny_letters/assets/images/Match3Star.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
funny_letters/assets/images/Match3Water.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
funny_letters/assets/images/button_back.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
funny_letters/assets/images/button_ok.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
funny_letters/assets/images/button_play.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
funny_letters/assets/images/button_rule.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
funny_letters/assets/images/button_settings.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
funny_letters/assets/images/game_over.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
funny_letters/assets/images/health_back.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
funny_letters/assets/images/health_front.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
funny_letters/assets/images/heart.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
BIN
funny_letters/assets/images/icons/back.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
funny_letters/assets/images/icons/play.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
funny_letters/assets/images/icons/restart.png
Normal file
|
After Width: | Height: | Size: 264 B |
BIN
funny_letters/assets/images/letters.png
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
funny_letters/assets/images/letters/icon_a.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
funny_letters/assets/images/letters/icon_b.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
funny_letters/assets/images/letters/icon_c.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
funny_letters/assets/images/letters/icon_d.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
funny_letters/assets/images/letters/icon_e.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
funny_letters/assets/images/letters/icon_f.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
funny_letters/assets/images/letters/icon_g.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
funny_letters/assets/images/letters/icon_h.png
Normal file
|
After Width: | Height: | Size: 9 KiB |
BIN
funny_letters/assets/images/letters/icon_i.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
funny_letters/assets/images/letters/icon_j.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
funny_letters/assets/images/letters/icon_k.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
funny_letters/assets/images/letters/icon_l.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
funny_letters/assets/images/letters/icon_m.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
funny_letters/assets/images/letters/icon_n.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
funny_letters/assets/images/letters/icon_o.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
funny_letters/assets/images/letters/icon_p.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
funny_letters/assets/images/letters/icon_q.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
funny_letters/assets/images/letters/icon_r.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
funny_letters/assets/images/letters/icon_s.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
funny_letters/assets/images/letters/icon_t.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
funny_letters/assets/images/letters/icon_u.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |