feature/websocket
#18
Merged
amirreza.chegini
merged 2 commits from feature/websocket into develop 5 days ago
69 changed files with 1198 additions and 489 deletions
-
16android/app/build.gradle.kts
-
18lib/core/constants/my_api.dart
-
5lib/core/params/battle_league_params.dart
-
14lib/core/params/bl_params.dart
-
22lib/core/routers/my_routes.dart
-
176lib/core/services/web_socket_service.dart
-
71lib/features/battle_league/data/datasource/battle_league_datasource.dart
-
9lib/features/battle_league/data/model/answer_model.dart
-
17lib/features/battle_league/data/model/clock_model.dart
-
23lib/features/battle_league/data/model/match_making_model.dart
-
13lib/features/battle_league/data/model/player_token_model.dart
-
18lib/features/battle_league/data/model/question_model.dart
-
2lib/features/battle_league/data/model/topics_model.dart
-
94lib/features/battle_league/data/repository_impl/battle_league_repository_impl.dart
-
11lib/features/battle_league/domain/entity/answer_entity.dart
-
12lib/features/battle_league/domain/entity/clock_entity.dart
-
29lib/features/battle_league/domain/entity/match_making_entity.dart
-
12lib/features/battle_league/domain/entity/player_token_entity.dart
-
14lib/features/battle_league/domain/entity/question_entity.dart
-
0lib/features/battle_league/domain/entity/topics_entity.dart
-
22lib/features/battle_league/domain/repository/battle_league_repository.dart
-
17lib/features/battle_league/domain/usecases/get_clock_usecase.dart
-
18lib/features/battle_league/domain/usecases/get_player_token_usecase.dart
-
18lib/features/battle_league/domain/usecases/get_topics_usecase.dart
-
17lib/features/battle_league/domain/usecases/match_making_usecase.dart
-
28lib/features/battle_league/first_part/data/datasource/battle_league_datasource.dart
-
14lib/features/battle_league/first_part/data/model/battle_league_model.dart
-
33lib/features/battle_league/first_part/data/repository_impl/battle_league_repository_impl.dart
-
15lib/features/battle_league/first_part/domain/entity/battle_league_entity.dart
-
10lib/features/battle_league/first_part/domain/repository/battle_league_repository.dart
-
18lib/features/battle_league/first_part/domain/usecases/get_topics_usecase.dart
-
70lib/features/battle_league/first_part/presentation/controller/battle_league_controller.dart
-
9lib/features/battle_league/presentation/binding/battle_league_binding.dart
-
280lib/features/battle_league/presentation/controller/battle_league_controller.dart
-
45lib/features/battle_league/presentation/ui/first_part/battle_league_finding_page.dart
-
6lib/features/battle_league/presentation/ui/first_part/battle_league_founded_page.dart
-
10lib/features/battle_league/presentation/ui/first_part/battle_league_page.dart
-
19lib/features/battle_league/presentation/ui/first_part/battle_league_topic_page.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/button/battle_golden_button.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/button/battle_grey_button.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/button/battle_purple_button.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/founded_page/founded_avatar.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/battle_league_tab_bar.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/filter_ranking_button.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/my_ranking_widget.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/ranking_region.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/ranking_scrollbar.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/ranking_time.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/main_page/ranking_widget.dart
-
10lib/features/battle_league/presentation/ui/first_part/widgets/main_page/regional_ranking.dart
-
10lib/features/battle_league/presentation/ui/first_part/widgets/main_page/time_ranking.dart
-
0lib/features/battle_league/presentation/ui/first_part/widgets/rank_title.dart
-
18lib/features/battle_league/presentation/ui/first_part/widgets/topic_page/topic_widget.dart
-
59lib/features/battle_league/presentation/ui/second_part/battle_league_result_page.dart
-
89lib/features/battle_league/presentation/ui/second_part/bl_question_page.dart
-
6lib/features/battle_league/presentation/ui/second_part/widgets/battle_league_question_avatar.dart
-
26lib/features/battle_league/presentation/ui/second_part/widgets/question_board.dart
-
0lib/features/battle_league/presentation/ui/second_part/widgets/question_timer_ready_widget.dart
-
28lib/features/battle_league/question_part/bl_question/data/datasource/bl_question_datasource.dart
-
13lib/features/battle_league/question_part/bl_question/data/model/bl_question_model.dart
-
29lib/features/battle_league/question_part/bl_question/data/repository_impl/bl_question_repository_impl.dart
-
14lib/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart
-
8lib/features/battle_league/question_part/bl_question/domain/repository/bl_question_repository.dart
-
19lib/features/battle_league/question_part/bl_question/domain/usecases/get_bl_question_usecase.dart
-
20lib/features/battle_league/question_part/bl_question/presentation/binding/bl_question_binding.dart
-
54lib/features/battle_league/question_part/bl_question/presentation/controller/bl_question_controller.dart
-
34lib/features/battle_league/question_part/bl_question/presentation/ui/bl_question_page.dart
-
23lib/init_bindings.dart
-
2pubspec.yaml
@ -1,5 +0,0 @@ |
|||||
class BattleLeagueParams { |
|
||||
int? id; |
|
||||
|
|
||||
BattleLeagueParams({this.id}); |
|
||||
} |
|
||||
@ -0,0 +1,14 @@ |
|||||
|
class BlParams { |
||||
|
List<int>? chooseTopics; |
||||
|
|
||||
|
BlParams({this.chooseTopics}); |
||||
|
|
||||
|
Map<String, dynamic> get toCockJson => { |
||||
|
'client_time': DateTime.now().millisecondsSinceEpoch, |
||||
|
}; |
||||
|
|
||||
|
Map<String, dynamic> get toMatchMakingJson => { |
||||
|
'skill_level': 0, |
||||
|
if (chooseTopics?.isNotEmpty ?? false) 'topic_ids': chooseTopics, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,176 @@ |
|||||
|
import 'dart:convert'; |
||||
|
|
||||
|
import 'package:centrifuge/centrifuge.dart'; |
||||
|
import 'package:flutter/foundation.dart'; |
||||
|
|
||||
|
class WebSocketService { |
||||
|
final Client _client; |
||||
|
late final Subscription _subscription; |
||||
|
|
||||
|
WebSocketService(this._client) { |
||||
|
_attachListeners(); |
||||
|
} |
||||
|
|
||||
|
/// ------- Connection ------- |
||||
|
Future<void> connect() async { |
||||
|
try { |
||||
|
await _client.connect(); |
||||
|
} catch (e) { |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void _attachListeners() { |
||||
|
_client.connecting.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Connection: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.connected.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Connected: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.disconnected.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Disconnected: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.message.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Message: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.error.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Error: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.join.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Join: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.leave.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Leave: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_client.publication.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Client Publication: $event'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
Stream<ConnectingEvent> get clientConnecting => _client.connecting; |
||||
|
|
||||
|
Stream<ConnectedEvent> get clientConnected => _client.connected; |
||||
|
|
||||
|
Stream<DisconnectedEvent> get clientDisconnected => _client.disconnected; |
||||
|
|
||||
|
Stream<ServerPublicationEvent> get clientPublication => _client.publication; |
||||
|
|
||||
|
Future<void> disconnect() async { |
||||
|
try { |
||||
|
await _client.disconnect(); |
||||
|
} catch (e) { |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Future<RPCResult> sendRpc({ |
||||
|
required String method, |
||||
|
required Map<String, dynamic> data, |
||||
|
}) async { |
||||
|
if (_client.state != State.connected) { |
||||
|
throw Exception('Websocket Is NOT Connected'); |
||||
|
} |
||||
|
try { |
||||
|
final RPCResult result = await _client.rpc(method, utf8.encode(json.encode(data))); |
||||
|
if (kDebugMode) { |
||||
|
print('RPC sent successfully. Result: $result'); |
||||
|
} |
||||
|
return result; |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
print('Error sending RPC: $e'); |
||||
|
} |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// ------- Connection ------- |
||||
|
Future<void> subscribe({required String channel}) async { |
||||
|
try { |
||||
|
_subscription = _client.newSubscription(channel); |
||||
|
_attachSubscribeListeners(); |
||||
|
await _subscription.subscribe(); |
||||
|
} catch (e) { |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void _attachSubscribeListeners() { |
||||
|
_subscription.error.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Error: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_subscription.join.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Join: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_subscription.leave.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Leave: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_subscription.publication.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Publication: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_subscription.subscribing.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Subscribing: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_subscription.subscribed.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Subscribed: $event'); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
_subscription.unsubscribed.listen((event) { |
||||
|
if (kDebugMode) { |
||||
|
print('Subscribe Unsubscribed: $event'); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
Stream<JoinEvent> get subscribeJoin => _subscription.join; |
||||
|
|
||||
|
Stream<PublicationEvent> get subscribePublication => _subscription.publication; |
||||
|
|
||||
|
Future<void> unSubscribe() async { |
||||
|
try { |
||||
|
await _subscription.unsubscribe(); |
||||
|
} catch (e) { |
||||
|
rethrow; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
import 'package:shia_game_flutter/core/constants/my_api.dart'; |
||||
|
import 'package:shia_game_flutter/core/network/http_request.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/response/base_response.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/data/model/clock_model.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/data/model/match_making_model.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/data/model/player_token_model.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/data/model/topics_model.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/clock_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/match_making_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/player_token_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/topics_entity.dart'; |
||||
|
|
||||
|
abstract class IBattleLeagueDatasource { |
||||
|
Future<PlayerTokenEntity> getPlayerToken({required BlParams params}); |
||||
|
Future<List<TopicsEntity>> getTopics({required BlParams params}); |
||||
|
Future<ClockEntity> getClock({required BlParams params}); |
||||
|
Future<MatchMakingEntity> matchMaking({required BlParams params}); |
||||
|
} |
||||
|
|
||||
|
class BattleLeagueDatasourceImpl implements IBattleLeagueDatasource { |
||||
|
final IHttpRequest httpRequest; |
||||
|
|
||||
|
const BattleLeagueDatasourceImpl(this.httpRequest); |
||||
|
|
||||
|
@override |
||||
|
Future<PlayerTokenEntity> getPlayerToken({required BlParams params}) async { |
||||
|
final response = await httpRequest.post(path: MyApi.centrifugoToken); |
||||
|
|
||||
|
return BaseResponse.getData<PlayerTokenEntity>( |
||||
|
response, |
||||
|
(json) => PlayerTokenModel.fromJson(json), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<List<TopicsEntity>> getTopics({required BlParams params}) async { |
||||
|
final response = await httpRequest.get(path: MyApi.topics); |
||||
|
|
||||
|
return BaseResponse.getDataList<TopicsEntity>( |
||||
|
response?['results'], |
||||
|
(json) => TopicsModel.fromJson(json), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<ClockEntity> getClock({required BlParams params}) async { |
||||
|
final response = await httpRequest.post( |
||||
|
path: MyApi.clock, |
||||
|
data: params.toCockJson, |
||||
|
); |
||||
|
|
||||
|
return BaseResponse.getData<ClockEntity>( |
||||
|
response, |
||||
|
(json) => ClockModel.fromJson(json), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<MatchMakingEntity> matchMaking({required BlParams params}) async { |
||||
|
final response = await httpRequest.post( |
||||
|
path: MyApi.matchMaking, |
||||
|
data: params.toMatchMakingJson, |
||||
|
); |
||||
|
|
||||
|
return BaseResponse.getData<MatchMakingEntity>( |
||||
|
response, |
||||
|
(json) => MatchMakingModel.fromJson(json), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/answer_entity.dart'; |
||||
|
|
||||
|
class AnswerModel extends AnswerEntity { |
||||
|
const AnswerModel({super.id, super.text}); |
||||
|
|
||||
|
factory AnswerModel.fromJson(Map<String, dynamic> json) { |
||||
|
return AnswerModel(id: json['id'], text: json['text']); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/clock_entity.dart'; |
||||
|
|
||||
|
class ClockModel extends ClockEntity { |
||||
|
const ClockModel({ |
||||
|
super.serverTime, |
||||
|
super.clientTime, |
||||
|
super.serverReceiveTime, |
||||
|
}); |
||||
|
|
||||
|
factory ClockModel.fromJson(Map<String, dynamic> json) { |
||||
|
return ClockModel( |
||||
|
clientTime: json['client_time'], |
||||
|
serverTime: json['server_time'], |
||||
|
serverReceiveTime: json['server_receive_time'], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/match_making_entity.dart'; |
||||
|
|
||||
|
class MatchMakingModel extends MatchMakingEntity { |
||||
|
const MatchMakingModel({ |
||||
|
super.estimatedWaitTime, |
||||
|
super.message, |
||||
|
super.position, |
||||
|
super.queueSize, |
||||
|
super.selectedTopics, |
||||
|
super.status, |
||||
|
}); |
||||
|
|
||||
|
factory MatchMakingModel.fromJson(Map<String, dynamic> json) { |
||||
|
return MatchMakingModel( |
||||
|
estimatedWaitTime: json['estimated_wait_time'], |
||||
|
message: json['message'], |
||||
|
position: json['position'], |
||||
|
queueSize: json['queue_size'], |
||||
|
selectedTopics: json['selected_topics']?.map<int>((e) => int.parse('$e')).toList(), |
||||
|
status: json['status'], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/player_token_entity.dart'; |
||||
|
|
||||
|
class PlayerTokenModel extends PlayerTokenEntity { |
||||
|
const PlayerTokenModel({super.token, super.user, super.channel}); |
||||
|
|
||||
|
factory PlayerTokenModel.fromJson(Map<String, dynamic> json) { |
||||
|
return PlayerTokenModel( |
||||
|
token: json['token'], |
||||
|
user: json['user'], |
||||
|
channel: json['channel'], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import 'package:shia_game_flutter/features/battle_league/data/model/answer_model.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/answer_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/question_entity.dart'; |
||||
|
|
||||
|
class QuestionModel extends QuestionEntity { |
||||
|
const QuestionModel({super.id, super.text, super.options, super.timeLimit}); |
||||
|
|
||||
|
factory QuestionModel.fromJson(Map<String, dynamic> json) { |
||||
|
return QuestionModel( |
||||
|
id: json['id'], |
||||
|
text: json['text'], |
||||
|
options: json['options'] |
||||
|
?.map<AnswerEntity>((e) => AnswerModel.fromJson(e)) |
||||
|
.toList(), |
||||
|
timeLimit: json['time_limit'], |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/topics_entity.dart'; |
|
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/topics_entity.dart'; |
||||
|
|
||||
class TopicsModel extends TopicsEntity { |
class TopicsModel extends TopicsEntity { |
||||
const TopicsModel({ |
const TopicsModel({ |
||||
@ -0,0 +1,94 @@ |
|||||
|
import 'package:flutter/foundation.dart'; |
||||
|
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/data/datasource/battle_league_datasource.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/clock_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/match_making_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/player_token_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/topics_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/repository/battle_league_repository.dart'; |
||||
|
|
||||
|
class BattleLeagueRepositoryImpl implements IBattleLeagueRepository { |
||||
|
final IBattleLeagueDatasource datasource; |
||||
|
|
||||
|
const BattleLeagueRepositoryImpl(this.datasource); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<PlayerTokenEntity, MyException>> getPlayerToken({ |
||||
|
required BlParams params, |
||||
|
}) async { |
||||
|
try { |
||||
|
final PlayerTokenEntity response = await datasource.getPlayerToken( |
||||
|
params: params, |
||||
|
); |
||||
|
return DataState.success(response); |
||||
|
} on MyException catch (e) { |
||||
|
return DataState.error(e); |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
rethrow; |
||||
|
} else { |
||||
|
return DataState.error(MyException(errorMessage: '$e')); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<List<TopicsEntity>, MyException>> getTopics({ |
||||
|
required BlParams params, |
||||
|
}) async { |
||||
|
try { |
||||
|
final List<TopicsEntity> response = await datasource.getTopics( |
||||
|
params: params, |
||||
|
); |
||||
|
return DataState.success(response); |
||||
|
} on MyException catch (e) { |
||||
|
return DataState.error(e); |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
rethrow; |
||||
|
} else { |
||||
|
return DataState.error(MyException(errorMessage: '$e')); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<ClockEntity, MyException>> getClock({ |
||||
|
required BlParams params, |
||||
|
}) async { |
||||
|
try { |
||||
|
final ClockEntity response = await datasource.getClock(params: params); |
||||
|
return DataState.success(response); |
||||
|
} on MyException catch (e) { |
||||
|
return DataState.error(e); |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
rethrow; |
||||
|
} else { |
||||
|
return DataState.error(MyException(errorMessage: '$e')); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<MatchMakingEntity, MyException>> matchMaking({ |
||||
|
required BlParams params, |
||||
|
}) async { |
||||
|
try { |
||||
|
final MatchMakingEntity response = await datasource.matchMaking( |
||||
|
params: params, |
||||
|
); |
||||
|
return DataState.success(response); |
||||
|
} on MyException catch (e) { |
||||
|
return DataState.error(e); |
||||
|
} catch (e) { |
||||
|
if (kDebugMode) { |
||||
|
rethrow; |
||||
|
} else { |
||||
|
return DataState.error(MyException(errorMessage: '$e')); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,11 @@ |
|||||
|
import 'package:equatable/equatable.dart'; |
||||
|
|
||||
|
class AnswerEntity extends Equatable { |
||||
|
final int? id; |
||||
|
final String? text; |
||||
|
|
||||
|
const AnswerEntity({this.id, this.text}); |
||||
|
|
||||
|
@override |
||||
|
List<Object?> get props => [id, text]; |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import 'package:equatable/equatable.dart'; |
||||
|
|
||||
|
class ClockEntity extends Equatable { |
||||
|
final double? serverTime; |
||||
|
final double? clientTime; |
||||
|
final double? serverReceiveTime; |
||||
|
|
||||
|
const ClockEntity({this.serverTime, this.clientTime, this.serverReceiveTime}); |
||||
|
|
||||
|
@override |
||||
|
List<Object?> get props => [serverTime, clientTime, serverReceiveTime]; |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
import 'package:equatable/equatable.dart'; |
||||
|
|
||||
|
class MatchMakingEntity extends Equatable { |
||||
|
final int? estimatedWaitTime; |
||||
|
final String? message; |
||||
|
final int? position; |
||||
|
final int? queueSize; |
||||
|
final List<int>? selectedTopics; |
||||
|
final String? status; |
||||
|
|
||||
|
const MatchMakingEntity({ |
||||
|
this.estimatedWaitTime, |
||||
|
this.message, |
||||
|
this.position, |
||||
|
this.queueSize, |
||||
|
this.selectedTopics, |
||||
|
this.status, |
||||
|
}); |
||||
|
|
||||
|
@override |
||||
|
List<Object?> get props => [ |
||||
|
estimatedWaitTime, |
||||
|
message, |
||||
|
position, |
||||
|
queueSize, |
||||
|
selectedTopics, |
||||
|
status, |
||||
|
]; |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import 'package:equatable/equatable.dart'; |
||||
|
|
||||
|
class PlayerTokenEntity extends Equatable { |
||||
|
final String? token; |
||||
|
final String? user; |
||||
|
final String? channel; |
||||
|
|
||||
|
const PlayerTokenEntity({this.token, this.user, this.channel}); |
||||
|
|
||||
|
@override |
||||
|
List<Object?> get props => [token, user, channel]; |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import 'package:equatable/equatable.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/answer_entity.dart'; |
||||
|
|
||||
|
class QuestionEntity extends Equatable { |
||||
|
final int? id; |
||||
|
final String? text; |
||||
|
final List<AnswerEntity>? options; |
||||
|
final int? timeLimit; |
||||
|
|
||||
|
const QuestionEntity({this.id, this.text, this.options, this.timeLimit}); |
||||
|
|
||||
|
@override |
||||
|
List<Object?> get props => [id, text, options, timeLimit]; |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/clock_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/match_making_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/player_token_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/topics_entity.dart'; |
||||
|
|
||||
|
abstract class IBattleLeagueRepository { |
||||
|
Future<DataState<PlayerTokenEntity, MyException>> getPlayerToken({ |
||||
|
required BlParams params, |
||||
|
}); |
||||
|
Future<DataState<List<TopicsEntity>, MyException>> getTopics({ |
||||
|
required BlParams params, |
||||
|
}); |
||||
|
Future<DataState<ClockEntity, MyException>> getClock({ |
||||
|
required BlParams params, |
||||
|
}); |
||||
|
Future<DataState<MatchMakingEntity, MyException>> matchMaking({ |
||||
|
required BlParams params, |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/usecase/usecase.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/clock_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/repository/battle_league_repository.dart'; |
||||
|
|
||||
|
class GetClockUseCase implements UseCase<ClockEntity, BlParams> { |
||||
|
final IBattleLeagueRepository repository; |
||||
|
|
||||
|
const GetClockUseCase(this.repository); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<ClockEntity, MyException>> call(BlParams params) { |
||||
|
return repository.getClock(params: params); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/usecase/usecase.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/player_token_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/repository/battle_league_repository.dart'; |
||||
|
|
||||
|
class GetPlayerTokenUseCase |
||||
|
implements UseCase<PlayerTokenEntity, BlParams> { |
||||
|
final IBattleLeagueRepository repository; |
||||
|
|
||||
|
const GetPlayerTokenUseCase(this.repository); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<PlayerTokenEntity, MyException>> call(BlParams params) { |
||||
|
return repository.getPlayerToken(params: params); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/usecase/usecase.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/topics_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/repository/battle_league_repository.dart'; |
||||
|
|
||||
|
class GetTopicsUseCase |
||||
|
implements UseCase<List<TopicsEntity>, BlParams> { |
||||
|
final IBattleLeagueRepository repository; |
||||
|
|
||||
|
const GetTopicsUseCase(this.repository); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<List<TopicsEntity>, MyException>> call(BlParams params,) { |
||||
|
return repository.getTopics(params: params); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/usecase/usecase.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/match_making_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/repository/battle_league_repository.dart'; |
||||
|
|
||||
|
class MatchMakingUseCase implements UseCase<MatchMakingEntity, BlParams> { |
||||
|
final IBattleLeagueRepository repository; |
||||
|
|
||||
|
const MatchMakingUseCase(this.repository); |
||||
|
|
||||
|
@override |
||||
|
Future<DataState<MatchMakingEntity, MyException>> call(BlParams params) { |
||||
|
return repository.matchMaking(params: params); |
||||
|
} |
||||
|
} |
||||
@ -1,28 +0,0 @@ |
|||||
import 'package:shia_game_flutter/core/constants/my_api.dart'; |
|
||||
import 'package:shia_game_flutter/core/network/http_request.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/battle_league_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/response/base_response.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/data/model/topics_model.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/topics_entity.dart'; |
|
||||
|
|
||||
abstract class IBattleLeagueDatasource { |
|
||||
Future<List<TopicsEntity>> getTopics({required BattleLeagueParams params}); |
|
||||
} |
|
||||
|
|
||||
class BattleLeagueDatasourceImpl implements IBattleLeagueDatasource { |
|
||||
final IHttpRequest httpRequest; |
|
||||
|
|
||||
const BattleLeagueDatasourceImpl(this.httpRequest); |
|
||||
|
|
||||
@override |
|
||||
Future<List<TopicsEntity>> getTopics({ |
|
||||
required BattleLeagueParams params, |
|
||||
}) async { |
|
||||
final response = await httpRequest.get(path: MyApi.topics); |
|
||||
|
|
||||
return BaseResponse.getDataList<TopicsEntity>( |
|
||||
response?['results'], |
|
||||
(json) => TopicsModel.fromJson(json), |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -1,14 +0,0 @@ |
|||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/battle_league_entity.dart'; |
|
||||
|
|
||||
class BattleLeagueModel extends BattleLeagueEntity { |
|
||||
const BattleLeagueModel({ |
|
||||
super.id, |
|
||||
}); |
|
||||
|
|
||||
factory BattleLeagueModel.fromJson(Map<String, dynamic> json) { |
|
||||
return BattleLeagueModel( |
|
||||
id: json['id'], |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@ -1,33 +0,0 @@ |
|||||
import 'package:flutter/foundation.dart'; |
|
||||
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/battle_league_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/data/datasource/battle_league_datasource.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/topics_entity.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/repository/battle_league_repository.dart'; |
|
||||
|
|
||||
class BattleLeagueRepositoryImpl implements IBattleLeagueRepository { |
|
||||
final IBattleLeagueDatasource datasource; |
|
||||
|
|
||||
const BattleLeagueRepositoryImpl(this.datasource); |
|
||||
|
|
||||
@override |
|
||||
Future<DataState<List<TopicsEntity>, MyException>> getTopics({ |
|
||||
required BattleLeagueParams params, |
|
||||
}) async { |
|
||||
try { |
|
||||
final List<TopicsEntity> response = await datasource.getTopics( |
|
||||
params: params, |
|
||||
); |
|
||||
return DataState.success(response); |
|
||||
} on MyException catch (e) { |
|
||||
return DataState.error(e); |
|
||||
} catch (e) { |
|
||||
if (kDebugMode) { |
|
||||
rethrow; |
|
||||
} else { |
|
||||
return DataState.error(MyException(errorMessage: '$e')); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
import 'package:equatable/equatable.dart'; |
|
||||
|
|
||||
class BattleLeagueEntity extends Equatable { |
|
||||
final int? id; |
|
||||
|
|
||||
const BattleLeagueEntity({ |
|
||||
this.id, |
|
||||
}); |
|
||||
|
|
||||
@override |
|
||||
List<Object?> get props => [ |
|
||||
id, |
|
||||
]; |
|
||||
} |
|
||||
|
|
||||
@ -1,10 +0,0 @@ |
|||||
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/battle_league_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/topics_entity.dart'; |
|
||||
|
|
||||
abstract class IBattleLeagueRepository { |
|
||||
Future<DataState<List<TopicsEntity>, MyException>> getTopics({ |
|
||||
required BattleLeagueParams params, |
|
||||
}); |
|
||||
} |
|
||||
@ -1,18 +0,0 @@ |
|||||
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/battle_league_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/usecase/usecase.dart'; |
|
||||
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/topics_entity.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/repository/battle_league_repository.dart'; |
|
||||
|
|
||||
class GetTopicsUseCase |
|
||||
implements UseCase<List<TopicsEntity>, BattleLeagueParams> { |
|
||||
final IBattleLeagueRepository repository; |
|
||||
|
|
||||
const GetTopicsUseCase(this.repository); |
|
||||
|
|
||||
@override |
|
||||
Future<DataState<List<TopicsEntity>, MyException>> call(BattleLeagueParams params,) { |
|
||||
return repository.getTopics(params: params); |
|
||||
} |
|
||||
} |
|
||||
@ -1,70 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/battle_league_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/routers/my_routes.dart'; |
|
||||
import 'package:shia_game_flutter/core/status/base_status.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/entity/topics_entity.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/first_part/domain/usecases/get_topics_usecase.dart'; |
|
||||
|
|
||||
class BattleLeagueController extends GetxController |
|
||||
with StateMixin, GetSingleTickerProviderStateMixin { |
|
||||
/// ----- Constructor ----- |
|
||||
BattleLeagueController(this._getTopicsUseCase); |
|
||||
|
|
||||
@override |
|
||||
void onInit() { |
|
||||
super.onInit(); |
|
||||
tabController = TabController(length: 2, vsync: this); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
void onClose() { |
|
||||
textEditingController.dispose(); |
|
||||
super.onClose(); |
|
||||
} |
|
||||
|
|
||||
/// ----- UseCases ----- |
|
||||
final GetTopicsUseCase _getTopicsUseCase; |
|
||||
|
|
||||
/// ----- Variables ----- |
|
||||
final BattleLeagueParams battleLeagueParams = BattleLeagueParams(); |
|
||||
final RxList<TopicsEntity> topicList = RxList.empty(); |
|
||||
|
|
||||
/// ------ Controllers ------ |
|
||||
final TextEditingController textEditingController = TextEditingController(); |
|
||||
late final TabController tabController; |
|
||||
|
|
||||
/// ------ Statuses ------ |
|
||||
final Rx<BaseStatus> getBattleLeagueStatus = Rx(const BaseInit()); |
|
||||
final Rx<BaseStatus> getTopicsStatus = Rx(const BaseInit()); |
|
||||
|
|
||||
/// ------ Functions ------ |
|
||||
void goToTopicPage() { |
|
||||
getTopics(); |
|
||||
Get.toNamed(Routes.battleLeagueTopicPage); |
|
||||
} |
|
||||
|
|
||||
void goToFindingPage() { |
|
||||
Get.toNamed(Routes.battleLeagueFindingPage); |
|
||||
} |
|
||||
|
|
||||
void goToFoundedPage() { |
|
||||
Get.toNamed(Routes.battleLeagueFoundedPage); |
|
||||
} |
|
||||
|
|
||||
/// ------ Api Calls ------ |
|
||||
Future<void> getTopics() async { |
|
||||
getTopicsStatus.value = const BaseLoading(); |
|
||||
await _getTopicsUseCase(battleLeagueParams).then( |
|
||||
(value) => value.fold( |
|
||||
(data) { |
|
||||
topicList.value = data; |
|
||||
getTopicsStatus.value = const BaseComplete(); |
|
||||
}, |
|
||||
(error) { |
|
||||
getTopicsStatus.value = BaseError(errorMessage: error.errorMessage); |
|
||||
}, |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -1,10 +1,15 @@ |
|||||
import 'package:shia_game_flutter/features/battle_league/first_part/presentation/controller/battle_league_controller.dart'; |
|
||||
|
import 'package:shia_game_flutter/features/battle_league/presentation/controller/battle_league_controller.dart'; |
||||
import 'package:get/get.dart'; |
import 'package:get/get.dart'; |
||||
|
|
||||
class BattleLeagueBinding extends Bindings { |
class BattleLeagueBinding extends Bindings { |
||||
@override |
@override |
||||
void dependencies() { |
void dependencies() { |
||||
Get.put<BattleLeagueController>(BattleLeagueController(Get.find())); |
|
||||
|
Get.put<BattleLeagueController>(BattleLeagueController( |
||||
|
Get.find(), |
||||
|
Get.find(), |
||||
|
Get.find(), |
||||
|
Get.find(), |
||||
|
)); |
||||
} |
} |
||||
|
|
||||
Future<void> deleteBindings() async { |
Future<void> deleteBindings() async { |
||||
@ -0,0 +1,280 @@ |
|||||
|
import 'dart:async'; |
||||
|
import 'dart:convert'; |
||||
|
|
||||
|
import 'package:centrifuge/centrifuge.dart'; |
||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:shia_game_flutter/core/auth_storage/auth_storage.dart'; |
||||
|
import 'package:shia_game_flutter/core/constants/my_api.dart'; |
||||
|
import 'package:shia_game_flutter/core/params/bl_params.dart'; |
||||
|
import 'package:shia_game_flutter/core/routers/my_routes.dart'; |
||||
|
import 'package:shia_game_flutter/core/services/web_socket_service.dart'; |
||||
|
import 'package:shia_game_flutter/core/status/base_status.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/data/model/question_model.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/player_token_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/question_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/entity/topics_entity.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/usecases/get_clock_usecase.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/usecases/get_player_token_usecase.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/usecases/get_topics_usecase.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/domain/usecases/match_making_usecase.dart'; |
||||
|
|
||||
|
class BattleLeagueController extends GetxController |
||||
|
with StateMixin, GetSingleTickerProviderStateMixin { |
||||
|
/// ----- Constructor ----- |
||||
|
BattleLeagueController( |
||||
|
this._getTopicsUseCase, |
||||
|
this._getPlayerTokenUseCase, |
||||
|
this._getClockUseCase, |
||||
|
this._matchMakingUseCase, |
||||
|
); |
||||
|
|
||||
|
@override |
||||
|
void onInit() { |
||||
|
super.onInit(); |
||||
|
tabController = TabController(length: 2, vsync: this); |
||||
|
// getPlayerToken(); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
void onClose() { |
||||
|
textEditingController.dispose(); |
||||
|
_webSocketService.disconnect(); |
||||
|
timer?.cancel(); |
||||
|
timer = null; |
||||
|
super.onClose(); |
||||
|
} |
||||
|
|
||||
|
/// ----- UseCases ----- |
||||
|
final GetTopicsUseCase _getTopicsUseCase; |
||||
|
final GetPlayerTokenUseCase _getPlayerTokenUseCase; |
||||
|
final GetClockUseCase _getClockUseCase; |
||||
|
final MatchMakingUseCase _matchMakingUseCase; |
||||
|
final WebSocketService _webSocketService = WebSocketService( |
||||
|
createClient( |
||||
|
MyApi.webSocketUrl, |
||||
|
ClientConfig( |
||||
|
name: 'Amirreza', |
||||
|
data: utf8.encode(jsonEncode({'player_token': AuthStorage.token})), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
|
||||
|
/// ----- Variables ----- |
||||
|
final BlParams blParams = BlParams(); |
||||
|
final RxList<TopicsEntity> topicList = RxList.empty(); |
||||
|
final RxList<TopicsEntity> chooseTopicList = RxList.empty(); |
||||
|
PlayerTokenEntity playerToken = const PlayerTokenEntity(); |
||||
|
final Rx<QuestionEntity> question = Rx(const QuestionEntity()); |
||||
|
int battleId = 0; |
||||
|
final RxInt roundNumber = RxInt(0); |
||||
|
final Rx<bool?> isCorrect = Rx(null); |
||||
|
final RxInt selectedIndex = RxInt(-1); |
||||
|
final RxInt roundTimer = RxInt(0); |
||||
|
final RxInt myWonNumber = RxInt(0); |
||||
|
final RxInt enemyWonNumber = RxInt(0); |
||||
|
Timer? timer; |
||||
|
String clientID = ''; |
||||
|
String player1ID = ''; |
||||
|
String player1Username = ''; |
||||
|
String player2ID = ''; |
||||
|
String player2Username = ''; |
||||
|
String myId = ''; |
||||
|
String enemyId = ''; |
||||
|
final RxString enemyUsername = RxString(''); |
||||
|
final Rx<String?> winnerId = Rx(null); |
||||
|
|
||||
|
/// ------ Controllers ------ |
||||
|
final TextEditingController textEditingController = TextEditingController(); |
||||
|
late final TabController tabController; |
||||
|
|
||||
|
/// ------ Statuses ------ |
||||
|
final Rx<BaseStatus> getTopicsStatus = Rx(const BaseInit()); |
||||
|
final Rx<BaseStatus> connectStatus = Rx(const BaseLoading()); |
||||
|
final Rx<BaseStatus> getClockStatus = Rx(const BaseInit()); |
||||
|
final Rx<BaseStatus> matchMakingStatus = Rx(const BaseInit()); |
||||
|
|
||||
|
/// ------ Functions ------ |
||||
|
void goToTopicPage() { |
||||
|
getTopics(); |
||||
|
Get.toNamed(Routes.battleLeagueTopicPage); |
||||
|
} |
||||
|
|
||||
|
void goToFindingPage() { |
||||
|
print(AuthStorage.token); |
||||
|
Get.toNamed(Routes.battleLeagueFindingPage); |
||||
|
_webSocketService.connect(); |
||||
|
_webSocketService.clientConnected.listen((event) { |
||||
|
clientID = json.decode(utf8.decode(event.data))['player_id']; |
||||
|
print(clientID); |
||||
|
getClock(); |
||||
|
connectStatus.value = const BaseComplete(); |
||||
|
}); |
||||
|
_webSocketService.clientPublication.listen((event) async { |
||||
|
final String type = json.decode(utf8.decode(event.data))['type']; |
||||
|
|
||||
|
if (type == 'match_found'){ |
||||
|
final String channel = json.decode( |
||||
|
utf8.decode(event.data), |
||||
|
)['battle_channel']; |
||||
|
final String battleId = json.decode(utf8.decode(event.data))['battle_id']; |
||||
|
player1ID = json.decode(utf8.decode(event.data))['player1']['id']; |
||||
|
player1Username = json.decode(utf8.decode(event.data))['player1']['username']; |
||||
|
player2ID = json.decode(utf8.decode(event.data))['player2']['id']; |
||||
|
player2Username = json.decode(utf8.decode(event.data))['player2']['username']; |
||||
|
if (player1ID == clientID) { |
||||
|
myId = player1ID; |
||||
|
enemyId = player2ID; |
||||
|
enemyUsername.value = player2Username; |
||||
|
} else { |
||||
|
myId = player2ID; |
||||
|
enemyId = player1ID; |
||||
|
enemyUsername.value = player1Username; |
||||
|
} |
||||
|
this.battleId = int.parse(battleId); |
||||
|
|
||||
|
await _webSocketService.subscribe(channel: channel); |
||||
|
_webSocketService.subscribeJoin.listen((event) { |
||||
|
Get.offNamed(Routes.battleLeagueQuestionPage); |
||||
|
}); |
||||
|
|
||||
|
_webSocketService.subscribePublication.listen((event) { |
||||
|
selectedIndex.value = -1; |
||||
|
isCorrect.value = null; |
||||
|
final String type = json.decode(utf8.decode(event.data))['type']; |
||||
|
|
||||
|
if (type == 'round_start') { |
||||
|
question.value = QuestionModel.fromJson( |
||||
|
json.decode(utf8.decode(event.data))['question'], |
||||
|
); |
||||
|
roundNumber.value = json.decode(utf8.decode(event.data))['round_number']; |
||||
|
roundTimer.value = json.decode(utf8.decode(event.data))['duration_seconds']; |
||||
|
timer?.cancel(); |
||||
|
timer = null; |
||||
|
timer = Timer.periodic(const Duration(seconds: 1), (timer) { |
||||
|
roundTimer.value--; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (type == 'battle_complete'){ |
||||
|
timer?.cancel(); |
||||
|
timer = null; |
||||
|
winnerId.value = json.decode(utf8.decode(event.data))['winner_id']; |
||||
|
Get.offNamed(Routes.battleLeagueResultPage); |
||||
|
} |
||||
|
|
||||
|
if (type == 'question_locked') { |
||||
|
final String lockedBy = json.decode( |
||||
|
utf8.decode(event.data))['locked_by']; |
||||
|
if (lockedBy == enemyId) { |
||||
|
print('rich enemy'); |
||||
|
enemyWonNumber.value = enemyWonNumber.value + 1; |
||||
|
} else { |
||||
|
print('rich myself'); |
||||
|
myWonNumber.value = myWonNumber.value + 1; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
Future<void> cancelFinding() async { |
||||
|
Get.back(); |
||||
|
connectStatus.value = const BaseLoading(); |
||||
|
await _webSocketService.unSubscribe(); |
||||
|
_webSocketService.disconnect(); |
||||
|
} |
||||
|
|
||||
|
void chooseTopic(TopicsEntity topic) { |
||||
|
if (chooseTopicList.contains(topic)) { |
||||
|
chooseTopicList.remove(topic); |
||||
|
} else { |
||||
|
if (chooseTopicList.length < 3) { |
||||
|
chooseTopicList.add(topic); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void choosRandomTopic() { |
||||
|
final List<TopicsEntity> newTopicList = List.from(topicList); |
||||
|
newTopicList.shuffle(); |
||||
|
chooseTopicList.value = newTopicList.take(3).toList(); |
||||
|
} |
||||
|
|
||||
|
Future<void> backToTopicPage() async { |
||||
|
Get.back(); |
||||
|
await _webSocketService.unSubscribe(); |
||||
|
_webSocketService.disconnect(); |
||||
|
} |
||||
|
|
||||
|
void chooseAnswer({required int index}) async { |
||||
|
selectedIndex.value = index; |
||||
|
await _webSocketService.sendRpc( |
||||
|
method: 'battle:submit_answer', |
||||
|
data: { |
||||
|
'battle_id': battleId, |
||||
|
'round_number': roundNumber.value, |
||||
|
'selected_option': index, |
||||
|
'client_timestamp': DateTime.timestamp().millisecondsSinceEpoch / 1000, |
||||
|
}, |
||||
|
).then((value) { |
||||
|
isCorrect.value = json.decode(utf8.decode(value.data))['is_correct']; |
||||
|
},); |
||||
|
} |
||||
|
|
||||
|
/// ------ Api Calls ------ |
||||
|
Future<void> getPlayerToken() async { |
||||
|
await _getPlayerTokenUseCase(blParams).then( |
||||
|
(value) => value.fold((data) { |
||||
|
playerToken = data; |
||||
|
_webSocketService.connect(); |
||||
|
}, (error) {}), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Future<void> getTopics() async { |
||||
|
getTopicsStatus.value = const BaseLoading(); |
||||
|
await _getTopicsUseCase(blParams).then( |
||||
|
(value) => value.fold( |
||||
|
(data) { |
||||
|
topicList.value = data; |
||||
|
getTopicsStatus.value = const BaseComplete(); |
||||
|
}, |
||||
|
(error) { |
||||
|
getTopicsStatus.value = BaseError(errorMessage: error.errorMessage); |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Future<void> getClock() async { |
||||
|
getClockStatus.value = const BaseLoading(); |
||||
|
await _getClockUseCase(blParams).then( |
||||
|
(value) => value.fold( |
||||
|
(data) { |
||||
|
matchMaking(); |
||||
|
getClockStatus.value = const BaseComplete(); |
||||
|
}, |
||||
|
(error) { |
||||
|
getClockStatus.value = BaseError(errorMessage: error.errorMessage); |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Future<void> matchMaking() async { |
||||
|
matchMakingStatus.value = const BaseLoading(); |
||||
|
blParams.chooseTopics = chooseTopicList.map((e) => e.id ?? 0).toList(); |
||||
|
await _matchMakingUseCase(blParams).then( |
||||
|
(value) => value.fold( |
||||
|
(data) { |
||||
|
matchMakingStatus.value = const BaseComplete(); |
||||
|
}, |
||||
|
(error) { |
||||
|
matchMakingStatus.value = BaseError(errorMessage: error.errorMessage); |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/gap.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/presentation/controller/battle_league_controller.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/presentation/ui/first_part/widgets/button/battle_purple_button.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/presentation/ui/second_part/widgets/question_board.dart'; |
||||
|
|
||||
|
class BattleLeagueResultPage extends GetView<BattleLeagueController> { |
||||
|
const BattleLeagueResultPage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
body: DecoratedBox( |
||||
|
decoration: const BoxDecoration( |
||||
|
gradient: LinearGradient( |
||||
|
begin: Alignment.topCenter, |
||||
|
end: Alignment.bottomCenter, |
||||
|
colors: [Color(0XFF390F72), Color(0XFF160C30)], |
||||
|
), |
||||
|
), |
||||
|
child: SafeArea( |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
Obx( |
||||
|
() => QuestionBoard( |
||||
|
enemyWonNumber: controller.enemyWonNumber.value, |
||||
|
myWonNumber: controller.myWonNumber.value, |
||||
|
roundNumber: controller.roundNumber.value, |
||||
|
enemyUserName: controller.enemyUsername.value, |
||||
|
), |
||||
|
), |
||||
|
50.0.gapHeight, |
||||
|
Obx( |
||||
|
() => Text( |
||||
|
controller.winnerId.value == controller.myId |
||||
|
? 'YOU WON GAME' |
||||
|
: controller.winnerId.value == controller.enemyId |
||||
|
? 'YOU LOOS GAME' |
||||
|
: 'DRAW GAME', |
||||
|
style: Lexend.black, |
||||
|
), |
||||
|
), |
||||
|
Spacer(), |
||||
|
Padding( |
||||
|
padding: const EdgeInsets.symmetric(horizontal: 16), |
||||
|
child: BattlePurpleButton( |
||||
|
label: 'Play Another Game', |
||||
|
onTap: controller.backToTopicPage, |
||||
|
), |
||||
|
), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,89 @@ |
|||||
|
import 'package:flutter/material.dart'; |
||||
|
import 'package:get/get.dart'; |
||||
|
import 'package:shia_game_flutter/common_ui/resources/my_colors.dart'; |
||||
|
import 'package:shia_game_flutter/common_ui/resources/my_text_style.dart'; |
||||
|
import 'package:shia_game_flutter/core/utils/gap.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/presentation/controller/battle_league_controller.dart'; |
||||
|
import 'package:shia_game_flutter/features/battle_league/presentation/ui/second_part/widgets/question_board.dart'; |
||||
|
|
||||
|
class BLQuestionPage extends GetView<BattleLeagueController> { |
||||
|
const BLQuestionPage({super.key}); |
||||
|
|
||||
|
@override |
||||
|
Widget build(BuildContext context) { |
||||
|
return Scaffold( |
||||
|
body: DecoratedBox( |
||||
|
decoration: const BoxDecoration( |
||||
|
gradient: LinearGradient( |
||||
|
begin: Alignment.topCenter, |
||||
|
end: Alignment.bottomCenter, |
||||
|
colors: [Color(0XFF390F72), Color(0XFF160C30)], |
||||
|
), |
||||
|
), |
||||
|
child: SafeArea( |
||||
|
child: Column( |
||||
|
children: [ |
||||
|
Obx( |
||||
|
() => QuestionBoard( |
||||
|
enemyWonNumber: controller.enemyWonNumber.value, |
||||
|
myWonNumber: controller.myWonNumber.value, |
||||
|
roundNumber: controller.roundNumber.value, |
||||
|
enemyUserName: controller.enemyUsername.value, |
||||
|
), |
||||
|
), |
||||
|
50.0.gapHeight, |
||||
|
Obx( |
||||
|
() => Text( |
||||
|
'${controller.roundTimer.value}', |
||||
|
style: Lexend.black, |
||||
|
), |
||||
|
), |
||||
|
25.0.gapHeight, |
||||
|
Obx( |
||||
|
() => Text( |
||||
|
controller.question.value.text ?? '', |
||||
|
style: Lexend.bold, |
||||
|
), |
||||
|
), |
||||
|
const Spacer(), |
||||
|
Obx( |
||||
|
() => ListView.separated( |
||||
|
itemCount: controller.question.value.options?.length ?? 0, |
||||
|
shrinkWrap: true, |
||||
|
padding: const EdgeInsets.symmetric(horizontal: 20), |
||||
|
itemBuilder: (context, index) => Material( |
||||
|
type: MaterialType.transparency, |
||||
|
child: Obx( |
||||
|
() => ListTile( |
||||
|
selected: index == controller.selectedIndex.value, |
||||
|
selectedColor: MyColors.white, |
||||
|
selectedTileColor: controller.isCorrect.value == true |
||||
|
? Colors.green |
||||
|
: controller.isCorrect.value == false |
||||
|
? Colors.red |
||||
|
: Colors.yellow, |
||||
|
onTap: () { |
||||
|
controller.chooseAnswer(index: index); |
||||
|
}, |
||||
|
title: Text( |
||||
|
controller.question.value.options?[index].text ?? '', |
||||
|
textDirection: TextDirection.rtl, |
||||
|
), |
||||
|
titleTextStyle: Lexend.bold, |
||||
|
shape: const StadiumBorder( |
||||
|
side: BorderSide(width: 1, color: MyColors.white), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
separatorBuilder: (context, index) => 12.0.gapHeight, |
||||
|
), |
||||
|
), |
||||
|
const Spacer(), |
||||
|
], |
||||
|
), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -1,28 +0,0 @@ |
|||||
import 'package:shia_game_flutter/core/constants/my_api.dart'; |
|
||||
import 'package:shia_game_flutter/core/network/http_request.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/bl_question_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/response/base_response.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/data/model/bl_question_model.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart'; |
|
||||
|
|
||||
abstract class IBLQuestionDatasource { |
|
||||
Future<BLQuestionEntity> getData({required BLQuestionParams params}); |
|
||||
} |
|
||||
|
|
||||
class BLQuestionDatasourceImpl implements IBLQuestionDatasource { |
|
||||
final IHttpRequest httpRequest; |
|
||||
|
|
||||
const BLQuestionDatasourceImpl(this.httpRequest); |
|
||||
|
|
||||
@override |
|
||||
Future<BLQuestionEntity> getData({required BLQuestionParams params}) async { |
|
||||
final response = await httpRequest.get( |
|
||||
path: MyApi.baseUrl, |
|
||||
); |
|
||||
|
|
||||
return BaseResponse.getData<BLQuestionEntity>( |
|
||||
response?['data'], |
|
||||
(json) => BLQuestionModel.fromJson(json), |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -1,13 +0,0 @@ |
|||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart'; |
|
||||
|
|
||||
class BLQuestionModel extends BLQuestionEntity { |
|
||||
const BLQuestionModel({ |
|
||||
super.id, |
|
||||
}); |
|
||||
|
|
||||
factory BLQuestionModel.fromJson(Map<String, dynamic> json) { |
|
||||
return BLQuestionModel( |
|
||||
id: json['id'], |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
import 'package:flutter/foundation.dart'; |
|
||||
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/bl_question_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/data/datasource/bl_question_datasource.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/repository/bl_question_repository.dart'; |
|
||||
|
|
||||
class BLQuestionRepositoryImpl implements IBLQuestionRepository { |
|
||||
final IBLQuestionDatasource datasource; |
|
||||
|
|
||||
const BLQuestionRepositoryImpl(this.datasource); |
|
||||
|
|
||||
@override |
|
||||
Future<DataState<BLQuestionEntity, MyException>> getData({required BLQuestionParams params}) async { |
|
||||
try { |
|
||||
final BLQuestionEntity response = await datasource.getData(params: params); |
|
||||
return DataState.success(response); |
|
||||
} on MyException catch (e) { |
|
||||
return DataState.error(e); |
|
||||
} catch (e) { |
|
||||
if (kDebugMode) { |
|
||||
rethrow; |
|
||||
} else { |
|
||||
return DataState.error(MyException(errorMessage: '$e')); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,14 +0,0 @@ |
|||||
import 'package:equatable/equatable.dart'; |
|
||||
|
|
||||
class BLQuestionEntity extends Equatable { |
|
||||
final int? id; |
|
||||
|
|
||||
const BLQuestionEntity({ |
|
||||
this.id, |
|
||||
}); |
|
||||
|
|
||||
@override |
|
||||
List<Object?> get props => [ |
|
||||
id, |
|
||||
]; |
|
||||
} |
|
||||
@ -1,8 +0,0 @@ |
|||||
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/bl_question_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart'; |
|
||||
|
|
||||
abstract class IBLQuestionRepository { |
|
||||
Future<DataState<BLQuestionEntity, MyException>> getData({required BLQuestionParams params}); |
|
||||
} |
|
||||
@ -1,19 +0,0 @@ |
|||||
import 'package:shia_game_flutter/core/error_handler/my_exception.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/bl_question_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/usecase/usecase.dart'; |
|
||||
import 'package:shia_game_flutter/core/utils/data_state.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/repository/bl_question_repository.dart'; |
|
||||
|
|
||||
class GetBLQuestionUseCase implements UseCase<BLQuestionEntity, BLQuestionParams> { |
|
||||
final IBLQuestionRepository repository; |
|
||||
|
|
||||
const GetBLQuestionUseCase(this.repository); |
|
||||
|
|
||||
@override |
|
||||
Future<DataState<BLQuestionEntity, MyException>> call(BLQuestionParams params) { |
|
||||
return repository.getData(params: params); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@ -1,20 +0,0 @@ |
|||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/presentation/controller/bl_question_controller.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
|
|
||||
class BLQuestionBinding extends Bindings { |
|
||||
@override |
|
||||
void dependencies() { |
|
||||
Get.put<BLQuestionController>(BLQuestionController(Get.find())); |
|
||||
} |
|
||||
|
|
||||
Future<void> deleteBindings() async { |
|
||||
await Future.wait([ |
|
||||
Get.delete<BLQuestionController>(), |
|
||||
]); |
|
||||
} |
|
||||
|
|
||||
Future<void> refreshBinding() async { |
|
||||
await deleteBindings(); |
|
||||
dependencies(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,54 +0,0 @@ |
|||||
import 'package:flutter/cupertino.dart'; |
|
||||
import 'package:shia_game_flutter/core/params/bl_question_params.dart'; |
|
||||
import 'package:shia_game_flutter/core/status/base_status.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/entity/bl_question_entity.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/domain/usecases/get_bl_question_usecase.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
|
|
||||
class BLQuestionController extends GetxController with StateMixin { |
|
||||
/// ----- Constructor ----- |
|
||||
BLQuestionController(this.getBLQuestionUseCase); |
|
||||
|
|
||||
@override |
|
||||
void onInit() { |
|
||||
super.onInit(); |
|
||||
change('', status: RxStatus.success()); |
|
||||
} |
|
||||
|
|
||||
@override |
|
||||
void onClose() { |
|
||||
textEditingController.dispose(); |
|
||||
super.onClose(); |
|
||||
} |
|
||||
|
|
||||
/// ----- UseCases ----- |
|
||||
final GetBLQuestionUseCase getBLQuestionUseCase; |
|
||||
|
|
||||
/// ----- Variables ----- |
|
||||
final Rx<BLQuestionParams> bLQuestionParams = Rx(BLQuestionParams()); |
|
||||
final Rx<BLQuestionEntity> bLQuestionEntity = Rx(const BLQuestionEntity()); |
|
||||
|
|
||||
/// ------ Controllers ------ |
|
||||
final TextEditingController textEditingController = TextEditingController(); |
|
||||
|
|
||||
/// ------ Statuses ------ |
|
||||
final Rx<BaseStatus> getBLQuestionStatus = Rx(const BaseInit()); |
|
||||
|
|
||||
/// ------ Functions ------ |
|
||||
|
|
||||
/// ------ Api Calls ------ |
|
||||
Future<void> getBLQuestion() async { |
|
||||
change('', status: RxStatus.loading()); |
|
||||
await getBLQuestionUseCase(bLQuestionParams.value).then( |
|
||||
(value) => value.fold( |
|
||||
(data) { |
|
||||
bLQuestionEntity.value = data; |
|
||||
change('', status: RxStatus.success()); |
|
||||
}, |
|
||||
(error) { |
|
||||
change('', status: RxStatus.error(error.errorMessage)); |
|
||||
}, |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +0,0 @@ |
|||||
import 'package:flutter/material.dart'; |
|
||||
import 'package:get/get.dart'; |
|
||||
import 'package:shia_game_flutter/core/widgets/stepper/my_stepper.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/presentation/controller/bl_question_controller.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/presentation/ui/widgets/question_board.dart'; |
|
||||
import 'package:shia_game_flutter/features/battle_league/question_part/bl_question/presentation/ui/widgets/question_timer_ready_widget.dart'; |
|
||||
|
|
||||
class BLQuestionPage extends GetView<BLQuestionController> { |
|
||||
const BLQuestionPage({super.key}); |
|
||||
|
|
||||
@override |
|
||||
Widget build(BuildContext context) { |
|
||||
return const Scaffold( |
|
||||
body: DecoratedBox( |
|
||||
decoration: BoxDecoration( |
|
||||
gradient: LinearGradient( |
|
||||
begin: Alignment.topCenter, |
|
||||
end: Alignment.bottomCenter, |
|
||||
colors: [Color(0XFF390F72), Color(0XFF160C30)], |
|
||||
), |
|
||||
), |
|
||||
child: SafeArea( |
|
||||
child: Column( |
|
||||
children: [ |
|
||||
QuestionBoard(), |
|
||||
MyStepper(), |
|
||||
QuestionTimerReadyWidget(), |
|
||||
], |
|
||||
), |
|
||||
), |
|
||||
), |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue