From ff49c9f45e761bfb4e4acfb38b7c170078f0c9de Mon Sep 17 00:00:00 2001 From: mohsen Date: Thu, 19 Oct 2023 13:35:24 +0330 Subject: [PATCH] fix some bug --- .../box_list/setting_box/app_setting_box.dart | 17 +- .../boxes/box_list/setting_box/box_keys.dart | 1 + .../initializer/gamification_initializer.dart | 34 ---- .../initializer/hamrah_cloud_initializer.dart | 37 ---- .../lib/initializer/logger_initializer.dart | 39 ---- .../lib/network/helper/config.dart | 14 +- .../lib/network/networking/api_endpoint.dart | 132 +------------- .../lib/network/networking/api_service.dart | 5 - .../interceptors/api_interceptor.dart | 22 +-- .../app_api_data/app_api_repository_impl.dart | 86 +++++++++ .../app_setting_box_repository_impl.dart | 10 ++ data/data_types/data/pubspec.yaml | 2 + .../app_api_domain/models/comment_model.dart | 19 ++ .../models/contact_us_model.dart | 19 ++ .../app_api_domain/models/login_model.dart | 22 +++ .../models/login_response_model.dart | 13 ++ .../lib/app_api_domain/models/post_model.dart | 43 +++++ .../models/send_comment_model.dart | 19 ++ .../models/single_post_model.dart | 45 +++++ .../repository/app_api_repository.dart | 23 +++ .../app_setting_box_repository.dart | 4 + .../cubit/select_language_cubit.dart | 45 ++++- lib/core/utils/app_utils.dart | 16 ++ lib/features/about_us/about_us_screen.dart | 2 + lib/features/main/main_screen.dart | 170 +++++------------- lib/features/posts/cubit/posts_cubit.dart | 77 ++++---- lib/features/posts/screen/posts_screen.dart | 80 ++++----- .../posts/widgets/post_item_widget.dart | 12 +- .../single_post/cubit/single_post_cubit.dart | 19 ++ .../screen/single_post_screen.dart | 27 ++- lib/features/splash/cubit/splash_cubit.dart | 2 +- pubspec.lock | 87 ++++++++- pubspec.yaml | 3 + 33 files changed, 642 insertions(+), 504 deletions(-) delete mode 100644 data/data_core/network/network_core/lib/initializer/gamification_initializer.dart delete mode 100644 data/data_core/network/network_core/lib/initializer/hamrah_cloud_initializer.dart delete mode 100644 data/data_core/network/network_core/lib/initializer/logger_initializer.dart create mode 100644 data/data_types/data/lib/app_api_data/app_api_repository_impl.dart create mode 100644 domain/repositories/lib/app_api_domain/models/comment_model.dart create mode 100644 domain/repositories/lib/app_api_domain/models/contact_us_model.dart create mode 100644 domain/repositories/lib/app_api_domain/models/login_model.dart create mode 100644 domain/repositories/lib/app_api_domain/models/login_response_model.dart create mode 100644 domain/repositories/lib/app_api_domain/models/post_model.dart create mode 100644 domain/repositories/lib/app_api_domain/models/send_comment_model.dart create mode 100644 domain/repositories/lib/app_api_domain/models/single_post_model.dart create mode 100644 domain/repositories/lib/app_api_domain/repository/app_api_repository.dart diff --git a/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/app_setting_box.dart b/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/app_setting_box.dart index 0df1521..f5cd4f4 100644 --- a/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/app_setting_box.dart +++ b/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/app_setting_box.dart @@ -50,7 +50,7 @@ class AppSettingBox implements ConfigBoxInterface { } Future getUserData() async { - if(!Hive.isBoxOpen(getBoxName())) { + if (!Hive.isBoxOpen(getBoxName())) { await Hive.openBox(getBoxName()); } Map? data = await _box!.get(getKeyName(BoxKeys.userData)); @@ -63,4 +63,19 @@ class AppSettingBox implements ConfigBoxInterface { Future setUserData(Map data) async { await _box!.put(getKeyName(BoxKeys.userData), data); } + + Future setUserToken(Map data) async { + await _box!.put(getKeyName(BoxKeys.token), data); + } + + Future getUserToken() async { + if (!Hive.isBoxOpen(getBoxName())) { + await Hive.openBox(getBoxName()); + } + Map? data = await _box!.get(getKeyName(BoxKeys.userData)); + if (data == null) { + return 'token'; + } + return data['token']; + } } diff --git a/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/box_keys.dart b/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/box_keys.dart index 2be7d14..f8da6b9 100644 --- a/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/box_keys.dart +++ b/data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/box_keys.dart @@ -1,6 +1,7 @@ enum BoxKeys { currentLanguage("current-language"), userData('user-data'), + token('token'), appDirection("app-direction"); final String value; diff --git a/data/data_core/network/network_core/lib/initializer/gamification_initializer.dart b/data/data_core/network/network_core/lib/initializer/gamification_initializer.dart deleted file mode 100644 index 18515b2..0000000 --- a/data/data_core/network/network_core/lib/initializer/gamification_initializer.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:network_core/network/interface/network_initializer_interface.dart'; -import 'package:network_core/network/networking/api_endpoint.dart'; -import 'package:network_core/network/networking/api_service.dart'; -import 'package:network_core/network/networking/dio_service.dart'; - -class GamificationInitializer implements NetworkInitializerInterface { - factory GamificationInitializer() { - return instance; - } - - GamificationInitializer.privateConstructor(); - - static final GamificationInitializer instance = GamificationInitializer.privateConstructor(); - - @override - DioService getDioService() { - return DioService( - dioClient: getDioClient(), - interceptors: [], - ); - } - - @override - Dio getDioClient() { - final baseOptions = BaseOptions(baseUrl: ApiEndpoint.gamificationBaseUrl); - return Dio(baseOptions); - } - - @override - ApiService getApiService() { - return ApiService(getDioService()); - } -} diff --git a/data/data_core/network/network_core/lib/initializer/hamrah_cloud_initializer.dart b/data/data_core/network/network_core/lib/initializer/hamrah_cloud_initializer.dart deleted file mode 100644 index 8ff1035..0000000 --- a/data/data_core/network/network_core/lib/initializer/hamrah_cloud_initializer.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:network_core/network/interface/network_initializer_interface.dart'; -import 'package:network_core/network/networking/api_endpoint.dart'; -import 'package:network_core/network/networking/api_service.dart'; -import 'package:network_core/network/networking/dio_service.dart'; -import 'package:network_core/network/networking/interceptors/api_interceptor.dart'; - -class HamrahCloudInitializer implements NetworkInitializerInterface { - factory HamrahCloudInitializer() { - return instance; - } - - HamrahCloudInitializer.privateConstructor(); - - static final HamrahCloudInitializer instance = HamrahCloudInitializer.privateConstructor(); - - @override - DioService getDioService() { - return DioService( - dioClient: getDioClient(), - interceptors: [ - ApiInterceptor(useToken: false, addAllowMethod: false), - ], - ); - } - - @override - Dio getDioClient() { - final baseOptions = BaseOptions(baseUrl: ApiEndpoint.hamrahCloudBaseUrl); - return Dio(baseOptions); - } - - @override - ApiService getApiService() { - return ApiService(getDioService()); - } -} diff --git a/data/data_core/network/network_core/lib/initializer/logger_initializer.dart b/data/data_core/network/network_core/lib/initializer/logger_initializer.dart deleted file mode 100644 index 6aecdc6..0000000 --- a/data/data_core/network/network_core/lib/initializer/logger_initializer.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:dio/dio.dart'; -import 'package:network_core/network/interface/network_initializer_interface.dart'; -import 'package:network_core/network/networking/api_endpoint.dart'; -import 'package:network_core/network/networking/api_service.dart'; -import 'package:network_core/network/networking/dio_service.dart'; -import 'package:network_core/network/networking/interceptors/api_interceptor.dart'; - -class LoggerInitializer implements NetworkInitializerInterface { - factory LoggerInitializer() { - return instance; - } - - LoggerInitializer.privateConstructor(); - - static final LoggerInitializer instance = LoggerInitializer.privateConstructor(); - - @override - DioService getDioService() { - return DioService( - dioClient: getDioClient(), - interceptors: [ - ApiInterceptor(), - ], - ); - } - - @override - Dio getDioClient() { - final baseOptions = BaseOptions( - baseUrl: ApiEndpoint.loggerBaseUrl, - ); - return Dio(baseOptions); - } - - @override - ApiService getApiService() { - return ApiService(getDioService()); - } -} diff --git a/data/data_core/network/network_core/lib/network/helper/config.dart b/data/data_core/network/network_core/lib/network/helper/config.dart index 86bcd11..a3165b0 100644 --- a/data/data_core/network/network_core/lib/network/helper/config.dart +++ b/data/data_core/network/network_core/lib/network/helper/config.dart @@ -4,17 +4,5 @@ import 'package:flutter/foundation.dart'; class Config { const Config._(); - static const baseUrl = String.fromEnvironment('BASE_URL', defaultValue: 'https://api-game.zarebin.ir/api'); - - static const loggerBaseUrl = String.fromEnvironment( - 'BASE_URL', - defaultValue: 'https://supervisor.pr.mci.dev/api/v1/', - ); - - static const hamrahCloudBaseUrl = String.fromEnvironment( - 'BASE_URL', - defaultValue: 'https://hamrahi.cloud/live/api/v1', - ); - - static const gamificationBaseUrl = String.fromEnvironment('BASE_URL', defaultValue: 'https://eagle-eyes.st.mci.dev'); + static const baseUrl = String.fromEnvironment('BASE_URL', defaultValue: 'https://sonnat.net/api/mobile/'); } diff --git a/data/data_core/network/network_core/lib/network/networking/api_endpoint.dart b/data/data_core/network/network_core/lib/network/networking/api_endpoint.dart index 2d8d68c..1419af2 100644 --- a/data/data_core/network/network_core/lib/network/networking/api_endpoint.dart +++ b/data/data_core/network/network_core/lib/network/networking/api_endpoint.dart @@ -6,135 +6,21 @@ class ApiEndpoint { const ApiEndpoint._(); static const baseUrl = Config.baseUrl; - static const loggerBaseUrl = Config.loggerBaseUrl; - static const hamrahCloudBaseUrl = Config.hamrahCloudBaseUrl; - static const gamificationBaseUrl = Config.gamificationBaseUrl; - static String account(AccountEndpoint endpoint) { - return '/account/${endpoint.value}'; + static String api(AppEndpoint endpoint) { + return '${endpoint.value}'; } - - static String game(GameEndpoint endpoint) { - return '/game/${endpoint.value}'; - } - - static String general(GeneralEndPoint endPoint) { - return '/general/${endPoint.value}'; - } - - static String leaderboard(LeaderboardEndpoint endpoint) { - return '/leaderboard/${endpoint.value}'; - } - - static String gamePlayer(GamePlayerEndpoint endpoint) { - return '/game-player/${endpoint.value}'; - } - - static String getFriendReq(FriendRequestEndPoint endPoint) { - return '/player/friendRequest/${endPoint.value}'; - } - - static String logger(LogEndPoint endPoint) { - return endPoint.value; - } - - static String hamrahCloud(HamrahCloudEndpoint endPoint) { - return endPoint.value; - } - - static String campaigns(CampaignsEndPoint endPoint) { - return '/api/v1/campaigns/${endPoint.value}/'; - } -} - -enum AccountEndpoint { - profile('profile'); - - final String value; - - const AccountEndpoint(this.value); } -enum GamePlayerEndpoint { - playerInfo('gamePlayerInfo'), - myRank('best'); - - final String value; - - const GamePlayerEndpoint(this.value); -} - -enum GameEndpoint { - banners('banners'), - categories('categories'), - favoriteGames('favorite-games'), - gameProfile('game-profile'), - games('games'), - recentPlayedGames('recent-played-games'), +enum AppEndpoint { + login('login'), + sendComment('send_comment'), search('search'), - searchHistory('search-history'), - suggestedGames('suggested-games'), - topGames('top-games'); + contactUs('contact_us'), + singlePost('singlepost'), + postList('getarticles'); final String value; - const GameEndpoint(this.value); + const AppEndpoint(this.value); } - -enum GeneralEndPoint { - streams('streams'), - whiteList('channels/whitelist'), - deepLink('deeplink'), - lv('lv'), - rootItems('root_items'); - - final String value; - - const GeneralEndPoint(this.value); -} - -enum LeaderboardEndpoint { - campaignLeaderboard('campaign-leaderboard'), - games('games'), - leaderboards('leaderboards'); - - final String value; - - const LeaderboardEndpoint(this.value); -} - -enum FriendRequestEndPoint { - createFriendReq(''), - answerFriendReq('answer'), - getWaitingAddFriendReq('waiting'); - - final String value; - - const FriendRequestEndPoint(this.value); -} - -enum LogEndPoint { - multiEvent('events'), - singleEvent('event'); - - final String value; - - const LogEndPoint(this.value); -} - -enum HamrahCloudEndpoint { - list('/vod/viewers/list-vod/'), - search('/live/search/pinobject/387/?'); - - final String value; - - const HamrahCloudEndpoint(this.value); -} - -enum CampaignsEndPoint { - events('events'); - - final String value; - - const CampaignsEndPoint(this.value); -} \ No newline at end of file diff --git a/data/data_core/network/network_core/lib/network/networking/api_service.dart b/data/data_core/network/network_core/lib/network/networking/api_service.dart index f374684..0f6fcc3 100644 --- a/data/data_core/network/network_core/lib/network/networking/api_service.dart +++ b/data/data_core/network/network_core/lib/network/networking/api_service.dart @@ -24,11 +24,6 @@ class ApiService implements ApiInterface { try { data = await _dioService.getData( endpoint: endpoint, - options: Options( - extra: { - 'requiresAuthToken': requiresAuthToken, - }, - ), queryParams: queryParams, cancelToken: cancelToken, ); diff --git a/data/data_core/network/network_core/lib/network/networking/interceptors/api_interceptor.dart b/data/data_core/network/network_core/lib/network/networking/interceptors/api_interceptor.dart index f869e15..54b7dcb 100644 --- a/data/data_core/network/network_core/lib/network/networking/interceptors/api_interceptor.dart +++ b/data/data_core/network/network_core/lib/network/networking/interceptors/api_interceptor.dart @@ -1,15 +1,13 @@ import 'package:data/app_setting_data/repository/app_setting_box_repository_impl.dart'; import 'package:dio/dio.dart'; import 'package:local_db_core/lib/boxes/box_list/setting_box/app_setting_box.dart'; -import 'package:repositories/app_setting_box_domain/model/user_data_model.dart'; import 'package:repositories/app_setting_box_domain/repository/app_setting_box_repository.dart'; class ApiInterceptor extends Interceptor { final bool useToken; - final bool addAllowMethod; AppSettingBoxRepository _repository = AppSettingBoxRepositoryImpl(appSettingBox: AppSettingBox()); - ApiInterceptor({this.useToken = true, this.addAllowMethod = true}); + ApiInterceptor({this.useToken = true}); @override Future onRequest(RequestOptions options, RequestInterceptorHandler handler) async { @@ -18,19 +16,13 @@ class ApiInterceptor extends Interceptor { 'Content-Type': 'application/json', 'Accept': '*/*', }); - if (addAllowMethod) { - options.headers.addAll({ - 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, HEAD', - 'Access-Control-Allow-Origin': '*', - }); - } - UserDataModel? userData = await _repository.getUserData(); + options.headers.addAll({ + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, HEAD', + 'Access-Control-Allow-Origin': '*', + }); + String token = await _repository.getUserToken(); if (useToken) { - if (userData != null) { - options.headers.addAll({ - 'Authorization': 'Bearer ${userData.token}', - }); - } + options.headers.addAll({'Authorization': '$token'}); } } options.extra.remove('requiresAuthToken'); diff --git a/data/data_types/data/lib/app_api_data/app_api_repository_impl.dart b/data/data_types/data/lib/app_api_data/app_api_repository_impl.dart new file mode 100644 index 0000000..692a7a1 --- /dev/null +++ b/data/data_types/data/lib/app_api_data/app_api_repository_impl.dart @@ -0,0 +1,86 @@ +import 'package:network_core/network/networking/api_endpoint.dart'; +import 'package:network_core/network/networking/api_service.dart'; +import 'package:repositories/app_api_domain/models/login_response_model.dart'; +import 'package:repositories/app_api_domain/models/post_model.dart'; +import 'package:repositories/app_api_domain/models/single_post_model.dart'; +import 'package:repositories/app_api_domain/repository/app_api_repository.dart'; + +class AppApiRepositoryImpl implements AppApiRepository { + final ApiService _apiService; + + AppApiRepositoryImpl({required ApiService apiService}) : _apiService = apiService; + + @override + Future contactUs({required Map data}) async { + return await _apiService.setData( + endpoint: ApiEndpoint.api(AppEndpoint.contactUs), + converter: (response) {}, + data: data, + ); + } + + @override + Future> getPostList({ + required String catId, + required String sort, + required int page, + required String languageCode, + }) async { + return await _apiService.getCollectionData( + endpoint: + '${ApiEndpoint.api(AppEndpoint.postList)}?cat=$catId&sort=$sort&page=$page&language_code=$languageCode', + converter: (response) { + return List.from( + response.data['data'].map((game) { + return PostModel.fromJson(game); + }), + ); + }, + ); + } + + @override + Future getSinglePost({required int postId}) async { + return await _apiService.getDocumentData( + endpoint: '${ApiEndpoint.api(AppEndpoint.singlePost)}?post=$postId', + converter: (response) { + return SinglePostModel.fromJson(response); + }, + ); + } + + @override + Future login({required Map data}) async { + return await _apiService.setData( + endpoint: ApiEndpoint.api(AppEndpoint.login), + converter: (response) { + return LoginResponseModel.fromJson(response.data); + }, + queryParams: data, + requiresAuthToken: false, + ); + } + + @override + Future> searchPost({required String query, required String languageCode}) async { + return await _apiService.getCollectionData>( + endpoint: '${ApiEndpoint.api(AppEndpoint.search)}/?s=$query&language_code=$languageCode', + converter: (response) { + return List.from( + response.data.map((game) { + return PostModel.fromJson(game); + }), + ); + }, + ); + } + + @override + Future sendComment({required Map data}) async { + return await _apiService.setData( + endpoint: ApiEndpoint.api(AppEndpoint.sendComment), + converter: (response) {}, + data: data, + ); + } +} diff --git a/data/data_types/data/lib/app_setting_data/repository/app_setting_box_repository_impl.dart b/data/data_types/data/lib/app_setting_data/repository/app_setting_box_repository_impl.dart index 40735b8..4c590b2 100644 --- a/data/data_types/data/lib/app_setting_data/repository/app_setting_box_repository_impl.dart +++ b/data/data_types/data/lib/app_setting_data/repository/app_setting_box_repository_impl.dart @@ -99,4 +99,14 @@ class AppSettingBoxRepositoryImpl extends AppSettingBoxRepository { } return utf8.decode(base64Url.decode(output)); } + + @override + Future getUserToken() async { + return await _appSettingBox.getUserToken(); + } + + @override + Future setUserToken(Map data) async { + return await _appSettingBox.setUserToken(data); + } } diff --git a/data/data_types/data/pubspec.yaml b/data/data_types/data/pubspec.yaml index 6ab0788..bdf6d1d 100644 --- a/data/data_types/data/pubspec.yaml +++ b/data/data_types/data/pubspec.yaml @@ -14,6 +14,8 @@ dependencies: path: ../../../domain/repositories local_db_core: path: ../../data_core/local_db/local_db_core + network_core: + path: ../../data_core/network/network_core dev_dependencies: flutter_test: diff --git a/domain/repositories/lib/app_api_domain/models/comment_model.dart b/domain/repositories/lib/app_api_domain/models/comment_model.dart new file mode 100644 index 0000000..8f07def --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/comment_model.dart @@ -0,0 +1,19 @@ +class CommentModel { + final String fullName; + final String createdDate; + final String text; + + const CommentModel({ + required this.text, + required this.createdDate, + required this.fullName, + }); + + factory CommentModel.fromJson(Map json) { + return CommentModel( + text: json['text'], + createdDate: json['created_date'], + fullName: json['full_name'], + ); + } +} diff --git a/domain/repositories/lib/app_api_domain/models/contact_us_model.dart b/domain/repositories/lib/app_api_domain/models/contact_us_model.dart new file mode 100644 index 0000000..3e5e1d3 --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/contact_us_model.dart @@ -0,0 +1,19 @@ +class ContactUsModel { + final String email; + final String text; + final String fullName; + + const ContactUsModel({ + required this.text, + required this.fullName, + required this.email, + }); + + Map toJson() { + return { + 'email': email, + 'full_name': fullName, + 'text': text, + }; + } +} diff --git a/domain/repositories/lib/app_api_domain/models/login_model.dart b/domain/repositories/lib/app_api_domain/models/login_model.dart new file mode 100644 index 0000000..fdc65ee --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/login_model.dart @@ -0,0 +1,22 @@ +class LoginModel { + final String language; + final String mobileDeviceId; + final String timeZone; + final String apiVersion; + + const LoginModel({ + required this.language, + required this.apiVersion, + required this.mobileDeviceId, + required this.timeZone, + }); + + Map toJson() { + return { + 'language': language, + 'mobile_device_id': mobileDeviceId, + 'timezone': timeZone, + 'api_version': apiVersion, + }; + } +} diff --git a/domain/repositories/lib/app_api_domain/models/login_response_model.dart b/domain/repositories/lib/app_api_domain/models/login_response_model.dart new file mode 100644 index 0000000..66c5aa2 --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/login_response_model.dart @@ -0,0 +1,13 @@ +class LoginResponseModel { + final String token; + + const LoginResponseModel({required this.token}); + + factory LoginResponseModel.fromJson(Map json) { + return LoginResponseModel(token: json['token']); + } + + Map toJson() { + return {'token': token}; + } +} diff --git a/domain/repositories/lib/app_api_domain/models/post_model.dart b/domain/repositories/lib/app_api_domain/models/post_model.dart new file mode 100644 index 0000000..661f085 --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/post_model.dart @@ -0,0 +1,43 @@ +class PostModel { + final int id; + final String title; + final String content; + final String summary; + final String publishDate; + final String? thumbnail; + final String languageCode; + final int viewCount; + final int commentCount; + final String websiteUrl; + final bool isPinned; + + const PostModel({ + required this.title, + required this.viewCount, + required this.commentCount, + required this.websiteUrl, + required this.content, + required this.id, + required this.summary, + required this.isPinned, + required this.languageCode, + required this.publishDate, + required this.thumbnail, + }); + + factory PostModel.fromJson(Map json) { + return PostModel( + id: json['id'], + title: json['title'], + content: json['content'], + commentCount: json['comm_count'], + viewCount: json['view_count'], + summary: json['summery'], + publishDate: json['publish_date'][0], + websiteUrl: json['website_url'], + thumbnail: json['thumbnail'] ?? '', + languageCode: json['language_code']['slug'], + isPinned: json['is_pinned'], + ); + } +} diff --git a/domain/repositories/lib/app_api_domain/models/send_comment_model.dart b/domain/repositories/lib/app_api_domain/models/send_comment_model.dart new file mode 100644 index 0000000..0339f69 --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/send_comment_model.dart @@ -0,0 +1,19 @@ +class SendCommentModel { + final int postId; + final String text; + final String fullName; + + const SendCommentModel({ + required this.text, + required this.fullName, + required this.postId, + }); + + Map toJson() { + return { + 'post_id': postId, + 'full_name': fullName, + 'text': text, + }; + } +} diff --git a/domain/repositories/lib/app_api_domain/models/single_post_model.dart b/domain/repositories/lib/app_api_domain/models/single_post_model.dart new file mode 100644 index 0000000..b7e98e6 --- /dev/null +++ b/domain/repositories/lib/app_api_domain/models/single_post_model.dart @@ -0,0 +1,45 @@ +import 'package:repositories/app_api_domain/models/comment_model.dart'; + +class SinglePostModel { + final int id; + final String title; + final String summary; + final String publishDate; + final String? thumbnail; + final String languageCode; + final String websiteUrl; + final int viewCount; + final int commentCount; + final List comments; + final String content; + + const SinglePostModel({ + required this.title, + required this.websiteUrl, + required this.content, + required this.id, + required this.comments, + required this.summary, + required this.languageCode, + required this.publishDate, + required this.thumbnail, + required this.commentCount, + required this.viewCount, + }); + + factory SinglePostModel.fromJson(Map json) { + return SinglePostModel( + id: json['id'], + websiteUrl: json['website_url'], + title: json['title'], + content: json['content'], + summary: json['summery'], + publishDate: json['publish_date'][0], + thumbnail: json['thumbnail'], + languageCode: json['language_code']['slug'], + comments: List.from(json['comments'].map((post) => CommentModel.fromJson(post))), + commentCount: json['comm_count'], + viewCount: json['view_count'], + ); + } +} diff --git a/domain/repositories/lib/app_api_domain/repository/app_api_repository.dart b/domain/repositories/lib/app_api_domain/repository/app_api_repository.dart new file mode 100644 index 0000000..b199090 --- /dev/null +++ b/domain/repositories/lib/app_api_domain/repository/app_api_repository.dart @@ -0,0 +1,23 @@ +import 'package:repositories/app_api_domain/models/login_response_model.dart'; +import 'package:repositories/app_api_domain/models/post_model.dart'; +import 'package:repositories/app_api_domain/models/single_post_model.dart'; +import 'package:repositories/typedefs.dart'; + +abstract class AppApiRepository { + Future login({required JSON data}); + + Future> getPostList({ + required String catId, + required String sort, + required int page, + required String languageCode, + }); + + Future getSinglePost({required int postId}); + + Future sendComment({required JSON data}); + + Future> searchPost({required String query, required String languageCode}); + + Future contactUs({required JSON data}); +} diff --git a/domain/repositories/lib/app_setting_box_domain/repository/app_setting_box_repository.dart b/domain/repositories/lib/app_setting_box_domain/repository/app_setting_box_repository.dart index 029e43e..7cf74e4 100644 --- a/domain/repositories/lib/app_setting_box_domain/repository/app_setting_box_repository.dart +++ b/domain/repositories/lib/app_setting_box_domain/repository/app_setting_box_repository.dart @@ -18,4 +18,8 @@ abstract class AppSettingBoxRepository { Future isUserLogin(); Future getUserId(); + + Future setUserToken(Map data); + + Future getUserToken(); } diff --git a/lib/core/select_language/cubit/select_language_cubit.dart b/lib/core/select_language/cubit/select_language_cubit.dart index 41de527..b12fe3e 100644 --- a/lib/core/select_language/cubit/select_language_cubit.dart +++ b/lib/core/select_language/cubit/select_language_cubit.dart @@ -1,34 +1,63 @@ +import 'package:data/app_api_data/app_api_repository_impl.dart'; +import 'package:data/app_setting_data/repository/app_setting_box_repository_impl.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:sonnat/core/language/language_cubit.dart'; +import 'package:local_db_core/lib/boxes/box_list/setting_box/app_setting_box.dart'; +import 'package:network_core/initializer/base_initializer.dart'; +import 'package:network_core/network/networking/custom_exception.dart'; +import 'package:repositories/app_api_domain/models/login_model.dart'; +import 'package:repositories/app_api_domain/models/login_response_model.dart'; +import 'package:repositories/app_api_domain/repository/app_api_repository.dart'; +import 'package:repositories/app_setting_box_domain/repository/app_setting_box_repository.dart'; import 'package:sonnat/core/language/languages.dart'; import 'package:sonnat/core/language/translator.dart'; +import 'package:sonnat/core/utils/app_utils.dart'; import 'package:sonnat/core/utils/base_cubit_type.dart'; class SelectLanguageCubit extends Cubit> { + final AppSettingBoxRepository _repository = AppSettingBoxRepositoryImpl(appSettingBox: AppSettingBox()); + final AppApiRepository _remoteRepository = AppApiRepositoryImpl(apiService: BaseInitializer.instance.getApiService()); SelectLanguageCubit() : super(BaseCubitType(eventName: SelectLanguageState.empty)); void empty() => emit(BaseCubitType(eventName: SelectLanguageState.empty)); - Future selectLanguage(String language) async { + void selectLanguage(String language) { switch (language) { case 'fa': - await Translator.setNewLanguage(Languages.fa); - emit(BaseCubitType(eventName: SelectLanguageState.loaded, data: language)); + login(Languages.fa); return; case 'en': - await Translator.setNewLanguage(Languages.en); - emit(BaseCubitType(eventName: SelectLanguageState.loaded, data: language)); + login(Languages.en); return; case 'ar': - await Translator.setNewLanguage(Languages.ar); - emit(BaseCubitType(eventName: SelectLanguageState.loaded, data: language)); + login(Languages.ar); return; } } + + Future login(Languages newLanguage) async { + await Translator.setNewLanguage(newLanguage); + await _repository.setCurrentLanguage(newLanguage.value); + String? deviceId = await Utils.instance.getId(); + deviceId ??= 'user-device'; + LoginModel loginModel = LoginModel( + language: newLanguage.value, + apiVersion: '1', + mobileDeviceId: deviceId, + timeZone: '${DateTime.now().timeZoneOffset.inHours}:${DateTime.now().timeZoneOffset.inMinutes}', + ); + try { + LoginResponseModel responseModel = await _remoteRepository.login(data: loginModel.toJson()); + await _repository.setUserToken(responseModel.toJson()); + emit(BaseCubitType(eventName: SelectLanguageState.loaded, data: newLanguage.value)); + } on CustomException { + emit(BaseCubitType(eventName: SelectLanguageState.error)); + } + } } enum SelectLanguageState { empty, loaded, + error, } diff --git a/lib/core/utils/app_utils.dart b/lib/core/utils/app_utils.dart index 00974e7..80827a0 100644 --- a/lib/core/utils/app_utils.dart +++ b/lib/core/utils/app_utils.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:shamsi_date/shamsi_date.dart'; @@ -115,4 +118,17 @@ class Utils { Jalali jalali = Jalali.fromDateTime(DateTime.fromMillisecondsSinceEpoch(dateInMilliseconds)); return '${jalali.year}/${jalali.month}/${jalali.day}'; } + + Future getId() async { + var deviceInfo = DeviceInfoPlugin(); + if (Platform.isIOS) { + var iosDeviceInfo = await deviceInfo.iosInfo; + return iosDeviceInfo.identifierForVendor; // unique ID on iOS + } + if (Platform.isAndroid) { + var androidDeviceInfo = await deviceInfo.androidInfo; + return androidDeviceInfo.androidId; // unique ID on Android + } + return null; + } } diff --git a/lib/features/about_us/about_us_screen.dart b/lib/features/about_us/about_us_screen.dart index d1b4585..9313be9 100644 --- a/lib/features/about_us/about_us_screen.dart +++ b/lib/features/about_us/about_us_screen.dart @@ -41,6 +41,7 @@ class _AboutUsScreenState extends State { fontSize: 16, fontWeight: FontWeight.bold, ), + ), ), ), @@ -173,6 +174,7 @@ class _AboutUsScreenState extends State { style: const TextStyle(color: Color(0xffffffff), fontSize: 12), ), ), + SizedBox(height: context.height * 9 / AppConstants.instance.appHeight), ], ), ), diff --git a/lib/features/main/main_screen.dart b/lib/features/main/main_screen.dart index 34469f5..70da8a1 100644 --- a/lib/features/main/main_screen.dart +++ b/lib/features/main/main_screen.dart @@ -4,11 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:sonnat/core/extensions/context_extension.dart'; import 'package:sonnat/core/extensions/string_extension.dart'; import 'package:sonnat/core/language/translator.dart'; -import 'package:sonnat/core/select_language/cubit/select_language_cubit.dart'; -import 'package:sonnat/core/select_language/screen/select_language_screen.dart'; import 'package:sonnat/core/utils/app_constants.dart'; -import 'package:sonnat/core/widgets/more_options_widget.dart'; -import 'package:sonnat/features/about_us/about_us_screen.dart'; import 'package:sonnat/features/main/widget/main_item_widget.dart'; import 'package:sonnat/features/posts/cubit/posts_cubit.dart'; import 'package:sonnat/features/posts/screen/posts_screen.dart'; @@ -29,13 +25,13 @@ class _MainScreenState extends State { 'ic_akhbar', 'ic_video', ]; - final List _names = [ - Translator.translate('forbidden'), - Translator.translate('doubts'), - Translator.translate('criticism_of_ideas'), - Translator.translate('specials'), - Translator.translate('news'), - Translator.translate('video'), + final List> _names = [ + {'name': Translator.translate('forbidden'), 'catId': '77,78,79'}, + {'name': Translator.translate('doubts'), 'catId': '83'}, + {'name': Translator.translate('criticism_of_ideas'), 'catId': '101'}, + {'name': Translator.translate('specials'), 'catId': '109'}, + {'name': Translator.translate('news'), 'catId': '104'}, + {'name': Translator.translate('video'), 'catId': ''}, ]; @override @@ -45,31 +41,16 @@ class _MainScreenState extends State { body: SafeArea( child: Column( children: [ - Stack( - children: [ - Container( - margin: EdgeInsets.only( - left: context.width * 86 / AppConstants.instance.appWidth, - right: context.width * 86 / AppConstants.instance.appWidth, - ), - child: Image.asset( - 'ic_main_header'.pngPath, - width: context.width * 200 / AppConstants.instance.appWidth, - height: context.height * 200 / AppConstants.instance.appHeight, - ), - ), - PositionedDirectional( - end: context.width * 35 / AppConstants.instance.appWidth, - top: 16, - child: Align( - alignment: AlignmentDirectional.centerStart, - child: GestureDetector( - onTap: _showOptions, - child: SvgPicture.asset('ic_more'.svgPath), - ), - ), - ), - ], + Container( + margin: EdgeInsets.only( + left: context.width * 86 / AppConstants.instance.appWidth, + right: context.width * 86 / AppConstants.instance.appWidth, + ), + child: Image.asset( + 'ic_main_header'.pngPath, + width: context.width * 200 / AppConstants.instance.appWidth, + height: context.height * 200 / AppConstants.instance.appHeight, + ), ), Text( Translator.translate('main_header_text'), @@ -87,14 +68,17 @@ class _MainScreenState extends State { ), GestureDetector( onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) { - return BlocProvider( - create: (context) => PostsCubit(), - child: const PostsScreen(title: '', searchMode: true), - ); - }, - )); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return BlocProvider( + create: (context) => PostsCubit(lastCatId: '77,78,79'), + child: const PostsScreen(title: '', searchMode: true), + ); + }, + ), + ); }, child: Container( decoration: BoxDecoration( @@ -133,17 +117,17 @@ class _MainScreenState extends State { child: Row( children: [ GestureDetector( - child: MainItemWidget(icon: _icons[2], name: _names[2]), + child: MainItemWidget(icon: _icons[2], name: _names[2]['name']!), onTap: () => _openItem(index: 2), ), SizedBox(width: context.width * 13 / AppConstants.instance.appWidth), GestureDetector( - child: MainItemWidget(icon: _icons[1], name: _names[1]), + child: MainItemWidget(icon: _icons[1], name: _names[1]['name']!), onTap: () => _openItem(index: 1), ), SizedBox(width: context.width * 13 / AppConstants.instance.appWidth), GestureDetector( - child: MainItemWidget(icon: _icons[0], name: _names[0]), + child: MainItemWidget(icon: _icons[0], name: _names[0]['name']!), onTap: () => _openItem(index: 0), ), ], @@ -155,17 +139,17 @@ class _MainScreenState extends State { child: Row( children: [ GestureDetector( - child: MainItemWidget(icon: _icons[5], name: _names[5]), + child: MainItemWidget(icon: _icons[5], name: _names[5]['name']!), onTap: () => _openItem(index: 5), ), SizedBox(width: context.width * 13 / AppConstants.instance.appWidth), GestureDetector( - child: MainItemWidget(icon: _icons[4], name: _names[4]), + child: MainItemWidget(icon: _icons[4], name: _names[4]['name']!), onTap: () => _openItem(index: 4), ), SizedBox(width: context.width * 13 / AppConstants.instance.appWidth), GestureDetector( - child: MainItemWidget(icon: _icons[3], name: _names[3]), + child: MainItemWidget(icon: _icons[3], name: _names[3]['name']!), onTap: () => _openItem(index: 3), ), ], @@ -179,81 +163,19 @@ class _MainScreenState extends State { } void _openItem({required int index}) { - Navigator.push(context, MaterialPageRoute( - builder: (context) { - return BlocProvider( - create: (context) => PostsCubit(), - child: PostsScreen(title: _names[index]), - ); - }, - )); - } - - void _showOptions() { - showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - builder: (context) { - return Container( - height: 120, - width: context.width, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.white, width: 0.2), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return BlocProvider( + create: (context) => PostsCubit(lastCatId: _names[index]['catId']!), + child: PostsScreen( + title: _names[index]['name']!, + showFilters: index == 0, ), - ), - padding: const EdgeInsets.only( - top: 16, - left: 4, - right: 4, - ), - child: Column( - children: [ - MoreOptionsWidget( - title: Translator.translate('about_us'), - onTap: () { - Navigator.pop(context); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const AboutUsScreen(); - }, - ), - ); - }, - ), - const SizedBox(height: 8), - Container( - width: context.width, - height: 1, - color: const Color(0xffD3D8E9), - ), - const SizedBox(height: 8), - MoreOptionsWidget( - title: Translator.translate('select_language'), - onTap: () { - Navigator.pop(context); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return BlocProvider( - child: const SelectLanguageScreen(), - create: (context) => SelectLanguageCubit(), - ); - }, - ), - ); - }, - ), - ], - ), - ); - }, + ); + }, + ), ); } } diff --git a/lib/features/posts/cubit/posts_cubit.dart b/lib/features/posts/cubit/posts_cubit.dart index 0498160..c1becef 100644 --- a/lib/features/posts/cubit/posts_cubit.dart +++ b/lib/features/posts/cubit/posts_cubit.dart @@ -1,24 +1,37 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:math'; -import 'package:flutter/services.dart'; +import 'package:data/app_api_data/app_api_repository_impl.dart'; +import 'package:data/app_setting_data/repository/app_setting_box_repository_impl.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:local_db_core/lib/boxes/box_list/setting_box/app_setting_box.dart'; +import 'package:network_core/initializer/base_initializer.dart'; +import 'package:repositories/app_api_domain/models/post_model.dart'; +import 'package:repositories/app_api_domain/repository/app_api_repository.dart'; +import 'package:repositories/app_setting_box_domain/repository/app_setting_box_repository.dart'; import 'package:sonnat/core/utils/base_cubit_type.dart'; -import 'package:sonnat/features/single_post/view_models/post.dart'; class PostsCubit extends Cubit> { - List allData = []; - List postList = []; - List searchedList = []; + List postList = []; + List searchedList = []; bool hasReachedMax = false; String _query = ''; + String lastCatId = ''; + int _page = 1; + final AppApiRepository _remoteRepository = AppApiRepositoryImpl(apiService: BaseInitializer.instance.getApiService()); + final AppSettingBoxRepository _repository = AppSettingBoxRepositoryImpl(appSettingBox: AppSettingBox()); - PostsCubit() : super(BaseCubitType(eventName: PostsState.empty)); + PostsCubit({required this.lastCatId}) : super(BaseCubitType(eventName: PostsState.empty)); void empty() => emit(BaseCubitType(eventName: PostsState.empty)); - Future getData() async { + Future getData({required String catId}) async { + if(lastCatId != catId) { + lastCatId = catId; + hasReachedMax = false; + _page = 1; + postList.clear(); + } if (hasReachedMax) { return; } @@ -27,25 +40,21 @@ class PostsCubit extends Cubit> { } else { emit(BaseCubitType(eventName: PostsState.loading)); } - String data = await rootBundle.loadString('assets/meta/data.json'); - allData = List.from( - jsonDecode(data)['posts'].map( - (post) => Post.fromJson(post).copyWith(DateTime.now().millisecondsSinceEpoch), - ), + List data = await _remoteRepository.getPostList( + catId: catId, + sort: 'newest', + page: _page, + languageCode: _repository.getCurrentLanguage() ?? 'en', ); - if (allData.isNotEmpty) { - List posts = allData.getRange(postList.length, min(postList.length + 20, allData.length)).toList(); - hasReachedMax = posts.length < 20; - postList += posts; - emit(BaseCubitType(eventName: PostsState.data)); - } else { - emit(BaseCubitType(eventName: PostsState.data)); - } + hasReachedMax = data.length < 15; + _page += 1; + postList.addAll(data); + emit(BaseCubitType(eventName: PostsState.data)); } Future search({required String query}) async { if (query.isEmpty) { - unawaited(getData()); + unawaited(getData(catId: lastCatId)); _query = ''; searchedList.clear(); emit(BaseCubitType(eventName: PostsState.data)); @@ -65,37 +74,21 @@ class PostsCubit extends Cubit> { emit(BaseCubitType(eventName: PostsState.loading)); } await Future.delayed(Duration(milliseconds: Random().nextInt(1000))); - String data = await rootBundle.loadString('assets/meta/data.json'); - allData = List.from( - jsonDecode(data)['posts'].map( - (post) => Post.fromJson(post).copyWith( - DateTime.now().millisecondsSinceEpoch, - ), - ), - ); - if (allData.isNotEmpty) { - List posts = allData.where((element) => element.name.contains(query.trim())).toList(); - hasReachedMax = posts.length < 20; - searchedList += posts; - emit(BaseCubitType(eventName: PostsState.data)); - } else { - emit(BaseCubitType(eventName: PostsState.data)); - } } String get query => _query; void clearSearch() { - unawaited(getData()); + unawaited(getData(catId: lastCatId)); _query = ''; searchedList.clear(); emit(BaseCubitType(eventName: PostsState.data)); } - Future changeFilter(int index) async { + Future changeFilter(int index, String catId) async { emit(BaseCubitType(eventName: PostsState.changeFilterIndex, data: index)); - await Future.delayed(Duration(milliseconds: Random().nextInt(1000))); - await getData(); + await Future.delayed(Duration(milliseconds: 500)); + await getData(catId: catId); } } diff --git a/lib/features/posts/screen/posts_screen.dart b/lib/features/posts/screen/posts_screen.dart index 9592c87..10839d5 100644 --- a/lib/features/posts/screen/posts_screen.dart +++ b/lib/features/posts/screen/posts_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:repositories/app_api_domain/models/post_model.dart'; import 'package:sonnat/core/extensions/context_extension.dart'; import 'package:sonnat/core/extensions/string_extension.dart'; import 'package:sonnat/core/language/translator.dart'; @@ -18,13 +19,18 @@ import 'package:sonnat/features/posts/widgets/post_item_widget.dart'; import 'package:sonnat/features/posts/widgets/search_widget.dart'; import 'package:sonnat/features/single_post/cubit/single_post_cubit.dart'; import 'package:sonnat/features/single_post/screen/single_post_screen.dart'; -import 'package:sonnat/features/single_post/view_models/post.dart'; class PostsScreen extends StatefulWidget { final String title; final bool searchMode; + final bool showFilters; - const PostsScreen({super.key, required this.title, this.searchMode = false}); + const PostsScreen({ + super.key, + required this.title, + this.searchMode = false, + this.showFilters = false, + }); @override State createState() => _PostsScreenState(); @@ -36,26 +42,19 @@ class _PostsScreenState extends State { bool _getListFlag = false; late bool _searchMode; final ScrollController _controller = ScrollController(); - int _selectedFilterIndex = 1; + int _selectedFilterIndex = -1; final List _filterList = [ - const FilterItem(selected: false, title: 'پر تکرارترین‌ها'), - const FilterItem(selected: true, title: 'مشابه‌ها'), - const FilterItem(selected: false, title: 'محبوبترین‌ها'), - const FilterItem(selected: false, title: 'برای‌ شما'), - const FilterItem(selected: false, title: 'محبوبترین‌ها'), - const FilterItem(selected: false, title: 'پر تکرارترین‌ها'), - const FilterItem(selected: true, title: 'مشابه‌ها'), - const FilterItem(selected: false, title: 'محبوبترین‌ها'), - const FilterItem(selected: false, title: 'برای‌ شما'), - const FilterItem(selected: false, title: 'محبوبترین‌ها'), + const FilterItem(selected: false, title: 'ابوبکر', catId: '77'), + const FilterItem(selected: true, title: 'عمر', catId: '78'), + const FilterItem(selected: false, title: 'عثمان', catId: '79'), ]; @override void initState() { _searchMode = widget.searchMode; _cubit = BlocProvider.of(context); - _cubit.getData(); + _cubit.getData(catId: _cubit.lastCatId); _controller.addListener(_onScroll); super.initState(); } @@ -66,7 +65,7 @@ class _PostsScreenState extends State { if (maxScroll - currentScroll <= 400) { if (_getListFlag) { _getListFlag = false; - _cubit.getData(); + _cubit.getData(catId: _cubit.lastCatId); } } } @@ -91,6 +90,7 @@ class _PostsScreenState extends State { break; case PostsState.changeFilterIndex: _selectedFilterIndex = state.data; + _controller.jumpTo(0); _loading = true; break; } @@ -143,9 +143,7 @@ class _PostsScreenState extends State { alignment: AlignmentDirectional.centerStart, child: GestureDetector( onTap: _showOptions, - child: SvgPicture.asset( - 'ic_more'.svgPath, - ), + child: SvgPicture.asset('ic_more'.svgPath), ), ), PositionedDirectional( @@ -242,27 +240,28 @@ class _PostsScreenState extends State { ), ), SizedBox(height: context.height * 35 / AppConstants.instance.appHeight), - SizedBox( - height: context.height * 31 / AppConstants.instance.appHeight, - child: ListView.builder( - padding: EdgeInsetsDirectional.only( - start: context.width * 26 / AppConstants.instance.appWidth, + if (widget.showFilters) + SizedBox( + height: context.height * 31 / AppConstants.instance.appHeight, + child: ListView.builder( + padding: EdgeInsetsDirectional.only( + start: context.width * 26 / AppConstants.instance.appWidth, + ), + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + _cubit.changeFilter(index, _filterList[index].catId); + }, + child: FilterItemWidget( + title: _filterList[index].title, + selected: index == _selectedFilterIndex, + ), + ); + }, + itemCount: _filterList.length, + scrollDirection: Axis.horizontal, ), - itemBuilder: (context, index) { - return GestureDetector( - onTap: () { - _cubit.changeFilter(index); - }, - child: FilterItemWidget( - title: _filterList[index].title, - selected: index == _selectedFilterIndex, - ), - ); - }, - itemCount: _filterList.length, - scrollDirection: Axis.horizontal, ), - ), SizedBox(height: context.height * 26 / AppConstants.instance.appHeight), ], ), @@ -303,13 +302,13 @@ class _PostsScreenState extends State { ); } - void _clickOnPost(Post post) { + void _clickOnPost(PostModel post) { Navigator.push( context, MaterialPageRoute( builder: (context) { return BlocProvider( - child: SinglePostScreen(post: post), + child: SinglePostScreen(postId: post.id), create: (context) => SinglePostCubit(), ); }, @@ -389,6 +388,7 @@ class _PostsScreenState extends State { class FilterItem { final bool selected; final String title; + final String catId; - const FilterItem({required this.selected, required this.title}); + const FilterItem({required this.selected, required this.title, required this.catId}); } diff --git a/lib/features/posts/widgets/post_item_widget.dart b/lib/features/posts/widgets/post_item_widget.dart index 763ae22..9064957 100644 --- a/lib/features/posts/widgets/post_item_widget.dart +++ b/lib/features/posts/widgets/post_item_widget.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:repositories/app_api_domain/models/post_model.dart'; import 'package:sonnat/core/extensions/context_extension.dart'; import 'package:sonnat/core/utils/app_constants.dart'; import 'package:sonnat/core/utils/app_utils.dart'; -import 'package:sonnat/features/single_post/view_models/post.dart'; class PostItemWidget extends StatelessWidget { - final Post post; + final PostModel post; const PostItemWidget({super.key, required this.post}); @@ -36,14 +36,14 @@ class PostItemWidget extends StatelessWidget { topRight: Radius.circular(16), ), image: DecorationImage( - image: AssetImage(post.image), + image: NetworkImage(post.thumbnail!), fit: BoxFit.cover, ), ), ), SizedBox(height: context.height * 14 / AppConstants.instance.appHeight), Text( - post.name, + post.title, style: const TextStyle( color: Color(0xff222D4E), fontSize: 16, @@ -52,7 +52,7 @@ class PostItemWidget extends StatelessWidget { ), SizedBox(height: context.height * 8 / AppConstants.instance.appHeight), Text( - post.description, + post.summary, style: const TextStyle( color: Color(0xff8990A1), fontSize: 13, @@ -63,7 +63,7 @@ class PostItemWidget extends StatelessWidget { Align( alignment: AlignmentDirectional.centerEnd, child: Text( - Utils.instance.dateToString(post.date), + post.publishDate, style: const TextStyle( color: Color(0xff8D95AB), fontSize: 11, diff --git a/lib/features/single_post/cubit/single_post_cubit.dart b/lib/features/single_post/cubit/single_post_cubit.dart index 7f58661..d47d505 100644 --- a/lib/features/single_post/cubit/single_post_cubit.dart +++ b/lib/features/single_post/cubit/single_post_cubit.dart @@ -1,11 +1,18 @@ import 'dart:math'; +import 'package:data/app_api_data/app_api_repository_impl.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:network_core/initializer/base_initializer.dart'; +import 'package:network_core/network/networking/custom_exception.dart'; +import 'package:repositories/app_api_domain/models/single_post_model.dart'; +import 'package:repositories/app_api_domain/repository/app_api_repository.dart'; import 'package:sonnat/core/utils/base_cubit_type.dart'; import 'package:sonnat/features/single_post/view_models/comment.dart'; class SinglePostCubit extends Cubit> { List commentList = []; + SinglePostModel? model; + final AppApiRepository _remoteRepository = AppApiRepositoryImpl(apiService: BaseInitializer.instance.getApiService()); SinglePostCubit() : super(BaseCubitType(eventName: SinglePostState.empty)); @@ -26,9 +33,21 @@ class SinglePostCubit extends Cubit> { commentList.removeWhere((element) => element.id == id); emit(BaseCubitType(eventName: SinglePostState.data)); } + + Future getSinglePostData({required int postId}) async { + try { + emit(BaseCubitType(eventName: SinglePostState.loading)); + model = await _remoteRepository.getSinglePost(postId: postId); + emit(BaseCubitType(eventName: SinglePostState.data)); + } on CustomException { + emit(BaseCubitType(eventName: SinglePostState.error)); + } + } } enum SinglePostState { empty, + loading, + error, data, } diff --git a/lib/features/single_post/screen/single_post_screen.dart b/lib/features/single_post/screen/single_post_screen.dart index 579c9bc..75489f0 100644 --- a/lib/features/single_post/screen/single_post_screen.dart +++ b/lib/features/single_post/screen/single_post_screen.dart @@ -9,14 +9,13 @@ import 'package:sonnat/core/utils/app_constants.dart'; import 'package:sonnat/core/utils/app_utils.dart'; import 'package:sonnat/core/utils/base_cubit_type.dart'; import 'package:sonnat/features/single_post/cubit/single_post_cubit.dart'; -import 'package:sonnat/features/single_post/view_models/post.dart'; import 'package:sonnat/features/single_post/widget/add_comment_widget.dart'; import 'package:sonnat/features/single_post/widget/post_comment_widget.dart'; class SinglePostScreen extends StatefulWidget { - final Post post; + final int postId; - const SinglePostScreen({super.key, required this.post}); + const SinglePostScreen({super.key, required this.postId}); @override State createState() => _SinglePostScreenState(); @@ -24,10 +23,12 @@ class SinglePostScreen extends StatefulWidget { class _SinglePostScreenState extends State { late final SinglePostCubit _cubit; + bool _loading = true; @override void initState() { _cubit = BlocProvider.of(context); + _cubit.getSinglePostData(postId: widget.postId); super.initState(); } @@ -41,7 +42,17 @@ class _SinglePostScreenState extends State { case SinglePostState.empty: break; case SinglePostState.data: + _loading = false; break; + case SinglePostState.loading: + _loading = true; + break; + case SinglePostState.error: + _loading = false; + break; + } + if(_loading) { + return const Center(child: CircularProgressIndicator()); } return SingleChildScrollView( child: Column( @@ -51,8 +62,8 @@ class _SinglePostScreenState extends State { child: Stack( fit: StackFit.expand, children: [ - Image.asset( - widget.post.image, + Image.network( + _cubit.model!.thumbnail ?? '', fit: BoxFit.cover, ), PositionedDirectional( @@ -152,18 +163,18 @@ class _SinglePostScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.post.name, + _cubit.model!.title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( - Utils.instance.dateToString(widget.post.date), + Utils.instance.dateToString(DateTime.parse(_cubit.model!.publishDate).millisecondsSinceEpoch), style: const TextStyle(fontSize: 11), ), HTMLViewer( - htmlContent: widget.post.description, + htmlContent: _cubit.model!.content, fontSizeFactor: 1, ), const SizedBox(height: 30), diff --git a/lib/features/splash/cubit/splash_cubit.dart b/lib/features/splash/cubit/splash_cubit.dart index 9ab2599..3168d9f 100644 --- a/lib/features/splash/cubit/splash_cubit.dart +++ b/lib/features/splash/cubit/splash_cubit.dart @@ -13,7 +13,7 @@ class SplashCubit extends Cubit> { Future checkLanguageSet() async { String? language = _repository.getCurrentLanguage(); - if(language == null || language == '') { + if (language == null || language == '') { emit(BaseCubitType(eventName: SplashCubitState.notSet)); } else { emit(BaseCubitType(eventName: SplashCubitState.set)); diff --git a/pubspec.lock b/pubspec.lock index 0b55be2..de994c5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -168,6 +168,70 @@ packages: relative: true source: path version: "0.0.1" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: c2386729379f04cd39ee0d5d4c48d8c8a0e70f7622dac626cbf5e396392602fd + url: "https://pub.dev" + source: hosted + version: "3.2.4" + device_info_plus_linux: + dependency: transitive + description: + name: device_info_plus_linux + sha256: e4eb5db4704f5534e872148a21cfcd39581022b63df556da6720d88f7c9f91a9 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + device_info_plus_macos: + dependency: transitive + description: + name: device_info_plus_macos + sha256: "38871fd2ad31871399d8307630c9f4eb5941dd2c643ee221c44d58de95d367a1" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: b2743934f0efc3e291880d76fb341ea114b7e8417d77ee0f93bd21f5dfd3e8d2 + url: "https://pub.dev" + source: hosted + version: "2.6.1" + device_info_plus_web: + dependency: transitive + description: + name: device_info_plus_web + sha256: "38715ad1ef3bee8915dd7bee08a9ac9ab54472a8df425c887062a3046209f663" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + device_info_plus_windows: + dependency: transitive + description: + name: device_info_plus_windows + sha256: "8fb1403fc94636d6ab48aeebb5f9379f2ca51cde3b337167ec6f39db09234492" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dio: + dependency: transitive + description: + name: dio + sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + url: "https://pub.dev" + source: hosted + version: "4.0.6" + dio_cache_interceptor: + dependency: transitive + description: + name: dio_cache_interceptor + sha256: a89166e6fb9c90a4bf2b7f20b5c055087969bc445ced3282e32505543e296e0f + url: "https://pub.dev" + source: hosted + version: "3.4.2" fake_async: dependency: transitive description: @@ -180,10 +244,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "13a6ccf6a459a125b3fcdb6ec73bd5ff90822e071207c663bfd1f70062d51d18" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "1.2.1" file: dependency: transitive description: @@ -406,6 +470,13 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + network_core: + dependency: "direct main" + description: + path: "data/data_core/network/network_core" + relative: true + source: path + version: "0.0.1" octo_image: dependency: transitive description: @@ -474,10 +545,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 + sha256: a34ecd7fb548f8e57321fd8e50d865d266941b54e6c3b7758cf8f37c24116905 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.0.7" pedantic: dependency: transitive description: @@ -822,18 +893,18 @@ packages: dependency: transitive description: name: wakelock_windows - sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567" + sha256: "108b1b73711f1664ee462e73af34a9286ff496e27d4d8371e2fb4da8fde4cdac" url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.0" win32: dependency: transitive description: name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + sha256: c0e3a4f7be7dae51d8f152230b86627e3397c1ba8c3fa58e63d44a9f3edc9cef url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "2.6.1" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c6d2f05..e2aaf5a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,7 @@ dependencies: list_counter: ^1.0.2 shamsi_date: ^1.0.1 url_launcher: ^6.1.11 + device_info_plus: ^3.2.3 cached_network_image: ^3.1.0 google_fonts: ^4.0.4 chewie_audio: ^1.5.0 @@ -31,6 +32,8 @@ dependencies: path: domain/repositories data: path: data/data_types/data + network_core: + path: data/data_core/network/network_core video_player: ^2.6.1 dev_dependencies: