Browse Source

fix some bug

fix_bug
mohsen 1 year ago
parent
commit
ff49c9f45e
  1. 17
      data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/app_setting_box.dart
  2. 1
      data/data_core/local_db/local_db_core/lib/lib/boxes/box_list/setting_box/box_keys.dart
  3. 34
      data/data_core/network/network_core/lib/initializer/gamification_initializer.dart
  4. 37
      data/data_core/network/network_core/lib/initializer/hamrah_cloud_initializer.dart
  5. 39
      data/data_core/network/network_core/lib/initializer/logger_initializer.dart
  6. 14
      data/data_core/network/network_core/lib/network/helper/config.dart
  7. 132
      data/data_core/network/network_core/lib/network/networking/api_endpoint.dart
  8. 5
      data/data_core/network/network_core/lib/network/networking/api_service.dart
  9. 22
      data/data_core/network/network_core/lib/network/networking/interceptors/api_interceptor.dart
  10. 86
      data/data_types/data/lib/app_api_data/app_api_repository_impl.dart
  11. 10
      data/data_types/data/lib/app_setting_data/repository/app_setting_box_repository_impl.dart
  12. 2
      data/data_types/data/pubspec.yaml
  13. 19
      domain/repositories/lib/app_api_domain/models/comment_model.dart
  14. 19
      domain/repositories/lib/app_api_domain/models/contact_us_model.dart
  15. 22
      domain/repositories/lib/app_api_domain/models/login_model.dart
  16. 13
      domain/repositories/lib/app_api_domain/models/login_response_model.dart
  17. 43
      domain/repositories/lib/app_api_domain/models/post_model.dart
  18. 19
      domain/repositories/lib/app_api_domain/models/send_comment_model.dart
  19. 45
      domain/repositories/lib/app_api_domain/models/single_post_model.dart
  20. 23
      domain/repositories/lib/app_api_domain/repository/app_api_repository.dart
  21. 4
      domain/repositories/lib/app_setting_box_domain/repository/app_setting_box_repository.dart
  22. 45
      lib/core/select_language/cubit/select_language_cubit.dart
  23. 16
      lib/core/utils/app_utils.dart
  24. 2
      lib/features/about_us/about_us_screen.dart
  25. 170
      lib/features/main/main_screen.dart
  26. 77
      lib/features/posts/cubit/posts_cubit.dart
  27. 80
      lib/features/posts/screen/posts_screen.dart
  28. 12
      lib/features/posts/widgets/post_item_widget.dart
  29. 19
      lib/features/single_post/cubit/single_post_cubit.dart
  30. 27
      lib/features/single_post/screen/single_post_screen.dart
  31. 2
      lib/features/splash/cubit/splash_cubit.dart
  32. 87
      pubspec.lock
  33. 3
      pubspec.yaml

17
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<BoxKeys> {
}
Future<UserDataModel?> getUserData() async {
if(!Hive.isBoxOpen(getBoxName())) {
if (!Hive.isBoxOpen(getBoxName())) {
await Hive.openBox(getBoxName());
}
Map<dynamic, dynamic>? data = await _box!.get(getKeyName(BoxKeys.userData));
@ -63,4 +63,19 @@ class AppSettingBox implements ConfigBoxInterface<BoxKeys> {
Future<void> setUserData(Map<String, dynamic> data) async {
await _box!.put(getKeyName(BoxKeys.userData), data);
}
Future<void> setUserToken(Map<String, dynamic> data) async {
await _box!.put(getKeyName(BoxKeys.token), data);
}
Future<String> getUserToken() async {
if (!Hive.isBoxOpen(getBoxName())) {
await Hive.openBox(getBoxName());
}
Map<dynamic, dynamic>? data = await _box!.get(getKeyName(BoxKeys.userData));
if (data == null) {
return 'token';
}
return data['token'];
}
}

1
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;

34
data/data_core/network/network_core/lib/initializer/gamification_initializer.dart

@ -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());
}
}

37
data/data_core/network/network_core/lib/initializer/hamrah_cloud_initializer.dart

@ -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());
}
}

39
data/data_core/network/network_core/lib/initializer/logger_initializer.dart

@ -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());
}
}

14
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/');
}

132
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);
}

5
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: <String, Object?>{
'requiresAuthToken': requiresAuthToken,
},
),
queryParams: queryParams,
cancelToken: cancelToken,
);

22
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<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
@ -18,19 +16,13 @@ class ApiInterceptor extends Interceptor {
'Content-Type': 'application/json',
'Accept': '*/*',
});
if (addAllowMethod) {
options.headers.addAll(<String, Object?>{
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE, HEAD',
'Access-Control-Allow-Origin': '*',
});
}
UserDataModel? userData = await _repository.getUserData();
options.headers.addAll(<String, Object?>{
'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(<String, Object?>{
'Authorization': 'Bearer ${userData.token}',
});
}
options.headers.addAll(<String, Object?>{'Authorization': '$token'});
}
}
options.extra.remove('requiresAuthToken');

86
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<void> contactUs({required Map<String, dynamic> data}) async {
return await _apiService.setData(
endpoint: ApiEndpoint.api(AppEndpoint.contactUs),
converter: (response) {},
data: data,
);
}
@override
Future<List<PostModel>> 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<PostModel>.from(
response.data['data'].map((game) {
return PostModel.fromJson(game);
}),
);
},
);
}
@override
Future<SinglePostModel> getSinglePost({required int postId}) async {
return await _apiService.getDocumentData<SinglePostModel>(
endpoint: '${ApiEndpoint.api(AppEndpoint.singlePost)}?post=$postId',
converter: (response) {
return SinglePostModel.fromJson(response);
},
);
}
@override
Future<LoginResponseModel> login({required Map<String, dynamic> data}) async {
return await _apiService.setData<LoginResponseModel>(
endpoint: ApiEndpoint.api(AppEndpoint.login),
converter: (response) {
return LoginResponseModel.fromJson(response.data);
},
queryParams: data,
requiresAuthToken: false,
);
}
@override
Future<List<PostModel>> searchPost({required String query, required String languageCode}) async {
return await _apiService.getCollectionData<List<PostModel>>(
endpoint: '${ApiEndpoint.api(AppEndpoint.search)}/?s=$query&language_code=$languageCode',
converter: (response) {
return List<PostModel>.from(
response.data.map((game) {
return PostModel.fromJson(game);
}),
);
},
);
}
@override
Future<void> sendComment({required Map<String, dynamic> data}) async {
return await _apiService.setData(
endpoint: ApiEndpoint.api(AppEndpoint.sendComment),
converter: (response) {},
data: data,
);
}
}

10
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<String> getUserToken() async {
return await _appSettingBox.getUserToken();
}
@override
Future<void> setUserToken(Map<String, dynamic> data) async {
return await _appSettingBox.setUserToken(data);
}
}

2
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:

19
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<String, dynamic> json) {
return CommentModel(
text: json['text'],
createdDate: json['created_date'],
fullName: json['full_name'],
);
}
}

19
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<String, dynamic> toJson() {
return {
'email': email,
'full_name': fullName,
'text': text,
};
}
}

22
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<String, dynamic> toJson() {
return {
'language': language,
'mobile_device_id': mobileDeviceId,
'timezone': timeZone,
'api_version': apiVersion,
};
}
}

13
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<String, dynamic> json) {
return LoginResponseModel(token: json['token']);
}
Map<String, dynamic> toJson() {
return {'token': token};
}
}

43
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<String, dynamic> 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'],
);
}
}

19
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<String, dynamic> toJson() {
return {
'post_id': postId,
'full_name': fullName,
'text': text,
};
}
}

45
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<CommentModel> 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<String, dynamic> 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<CommentModel>.from(json['comments'].map((post) => CommentModel.fromJson(post))),
commentCount: json['comm_count'],
viewCount: json['view_count'],
);
}
}

23
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<LoginResponseModel> login({required JSON data});
Future<List<PostModel>> getPostList({
required String catId,
required String sort,
required int page,
required String languageCode,
});
Future<SinglePostModel> getSinglePost({required int postId});
Future<void> sendComment({required JSON data});
Future<List<PostModel>> searchPost({required String query, required String languageCode});
Future<void> contactUs({required JSON data});
}

4
domain/repositories/lib/app_setting_box_domain/repository/app_setting_box_repository.dart

@ -18,4 +18,8 @@ abstract class AppSettingBoxRepository {
Future<bool> isUserLogin();
Future<String?> getUserId();
Future<void> setUserToken(Map<String, dynamic> data);
Future<String> getUserToken();
}

45
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<BaseCubitType<SelectLanguageState>> {
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<void> 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<void> 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,
}

16
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<String?> 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;
}
}

2
lib/features/about_us/about_us_screen.dart

@ -41,6 +41,7 @@ class _AboutUsScreenState extends State<AboutUsScreen> {
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
@ -173,6 +174,7 @@ class _AboutUsScreenState extends State<AboutUsScreen> {
style: const TextStyle(color: Color(0xffffffff), fontSize: 12),
),
),
SizedBox(height: context.height * 9 / AppConstants.instance.appHeight),
],
),
),

170
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<MainScreen> {
'ic_akhbar',
'ic_video',
];
final List<String> _names = [
Translator.translate('forbidden'),
Translator.translate('doubts'),
Translator.translate('criticism_of_ideas'),
Translator.translate('specials'),
Translator.translate('news'),
Translator.translate('video'),
final List<Map<String, String>> _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<MainScreen> {
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<MainScreen> {
),
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<MainScreen> {
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<MainScreen> {
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<MainScreen> {
}
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(),
);
},
),
);
},
),
],
),
);
},
);
},
),
);
}
}

77
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<BaseCubitType<PostsState>> {
List<Post> allData = [];
List<Post> postList = [];
List<Post> searchedList = [];
List<PostModel> postList = [];
List<PostModel> 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<void> getData() async {
Future<void> 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<BaseCubitType<PostsState>> {
} else {
emit(BaseCubitType(eventName: PostsState.loading));
}
String data = await rootBundle.loadString('assets/meta/data.json');
allData = List<Post>.from(
jsonDecode(data)['posts'].map(
(post) => Post.fromJson(post).copyWith(DateTime.now().millisecondsSinceEpoch),
),
List<PostModel> data = await _remoteRepository.getPostList(
catId: catId,
sort: 'newest',
page: _page,
languageCode: _repository.getCurrentLanguage() ?? 'en',
);
if (allData.isNotEmpty) {
List<Post> 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<void> 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<BaseCubitType<PostsState>> {
emit(BaseCubitType(eventName: PostsState.loading));
}
await Future.delayed(Duration(milliseconds: Random().nextInt(1000)));
String data = await rootBundle.loadString('assets/meta/data.json');
allData = List<Post>.from(
jsonDecode(data)['posts'].map(
(post) => Post.fromJson(post).copyWith(
DateTime.now().millisecondsSinceEpoch,
),
),
);
if (allData.isNotEmpty) {
List<Post> 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<void> changeFilter(int index) async {
Future<void> 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);
}
}

80
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<PostsScreen> createState() => _PostsScreenState();
@ -36,26 +42,19 @@ class _PostsScreenState extends State<PostsScreen> {
bool _getListFlag = false;
late bool _searchMode;
final ScrollController _controller = ScrollController();
int _selectedFilterIndex = 1;
int _selectedFilterIndex = -1;
final List<FilterItem> _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<PostsCubit>(context);
_cubit.getData();
_cubit.getData(catId: _cubit.lastCatId);
_controller.addListener(_onScroll);
super.initState();
}
@ -66,7 +65,7 @@ class _PostsScreenState extends State<PostsScreen> {
if (maxScroll - currentScroll <= 400) {
if (_getListFlag) {
_getListFlag = false;
_cubit.getData();
_cubit.getData(catId: _cubit.lastCatId);
}
}
}
@ -91,6 +90,7 @@ class _PostsScreenState extends State<PostsScreen> {
break;
case PostsState.changeFilterIndex:
_selectedFilterIndex = state.data;
_controller.jumpTo(0);
_loading = true;
break;
}
@ -143,9 +143,7 @@ class _PostsScreenState extends State<PostsScreen> {
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<PostsScreen> {
),
),
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<PostsScreen> {
);
}
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<PostsScreen> {
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});
}

12
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,

19
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<BaseCubitType<SinglePostState>> {
List<Comment> 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<BaseCubitType<SinglePostState>> {
commentList.removeWhere((element) => element.id == id);