forked from eatvio/eatvio_app
socket fixed
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/auth/auth_screen/auth_screen.dart';
|
||||
@@ -79,7 +79,7 @@ class AppRouter {
|
||||
path: '/my-profile-screen',
|
||||
name: 'myProfileScreen',
|
||||
redirect: (context, state) async {
|
||||
await sl<InitializationCubit>().runInitialization();
|
||||
await sl<WsManager>().initialize();
|
||||
return null;
|
||||
},
|
||||
pageBuilder: (context, state) => NoTransitionPage(child: ProfileScreen()),
|
||||
|
||||
@@ -4,3 +4,7 @@ const List<int> durationValues = [7, 14, 21];
|
||||
|
||||
const List<String> dietTypeValues = ['veg', 'light', 'carb', 'meat'];
|
||||
const List<String> planTypeValues = ['fit', 'slim', 'wellness', 'behavior'];
|
||||
|
||||
const newMessagesChannelName = 'private-new-chat-messages';
|
||||
const newNotificationsChannelName = 'private-message-events';
|
||||
const directChatChannelName = 'private-direct-chat';
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_state.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class InitializationCubit extends Cubit<InitializationState> {
|
||||
final ChatRepository chatRepository;
|
||||
bool wasInitialized = false;
|
||||
|
||||
InitializationCubit({required this.chatRepository}) : super(const InitializationState.loading());
|
||||
|
||||
Future<void> subscribeNotifications(int userId, PusherChannelsClient client) async {
|
||||
final privateNotificationChannel = await client.privateChannel(
|
||||
'private-message-events.$userId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse(baseUrl + '/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
privateNotificationChannel.subscribe();
|
||||
|
||||
privateNotificationChannel.bindToAll().listen((event) {
|
||||
sl<NotificationsCubit>().incrementCounter(event.name);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> subscribeChatMessages(int userId, PusherChannelsClient client) async {
|
||||
final privateMessagesChannel = await client.privateChannel(
|
||||
'private-new-chat-messages.$userId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse(baseUrl + '/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
privateMessagesChannel.subscribe();
|
||||
|
||||
privateMessagesChannel.bindToAll().listen((event) {
|
||||
sl<NewMessagesCubit>().incrementCounter();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> runInitialization() async {
|
||||
if (!wasInitialized) {
|
||||
wasInitialized = true;
|
||||
try {
|
||||
await TokenStorage.haveTokenInStorage();
|
||||
final wsUrl = await chatRepository.getWebsocket();
|
||||
final options = PusherChannelsOptions.fromHost(
|
||||
scheme: 'wss',
|
||||
key: wsUrl.key,
|
||||
host: wsUrl.host,
|
||||
shouldSupplyMetadataQueries: true,
|
||||
metadata: PusherChannelsOptionsMetadata.byDefault(),
|
||||
port: 443,
|
||||
);
|
||||
final client = PusherChannelsClient.websocket(
|
||||
options: options,
|
||||
connectionErrorHandler: (exception, trace, refresh) {
|
||||
refresh();
|
||||
},
|
||||
minimumReconnectDelayDuration: const Duration(
|
||||
seconds: 1,
|
||||
),
|
||||
defaultActivityDuration: const Duration(
|
||||
seconds: 120,
|
||||
),
|
||||
activityDurationOverride: const Duration(
|
||||
seconds: 120,
|
||||
),
|
||||
waitForPongDuration: const Duration(
|
||||
seconds: 30,
|
||||
),
|
||||
);
|
||||
await client.connect();
|
||||
|
||||
await subscribeNotifications(wsUrl.userId, client);
|
||||
await subscribeChatMessages(wsUrl.userId, client);
|
||||
|
||||
emit(
|
||||
InitializationState.success(
|
||||
client: client,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
InitializationState.error(
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'initialization_state.freezed.dart';
|
||||
|
||||
@freezed
|
||||
abstract class InitializationState with _$InitializationState {
|
||||
const factory InitializationState() = _InitializationState;
|
||||
|
||||
const factory InitializationState.loading() = InitializationStateLoading;
|
||||
|
||||
const factory InitializationState.success({
|
||||
required PusherChannelsClient client,
|
||||
}) = InitializationStateSuccess;
|
||||
|
||||
const factory InitializationState.error({
|
||||
required String errorMessage,
|
||||
}) = InitializationStateError;
|
||||
}
|
||||
@@ -1,645 +0,0 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'initialization_state.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||
|
||||
/// @nodoc
|
||||
mixin _$InitializationState {
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(PusherChannelsClient client) success,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(PusherChannelsClient client)? success,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(PusherChannelsClient client)? success,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value) $default, {
|
||||
required TResult Function(InitializationStateLoading value) loading,
|
||||
required TResult Function(InitializationStateSuccess value) success,
|
||||
required TResult Function(InitializationStateError value) error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_InitializationState value)? $default, {
|
||||
TResult? Function(InitializationStateLoading value)? loading,
|
||||
TResult? Function(InitializationStateSuccess value)? success,
|
||||
TResult? Function(InitializationStateError value)? error,
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value)? $default, {
|
||||
TResult Function(InitializationStateLoading value)? loading,
|
||||
TResult Function(InitializationStateSuccess value)? success,
|
||||
TResult Function(InitializationStateError value)? error,
|
||||
required TResult orElse(),
|
||||
}) =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $InitializationStateCopyWith<$Res> {
|
||||
factory $InitializationStateCopyWith(
|
||||
InitializationState value, $Res Function(InitializationState) then) =
|
||||
_$InitializationStateCopyWithImpl<$Res, InitializationState>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$InitializationStateCopyWithImpl<$Res, $Val extends InitializationState>
|
||||
implements $InitializationStateCopyWith<$Res> {
|
||||
_$InitializationStateCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$InitializationStateImplCopyWith<$Res> {
|
||||
factory _$$InitializationStateImplCopyWith(_$InitializationStateImpl value,
|
||||
$Res Function(_$InitializationStateImpl) then) =
|
||||
__$$InitializationStateImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InitializationStateImplCopyWithImpl<$Res>
|
||||
extends _$InitializationStateCopyWithImpl<$Res, _$InitializationStateImpl>
|
||||
implements _$$InitializationStateImplCopyWith<$Res> {
|
||||
__$$InitializationStateImplCopyWithImpl(_$InitializationStateImpl _value,
|
||||
$Res Function(_$InitializationStateImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InitializationStateImpl implements _InitializationState {
|
||||
const _$InitializationStateImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InitializationState()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$InitializationStateImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(PusherChannelsClient client) success,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return $default();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(PusherChannelsClient client)? success,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return $default?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(PusherChannelsClient client)? success,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if ($default != null) {
|
||||
return $default();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value) $default, {
|
||||
required TResult Function(InitializationStateLoading value) loading,
|
||||
required TResult Function(InitializationStateSuccess value) success,
|
||||
required TResult Function(InitializationStateError value) error,
|
||||
}) {
|
||||
return $default(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_InitializationState value)? $default, {
|
||||
TResult? Function(InitializationStateLoading value)? loading,
|
||||
TResult? Function(InitializationStateSuccess value)? success,
|
||||
TResult? Function(InitializationStateError value)? error,
|
||||
}) {
|
||||
return $default?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value)? $default, {
|
||||
TResult Function(InitializationStateLoading value)? loading,
|
||||
TResult Function(InitializationStateSuccess value)? success,
|
||||
TResult Function(InitializationStateError value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if ($default != null) {
|
||||
return $default(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _InitializationState implements InitializationState {
|
||||
const factory _InitializationState() = _$InitializationStateImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$InitializationStateLoadingImplCopyWith<$Res> {
|
||||
factory _$$InitializationStateLoadingImplCopyWith(
|
||||
_$InitializationStateLoadingImpl value,
|
||||
$Res Function(_$InitializationStateLoadingImpl) then) =
|
||||
__$$InitializationStateLoadingImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InitializationStateLoadingImplCopyWithImpl<$Res>
|
||||
extends _$InitializationStateCopyWithImpl<$Res,
|
||||
_$InitializationStateLoadingImpl>
|
||||
implements _$$InitializationStateLoadingImplCopyWith<$Res> {
|
||||
__$$InitializationStateLoadingImplCopyWithImpl(
|
||||
_$InitializationStateLoadingImpl _value,
|
||||
$Res Function(_$InitializationStateLoadingImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InitializationStateLoadingImpl implements InitializationStateLoading {
|
||||
const _$InitializationStateLoadingImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InitializationState.loading()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$InitializationStateLoadingImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(PusherChannelsClient client) success,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return loading();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(PusherChannelsClient client)? success,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return loading?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(PusherChannelsClient client)? success,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loading != null) {
|
||||
return loading();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value) $default, {
|
||||
required TResult Function(InitializationStateLoading value) loading,
|
||||
required TResult Function(InitializationStateSuccess value) success,
|
||||
required TResult Function(InitializationStateError value) error,
|
||||
}) {
|
||||
return loading(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_InitializationState value)? $default, {
|
||||
TResult? Function(InitializationStateLoading value)? loading,
|
||||
TResult? Function(InitializationStateSuccess value)? success,
|
||||
TResult? Function(InitializationStateError value)? error,
|
||||
}) {
|
||||
return loading?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value)? $default, {
|
||||
TResult Function(InitializationStateLoading value)? loading,
|
||||
TResult Function(InitializationStateSuccess value)? success,
|
||||
TResult Function(InitializationStateError value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (loading != null) {
|
||||
return loading(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class InitializationStateLoading implements InitializationState {
|
||||
const factory InitializationStateLoading() = _$InitializationStateLoadingImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$InitializationStateSuccessImplCopyWith<$Res> {
|
||||
factory _$$InitializationStateSuccessImplCopyWith(
|
||||
_$InitializationStateSuccessImpl value,
|
||||
$Res Function(_$InitializationStateSuccessImpl) then) =
|
||||
__$$InitializationStateSuccessImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({PusherChannelsClient client});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InitializationStateSuccessImplCopyWithImpl<$Res>
|
||||
extends _$InitializationStateCopyWithImpl<$Res,
|
||||
_$InitializationStateSuccessImpl>
|
||||
implements _$$InitializationStateSuccessImplCopyWith<$Res> {
|
||||
__$$InitializationStateSuccessImplCopyWithImpl(
|
||||
_$InitializationStateSuccessImpl _value,
|
||||
$Res Function(_$InitializationStateSuccessImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? client = null,
|
||||
}) {
|
||||
return _then(_$InitializationStateSuccessImpl(
|
||||
client: null == client
|
||||
? _value.client
|
||||
: client // ignore: cast_nullable_to_non_nullable
|
||||
as PusherChannelsClient,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InitializationStateSuccessImpl implements InitializationStateSuccess {
|
||||
const _$InitializationStateSuccessImpl({required this.client});
|
||||
|
||||
@override
|
||||
final PusherChannelsClient client;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InitializationState.success(client: $client)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$InitializationStateSuccessImpl &&
|
||||
(identical(other.client, client) || other.client == client));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, client);
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$InitializationStateSuccessImplCopyWith<_$InitializationStateSuccessImpl>
|
||||
get copyWith => __$$InitializationStateSuccessImplCopyWithImpl<
|
||||
_$InitializationStateSuccessImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(PusherChannelsClient client) success,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return success(client);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(PusherChannelsClient client)? success,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return success?.call(client);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(PusherChannelsClient client)? success,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (success != null) {
|
||||
return success(client);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value) $default, {
|
||||
required TResult Function(InitializationStateLoading value) loading,
|
||||
required TResult Function(InitializationStateSuccess value) success,
|
||||
required TResult Function(InitializationStateError value) error,
|
||||
}) {
|
||||
return success(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_InitializationState value)? $default, {
|
||||
TResult? Function(InitializationStateLoading value)? loading,
|
||||
TResult? Function(InitializationStateSuccess value)? success,
|
||||
TResult? Function(InitializationStateError value)? error,
|
||||
}) {
|
||||
return success?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value)? $default, {
|
||||
TResult Function(InitializationStateLoading value)? loading,
|
||||
TResult Function(InitializationStateSuccess value)? success,
|
||||
TResult Function(InitializationStateError value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (success != null) {
|
||||
return success(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class InitializationStateSuccess implements InitializationState {
|
||||
const factory InitializationStateSuccess(
|
||||
{required final PusherChannelsClient client}) =
|
||||
_$InitializationStateSuccessImpl;
|
||||
|
||||
PusherChannelsClient get client;
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$InitializationStateSuccessImplCopyWith<_$InitializationStateSuccessImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$InitializationStateErrorImplCopyWith<$Res> {
|
||||
factory _$$InitializationStateErrorImplCopyWith(
|
||||
_$InitializationStateErrorImpl value,
|
||||
$Res Function(_$InitializationStateErrorImpl) then) =
|
||||
__$$InitializationStateErrorImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({String errorMessage});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InitializationStateErrorImplCopyWithImpl<$Res>
|
||||
extends _$InitializationStateCopyWithImpl<$Res,
|
||||
_$InitializationStateErrorImpl>
|
||||
implements _$$InitializationStateErrorImplCopyWith<$Res> {
|
||||
__$$InitializationStateErrorImplCopyWithImpl(
|
||||
_$InitializationStateErrorImpl _value,
|
||||
$Res Function(_$InitializationStateErrorImpl) _then)
|
||||
: super(_value, _then);
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? errorMessage = null,
|
||||
}) {
|
||||
return _then(_$InitializationStateErrorImpl(
|
||||
errorMessage: null == errorMessage
|
||||
? _value.errorMessage
|
||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InitializationStateErrorImpl implements InitializationStateError {
|
||||
const _$InitializationStateErrorImpl({required this.errorMessage});
|
||||
|
||||
@override
|
||||
final String errorMessage;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InitializationState.error(errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$InitializationStateErrorImpl &&
|
||||
(identical(other.errorMessage, errorMessage) ||
|
||||
other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, errorMessage);
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$InitializationStateErrorImplCopyWith<_$InitializationStateErrorImpl>
|
||||
get copyWith => __$$InitializationStateErrorImplCopyWithImpl<
|
||||
_$InitializationStateErrorImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>(
|
||||
TResult Function() $default, {
|
||||
required TResult Function() loading,
|
||||
required TResult Function(PusherChannelsClient client) success,
|
||||
required TResult Function(String errorMessage) error,
|
||||
}) {
|
||||
return error(errorMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>(
|
||||
TResult? Function()? $default, {
|
||||
TResult? Function()? loading,
|
||||
TResult? Function(PusherChannelsClient client)? success,
|
||||
TResult? Function(String errorMessage)? error,
|
||||
}) {
|
||||
return error?.call(errorMessage);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>(
|
||||
TResult Function()? $default, {
|
||||
TResult Function()? loading,
|
||||
TResult Function(PusherChannelsClient client)? success,
|
||||
TResult Function(String errorMessage)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (error != null) {
|
||||
return error(errorMessage);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value) $default, {
|
||||
required TResult Function(InitializationStateLoading value) loading,
|
||||
required TResult Function(InitializationStateSuccess value) success,
|
||||
required TResult Function(InitializationStateError value) error,
|
||||
}) {
|
||||
return error(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>(
|
||||
TResult? Function(_InitializationState value)? $default, {
|
||||
TResult? Function(InitializationStateLoading value)? loading,
|
||||
TResult? Function(InitializationStateSuccess value)? success,
|
||||
TResult? Function(InitializationStateError value)? error,
|
||||
}) {
|
||||
return error?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>(
|
||||
TResult Function(_InitializationState value)? $default, {
|
||||
TResult Function(InitializationStateLoading value)? loading,
|
||||
TResult Function(InitializationStateSuccess value)? success,
|
||||
TResult Function(InitializationStateError value)? error,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (error != null) {
|
||||
return error(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class InitializationStateError implements InitializationState {
|
||||
const factory InitializationStateError({required final String errorMessage}) =
|
||||
_$InitializationStateErrorImpl;
|
||||
|
||||
String get errorMessage;
|
||||
|
||||
/// Create a copy of InitializationState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$InitializationStateErrorImplCopyWith<_$InitializationStateErrorImpl>
|
||||
get copyWith => throw _privateConstructorUsedError;
|
||||
}
|
||||
139
lib/core/initialization_cubit/ws_manager.dart
Normal file
139
lib/core/initialization_cubit/ws_manager.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_service.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
|
||||
class WsManager {
|
||||
final WsService wsService;
|
||||
|
||||
WsManager({required this.wsService});
|
||||
|
||||
PusherChannelsClient? client;
|
||||
int? userId;
|
||||
|
||||
PrivateChannel? _privateNotificationChannel;
|
||||
PrivateChannel? _privateMessagesChannel;
|
||||
PrivateChannel? _privateDirectChatChannel;
|
||||
|
||||
Stream<ChannelReadEvent>? get newMessagesStream => _privateMessagesChannel?.bind('App\\Events\\ChatMessageUpdated');
|
||||
|
||||
// TODO: change name of event to a right one
|
||||
Stream<ChannelReadEvent>? get newNotificationsStream => _privateNotificationChannel?.bind('App\\Events\\ChatMessageUpdated');
|
||||
|
||||
Stream<ChannelReadEvent>? get directChatStream => _privateDirectChatChannel?.bind('App\\Events\\ChatMessageUpdated');
|
||||
|
||||
Stream<ChannelReadEvent>? get clientTypingStream => _privateDirectChatChannel?.bind('client-typing');
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (this.client == null) {
|
||||
try {
|
||||
await TokenStorage.haveTokenInStorage();
|
||||
final wsUrl = await wsService.getWebsocket();
|
||||
|
||||
final options = PusherChannelsOptions.fromHost(
|
||||
scheme: 'wss',
|
||||
key: wsUrl.key,
|
||||
host: wsUrl.host,
|
||||
shouldSupplyMetadataQueries: true,
|
||||
metadata: PusherChannelsOptionsMetadata.byDefault(),
|
||||
port: 443,
|
||||
);
|
||||
client = PusherChannelsClient.websocket(
|
||||
options: options,
|
||||
connectionErrorHandler: (exception, trace, refresh) {
|
||||
refresh();
|
||||
},
|
||||
minimumReconnectDelayDuration: const Duration(
|
||||
seconds: 1,
|
||||
),
|
||||
defaultActivityDuration: const Duration(
|
||||
seconds: 30,
|
||||
),
|
||||
waitForPongDuration: const Duration(
|
||||
seconds: 30,
|
||||
),
|
||||
);
|
||||
|
||||
await client?.connect();
|
||||
|
||||
this.client = client;
|
||||
userId = wsUrl.userId;
|
||||
|
||||
await _initPrivateNotificationsChannel();
|
||||
await _initPrivateMessagesChannel();
|
||||
|
||||
client?.lifecycleStream.listen((data) {
|
||||
if (data == PusherChannelsClientLifeCycleState.establishedConnection) {
|
||||
_unsubscribe();
|
||||
_subscribe();
|
||||
}
|
||||
});
|
||||
client?.eventStream.listen((event) {
|
||||
log(event.rootObject.toString());
|
||||
});
|
||||
_subscribe();
|
||||
} catch (e, st) {
|
||||
log(e.toString(), stackTrace: st);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initPrivateNotificationsChannel() async {
|
||||
final token = await TokenStorage.getTokenFromStorage();
|
||||
_privateNotificationChannel = await client?.privateChannel(
|
||||
'$newNotificationsChannelName.$userId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse(baseUrl + '/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _initPrivateMessagesChannel() async {
|
||||
final token = await TokenStorage.getTokenFromStorage();
|
||||
_privateMessagesChannel = await client?.privateChannel(
|
||||
'$newMessagesChannelName.$userId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse(baseUrl + '/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initPrivateDirectChatChannel(String chatId) async {
|
||||
final token = await TokenStorage.getTokenFromStorage();
|
||||
_privateDirectChatChannel = await client?.privateChannel(
|
||||
'$directChatChannelName.$chatId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse(baseUrl + '/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _subscribe() {
|
||||
_privateMessagesChannel?.subscribe();
|
||||
_privateNotificationChannel?.subscribe();
|
||||
}
|
||||
|
||||
void _unsubscribe() {
|
||||
_privateMessagesChannel?.unsubscribe();
|
||||
_privateNotificationChannel?.unsubscribe();
|
||||
}
|
||||
|
||||
void subscribeDirectChatChannel() {
|
||||
_privateDirectChatChannel?.subscribe();
|
||||
}
|
||||
|
||||
void unsubscribeDirectChatChannel() {
|
||||
_privateDirectChatChannel?.unsubscribe();
|
||||
}
|
||||
}
|
||||
15
lib/core/initialization_cubit/ws_service.dart
Normal file
15
lib/core/initialization_cubit/ws_service.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/chat/models/websocket_info_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'ws_service.g.dart';
|
||||
|
||||
|
||||
@RestApi()
|
||||
abstract class WsService {
|
||||
|
||||
factory WsService(Dio dio, {String baseUrl}) = _WsService;
|
||||
|
||||
@GET('/api/profile/ws/params')
|
||||
Future<WebsocketInfoModel> getWebsocket();
|
||||
}
|
||||
86
lib/core/initialization_cubit/ws_service.g.dart
Normal file
86
lib/core/initialization_cubit/ws_service.g.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'ws_service.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RetrofitGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations
|
||||
|
||||
class _WsService implements WsService {
|
||||
_WsService(
|
||||
this._dio, {
|
||||
this.baseUrl,
|
||||
this.errorLogger,
|
||||
});
|
||||
|
||||
final Dio _dio;
|
||||
|
||||
String? baseUrl;
|
||||
|
||||
final ParseErrorLogger? errorLogger;
|
||||
|
||||
@override
|
||||
Future<WebsocketInfoModel> getWebsocket() async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<WebsocketInfoModel>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/ws/params',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
|
||||
late WebsocketInfoModel _value;
|
||||
try {
|
||||
_value = WebsocketInfoModel.fromJson(_result.data!);
|
||||
} on Object catch (e, s) {
|
||||
errorLogger?.logError(e, s, _options);
|
||||
rethrow;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
requestOptions.responseType == ResponseType.stream)) {
|
||||
if (T == String) {
|
||||
requestOptions.responseType = ResponseType.plain;
|
||||
} else {
|
||||
requestOptions.responseType = ResponseType.json;
|
||||
}
|
||||
}
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
String _combineBaseUrls(
|
||||
String dioBaseUrl,
|
||||
String? baseUrl,
|
||||
) {
|
||||
if (baseUrl == null || baseUrl.trim().isEmpty) {
|
||||
return dioBaseUrl;
|
||||
}
|
||||
|
||||
final url = Uri.parse(baseUrl);
|
||||
|
||||
if (url.isAbsolute) {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_service.dart';
|
||||
import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart';
|
||||
import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart';
|
||||
import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart';
|
||||
import 'package:eatvio_app/features/create_plan/presentation/cubit/create_plan_cubit.dart';
|
||||
import 'package:eatvio_app/features/diets/repositories/diet_repository.dart';
|
||||
import 'package:eatvio_app/features/diets/services/diet_service.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/bloc/blog_post_create_cubit/blog_post_create_cubit.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repository.dart';
|
||||
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
|
||||
@@ -69,15 +70,15 @@ final sl = GetIt.instance;
|
||||
|
||||
Future<void> init() async {
|
||||
// Cubit singletons
|
||||
sl.registerLazySingleton<InitializationCubit>(() => InitializationCubit(chatRepository: sl()));
|
||||
sl.registerLazySingleton<WsManager>(() => WsManager(wsService: sl()));
|
||||
sl.registerLazySingleton<NewMessagesCubit>(() => NewMessagesCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit());
|
||||
sl.registerLazySingleton<NotificationsCubit>(() => NotificationsCubit(wsManager: sl()));
|
||||
sl.registerLazySingleton<FeedCubit>(() => FeedCubit(feedRepository: sl()));
|
||||
sl.registerLazySingleton<ProfileAvatarCubit>(() => ProfileAvatarCubit(profileRepository: sl()));
|
||||
|
||||
// Cubit factories
|
||||
sl.registerFactory<RegistrationCubit>(() => RegistrationCubit(registrationRepository: sl()));
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(chatRepository: sl(), initializationCubit: sl()));
|
||||
sl.registerFactory<ChatRoomCubit>(() => ChatRoomCubit(chatRepository: sl(), wsManager: sl()));
|
||||
sl.registerFactory<RecipesCubit>(() => RecipesCubit(feedRepository: sl(), recipeRepository: sl()));
|
||||
sl.registerFactory<CalendarCubit>(() => CalendarCubit(calendarRepository: sl()));
|
||||
sl.registerFactory<ProfileCubit>(() => ProfileCubit(profileRepository: sl(), chatRepository: sl()));
|
||||
@@ -138,6 +139,7 @@ Future<void> init() async {
|
||||
sl.registerLazySingleton<PlanService>(() => PlanService(sl()));
|
||||
sl.registerLazySingleton<MyDietsService>(() => MyDietsService(sl()));
|
||||
sl.registerLazySingleton<MyPlansService>(() => MyPlansService(sl()));
|
||||
sl.registerLazySingleton<WsService>(() => WsService(sl()));
|
||||
|
||||
// External
|
||||
sl.registerLazySingleton(
|
||||
|
||||
@@ -220,6 +220,12 @@ abstract class TextStyles {
|
||||
height: 1.255,
|
||||
);
|
||||
|
||||
static const textFS13FW400 = TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.255,
|
||||
);
|
||||
|
||||
static const textXSSemibold = TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -186,6 +186,8 @@ class PrimaryColors {
|
||||
Color get red => Color(0xFFCD4141);
|
||||
|
||||
Color get transparentBlack => Color(0xFF1313264).withValues(alpha: 0.3);
|
||||
|
||||
Color get gray60004 => Color(0xFFD4DAE2).withValues(alpha: 0.2);
|
||||
}
|
||||
|
||||
PrimaryColors get appTheme => ThemeHelper().themeColor();
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class NotificationsCubit extends Cubit<NotificationsState> {
|
||||
int _notificationsCount = 0;
|
||||
List<String> notifications = [];
|
||||
|
||||
NotificationsCubit() : super(const NotificationsState.empty());
|
||||
final WsManager wsManager;
|
||||
NotificationsCubit({required this.wsManager}) : super(const NotificationsState.empty()) {
|
||||
wsManager.newNotificationsStream?.listen((event) {
|
||||
incrementCounter(event.data);
|
||||
});
|
||||
}
|
||||
|
||||
incrementCounter(String data) {
|
||||
_notificationsCount++;
|
||||
|
||||
@@ -2,13 +2,9 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:dart_pusher_channels/dart_pusher_channels.dart';
|
||||
import 'package:easy_debounce/easy_debounce.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/image_compressing/image_compress_utility.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_state.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_state.dart';
|
||||
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
|
||||
@@ -16,41 +12,42 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
final ChatRepository chatRepository;
|
||||
final InitializationCubit initializationCubit;
|
||||
final WsManager wsManager;
|
||||
|
||||
int currentPage = 1;
|
||||
late final int lastPage;
|
||||
bool isLoading = false;
|
||||
|
||||
late final PusherChannelsClient client;
|
||||
late final PrivateChannel privateChatChannel;
|
||||
|
||||
ChatRoomCubit({
|
||||
required this.chatRepository,
|
||||
required this.initializationCubit,
|
||||
required this.wsManager,
|
||||
}) : super(ChatRoomState.loading());
|
||||
|
||||
loadNextPage(String chatId) async {
|
||||
if (currentPage < lastPage && !isLoading) {
|
||||
isLoading = true;
|
||||
|
||||
emit(ChatRoomState.loaded(
|
||||
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages,
|
||||
isTyping: (state as ChatRoomStateLoaded).isTyping,
|
||||
isLoading: isLoading,
|
||||
),);
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
|
||||
chatMessages: (state as ChatRoomStateLoaded).chatMessages,
|
||||
isTyping: (state as ChatRoomStateLoaded).isTyping,
|
||||
isLoading: isLoading,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage + 1);
|
||||
|
||||
isLoading = false;
|
||||
emit(ChatRoomState.loaded(
|
||||
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
|
||||
chatMessages: [...(state as ChatRoomStateLoaded).chatMessages, ...chatMessages.messagesList],
|
||||
isTyping: (state as ChatRoomStateLoaded).isTyping,
|
||||
isLoading: isLoading,
|
||||
),);
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
|
||||
chatMessages: [...(state as ChatRoomStateLoaded).chatMessages, ...chatMessages.messagesList],
|
||||
isTyping: (state as ChatRoomStateLoaded).isTyping,
|
||||
isLoading: isLoading,
|
||||
),
|
||||
);
|
||||
|
||||
currentPage++;
|
||||
} catch (e) {
|
||||
@@ -66,38 +63,29 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
final chatMessages = await chatRepository.getChatMessages(chatId, currentPage);
|
||||
lastPage = chatMessages.meta.lastPage;
|
||||
|
||||
await _subscribeToChatChannel(initCubit: initializationCubit, chatId: chatId);
|
||||
await _subscribeToChatChannel(chatId: chatId);
|
||||
|
||||
emit(ChatRoomState.loaded(
|
||||
chatRoom: chatRoom,
|
||||
chatMessages: chatMessages.messagesList,
|
||||
isTyping: false,
|
||||
isLoading: isLoading,
|
||||
),);
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: chatRoom,
|
||||
chatMessages: chatMessages.messagesList,
|
||||
isTyping: false,
|
||||
isLoading: isLoading,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(ChatRoomState.error(errorMessage: 'Ошибка получеия данных'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _subscribeToChatChannel({
|
||||
required InitializationCubit initCubit,
|
||||
required String chatId,
|
||||
}) async {
|
||||
client = (initCubit.state as InitializationStateSuccess).client;
|
||||
|
||||
privateChatChannel = await client.privateChannel(
|
||||
'private-direct-chat.$chatId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPrivateChannel(
|
||||
authorizationEndpoint: Uri.parse(baseUrl + '/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer ${await TokenStorage.getTokenFromStorage()}',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
privateChatChannel.subscribe();
|
||||
|
||||
privateChatChannel.bind('App\\Events\\ChatMessageUpdated').listen((event) {
|
||||
final client = wsManager.client;
|
||||
if (client == null) return;
|
||||
await wsManager.initPrivateDirectChatChannel(chatId);
|
||||
wsManager.subscribeDirectChatChannel();
|
||||
wsManager.directChatStream?.listen((event) {
|
||||
AudioPlayer().play((AssetSource('audio/recive_message.wav')));
|
||||
List<ChatMessageModel> messagesList = [
|
||||
ChatMessageModel.fromJson(jsonDecode(event.data)['message']),
|
||||
@@ -113,7 +101,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
);
|
||||
});
|
||||
|
||||
privateChatChannel.bind('client-typing').listen((event) {
|
||||
wsManager.clientTypingStream?.listen((event) {
|
||||
emit(
|
||||
ChatRoomState.loaded(
|
||||
chatRoom: (state as ChatRoomStateLoaded).chatRoom,
|
||||
@@ -152,7 +140,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
privateChatChannel.unsubscribe();
|
||||
wsManager.unsubscribeDirectChatChannel();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chat_room_cubit/chat_room_cubit.dart';
|
||||
@@ -78,6 +79,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
backgroundColor: appTheme.gray100,
|
||||
centerTitle: false,
|
||||
title: Row(
|
||||
children: [
|
||||
@@ -136,7 +138,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
color: Color(0xFFF0F2F5),
|
||||
color: appTheme.whiteA700,
|
||||
width: double.maxFinite,
|
||||
child: CustomScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
@@ -149,20 +151,16 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index < state.chatMessages.length) {
|
||||
return Column(
|
||||
children: [
|
||||
if ((index + 1 != state.chatMessages.length &&
|
||||
DateTime.parse(state.chatMessages[index].updatedAt).day !=
|
||||
DateTime.parse(state.chatMessages[index + 1].updatedAt).day) ||
|
||||
index + 1 == state.chatMessages.length)
|
||||
_messageDate(state.chatMessages[index].updatedAt),
|
||||
ChatBubbleWidget(
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
),
|
||||
],
|
||||
if ((index + 1 != state.chatMessages.length &&
|
||||
DateTime.parse(state.chatMessages[index].updatedAt).day != DateTime.parse(state.chatMessages[index + 1].updatedAt).day) ||
|
||||
index + 1 == state.chatMessages.length) {
|
||||
return _messageDate(state.chatMessages[index].updatedAt);
|
||||
}
|
||||
return ChatBubbleWidget(
|
||||
sendTime: state.chatMessages[index].updatedAt,
|
||||
text: state.chatMessages[index].data.trim(),
|
||||
isCurrentUser: state.chatRoom.users['to_chat_user']!.id != state.chatMessages[index].authorId,
|
||||
mediaData: state.chatMessages[index].mediaData ?? [],
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
@@ -181,13 +179,6 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: Colors.black.withValues(alpha: 0.1),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
@@ -231,57 +222,60 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
},
|
||||
customBorder: CircleBorder(),
|
||||
child: Ink(
|
||||
height: 32,
|
||||
width: 32,
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Transform.rotate(
|
||||
angle: 3.8,
|
||||
angle: pi / 4,
|
||||
child: Icon(
|
||||
Icons.attach_file_rounded,
|
||||
size: 24,
|
||||
size: 30,
|
||||
color: Color(0xFF858E99),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width - 96,
|
||||
child: TextFormField(
|
||||
controller: textEditingController,
|
||||
maxLines: 5,
|
||||
minLines: 1,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
hintText: 'Сообщение',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
color: Color(0xFFAEAEB2),
|
||||
),
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextFormField(
|
||||
expands: true,
|
||||
controller: textEditingController,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w400, color: Colors.black, height: 1),
|
||||
decoration: InputDecoration(
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
hintText: 'Сообщение',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1,
|
||||
color: Color(0xFFAEAEB2),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(100),
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xFFD1D1D6),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -306,11 +300,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
color: appTheme.blueA400,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
height: 32,
|
||||
width: 32,
|
||||
height: 40,
|
||||
width: 40,
|
||||
child: Icon(
|
||||
Icons.arrow_upward_rounded,
|
||||
size: 20,
|
||||
size: 24,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
@@ -326,16 +320,18 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
_messageDate(String date) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
DateFormat.MMMMd('RU').format(
|
||||
DateTime.parse(date),
|
||||
),
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Color(0xFF8393A1),
|
||||
fontWeight: FontWeight.w500,
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(
|
||||
DateFormat.yMMMMd('RU').format(
|
||||
DateTime.parse(date),
|
||||
),
|
||||
style: const TextStyle(
|
||||
height: 1,
|
||||
color: Color(0xFF8393A1),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dismissible_page/dismissible_page.dart';
|
||||
import 'package:eatvio_app/core/const.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/utils/date_time_utils.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/pages/photo_view_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -28,7 +30,7 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
alignment: isCurrentUser ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrentUser ? Color(0xFF7F9CF5) : Colors.white,
|
||||
color: isCurrentUser ? appTheme.gray50 : appTheme.gray60004,
|
||||
borderRadius: mediaData.length > 0
|
||||
? BorderRadius.circular(24)
|
||||
: BorderRadius.horizontal(
|
||||
@@ -108,15 +110,15 @@ class ChatBubbleWidget extends StatelessWidget {
|
||||
if (text.isNotEmpty)
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(color: isCurrentUser ? Colors.white : Color(0xFF323F4B), height: 1),
|
||||
style: TextStyles.textFS13FW400.copyWith(color: appTheme.black),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: mediaData.length > 0 ? const EdgeInsets.only(right: 8, top: 4, left: 8) : EdgeInsets.zero,
|
||||
child: Text(
|
||||
DateTime.parse(sendTime).format('hh:mm'),
|
||||
style: TextStyle(
|
||||
color: isCurrentUser ? Colors.white.withValues(alpha: 0.4) : Color(0xFF323F4B).withValues(alpha: 0.4),
|
||||
color: Color(0xFF7B8794),
|
||||
height: 1,
|
||||
fontSize: 10,
|
||||
),
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/websocket_info_model.dart';
|
||||
import 'package:eatvio_app/features/chat/services/chat_service.dart';
|
||||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
@@ -67,15 +66,7 @@ class ChatRepository {
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<WebsocketInfoModel> getWebsocket() async {
|
||||
try {
|
||||
return (await chatService.getWebsocket());
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String slug}) async {
|
||||
try {
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/chat_room_preview_model.dart';
|
||||
import 'package:eatvio_app/features/chat/models/websocket_info_model.dart';
|
||||
import 'package:retrofit/retrofit.dart';
|
||||
|
||||
part 'chat_service.g.dart';
|
||||
@@ -35,9 +34,6 @@ abstract class ChatService {
|
||||
@Path() required int page,
|
||||
});
|
||||
|
||||
@GET('/api/profile/ws/params')
|
||||
Future<WebsocketInfoModel> getWebsocket();
|
||||
|
||||
@GET('/api/chat/get_by_user/{user}')
|
||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({
|
||||
@Path() required String user,
|
||||
|
||||
@@ -165,39 +165,6 @@ class _ChatService implements ChatService {
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WebsocketInfoModel> getWebsocket() async {
|
||||
final _extra = <String, dynamic>{};
|
||||
final queryParameters = <String, dynamic>{};
|
||||
final _headers = <String, dynamic>{};
|
||||
const Map<String, dynamic>? _data = null;
|
||||
final _options = _setStreamType<WebsocketInfoModel>(Options(
|
||||
method: 'GET',
|
||||
headers: _headers,
|
||||
extra: _extra,
|
||||
)
|
||||
.compose(
|
||||
_dio.options,
|
||||
'/api/profile/ws/params',
|
||||
queryParameters: queryParameters,
|
||||
data: _data,
|
||||
)
|
||||
.copyWith(
|
||||
baseUrl: _combineBaseUrls(
|
||||
_dio.options.baseUrl,
|
||||
baseUrl,
|
||||
)));
|
||||
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
|
||||
late WebsocketInfoModel _value;
|
||||
try {
|
||||
_value = WebsocketInfoModel.fromJson(_result.data!);
|
||||
} on Object catch (e, s) {
|
||||
errorLogger?.logError(e, s, _options);
|
||||
rethrow;
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ChatRoomModel> getChatRoomIdByUserSlug({required String user}) async {
|
||||
final _extra = <String, dynamic>{};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_cubit.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||
@@ -22,6 +24,9 @@ class _DiaryScreenState extends State<DiaryScreen> {
|
||||
create: (BuildContext context) => sl<DiaryCubit>()..fetchDiary(true),
|
||||
child: BlocBuilder<DiaryCubit, DiaryState>(
|
||||
builder: (BuildContext context, DiaryState state) {
|
||||
if (state is DiaryStateLoaded) {
|
||||
log(state.diary.id.toString());
|
||||
}
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey.shade200,
|
||||
appBar: AppBar(
|
||||
|
||||
@@ -39,7 +39,7 @@ class PlanModel with _$PlanModel {
|
||||
@JsonKey(name: "is_favorite") required bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") required double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") required double maxPriceOfOptions,
|
||||
@Default(false) bool wasLikedByMe,
|
||||
@JsonKey(name: "was_liked_by_me") required bool wasLikedByMe,
|
||||
}) = _PlanModel;
|
||||
|
||||
factory PlanModel.fromJson(Map<String, dynamic> json) => _$PlanModelFromJson(json);
|
||||
|
||||
@@ -298,6 +298,7 @@ mixin _$PlanModel {
|
||||
double get minPriceOfOptions => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
double get maxPriceOfOptions => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "was_liked_by_me")
|
||||
bool get wasLikedByMe => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this PlanModel to a JSON map.
|
||||
@@ -335,7 +336,7 @@ abstract class $PlanModelCopyWith<$Res> {
|
||||
@JsonKey(name: "is_favorite") bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") double maxPriceOfOptions,
|
||||
bool wasLikedByMe});
|
||||
@JsonKey(name: "was_liked_by_me") bool wasLikedByMe});
|
||||
|
||||
$PlanTypeCopyWith<$Res> get type;
|
||||
$PacketOptionsCopyWith<$Res>? get packet1Options;
|
||||
@@ -555,7 +556,7 @@ abstract class _$$PlanModelImplCopyWith<$Res>
|
||||
@JsonKey(name: "is_favorite") bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") double maxPriceOfOptions,
|
||||
bool wasLikedByMe});
|
||||
@JsonKey(name: "was_liked_by_me") bool wasLikedByMe});
|
||||
|
||||
@override
|
||||
$PlanTypeCopyWith<$Res> get type;
|
||||
@@ -711,7 +712,7 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
@JsonKey(name: "is_favorite") required this.isFavorite,
|
||||
@JsonKey(name: "min_price_of_options") required this.minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options") required this.maxPriceOfOptions,
|
||||
this.wasLikedByMe = false});
|
||||
@JsonKey(name: "was_liked_by_me") required this.wasLikedByMe});
|
||||
|
||||
factory _$PlanModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$PlanModelImplFromJson(json);
|
||||
@@ -774,7 +775,7 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
final double maxPriceOfOptions;
|
||||
@override
|
||||
@JsonKey()
|
||||
@JsonKey(name: "was_liked_by_me")
|
||||
final bool wasLikedByMe;
|
||||
|
||||
@override
|
||||
@@ -865,29 +866,30 @@ class _$PlanModelImpl implements _PlanModel {
|
||||
|
||||
abstract class _PlanModel implements PlanModel {
|
||||
const factory _PlanModel(
|
||||
{@JsonKey(name: "id") required final int id,
|
||||
@JsonKey(name: "slug") required final String slug,
|
||||
@JsonKey(name: "name") required final String name,
|
||||
@JsonKey(name: "image") required final String image,
|
||||
@JsonKey(name: "duration") required final int duration,
|
||||
@JsonKey(name: "state") required final String state,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson)
|
||||
required final PlanType type,
|
||||
@JsonKey(name: "details") required final String details,
|
||||
@JsonKey(name: "packets_count") required final int packetsCount,
|
||||
@JsonKey(name: "packet1_options") final PacketOptions? packet1Options,
|
||||
@JsonKey(name: "packet2_options") final PacketOptions? packet2Options,
|
||||
@JsonKey(name: "packet3_options") final PacketOptions? packet3Options,
|
||||
@JsonKey(name: "author") required final Author author,
|
||||
@JsonKey(name: "total_likes") required final int totalLikes,
|
||||
@JsonKey(name: "total_views") required final int totalViews,
|
||||
@JsonKey(name: "total_comments") required final int totalComments,
|
||||
@JsonKey(name: "is_favorite") required final bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options")
|
||||
required final double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
required final double maxPriceOfOptions,
|
||||
final bool wasLikedByMe}) = _$PlanModelImpl;
|
||||
{@JsonKey(name: "id") required final int id,
|
||||
@JsonKey(name: "slug") required final String slug,
|
||||
@JsonKey(name: "name") required final String name,
|
||||
@JsonKey(name: "image") required final String image,
|
||||
@JsonKey(name: "duration") required final int duration,
|
||||
@JsonKey(name: "state") required final String state,
|
||||
@JsonKey(name: "type", fromJson: planTypeFromJson)
|
||||
required final PlanType type,
|
||||
@JsonKey(name: "details") required final String details,
|
||||
@JsonKey(name: "packets_count") required final int packetsCount,
|
||||
@JsonKey(name: "packet1_options") final PacketOptions? packet1Options,
|
||||
@JsonKey(name: "packet2_options") final PacketOptions? packet2Options,
|
||||
@JsonKey(name: "packet3_options") final PacketOptions? packet3Options,
|
||||
@JsonKey(name: "author") required final Author author,
|
||||
@JsonKey(name: "total_likes") required final int totalLikes,
|
||||
@JsonKey(name: "total_views") required final int totalViews,
|
||||
@JsonKey(name: "total_comments") required final int totalComments,
|
||||
@JsonKey(name: "is_favorite") required final bool isFavorite,
|
||||
@JsonKey(name: "min_price_of_options")
|
||||
required final double minPriceOfOptions,
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
required final double maxPriceOfOptions,
|
||||
@JsonKey(name: "was_liked_by_me") required final bool wasLikedByMe}) =
|
||||
_$PlanModelImpl;
|
||||
|
||||
factory _PlanModel.fromJson(Map<String, dynamic> json) =
|
||||
_$PlanModelImpl.fromJson;
|
||||
@@ -950,6 +952,7 @@ abstract class _PlanModel implements PlanModel {
|
||||
@JsonKey(name: "max_price_of_options")
|
||||
double get maxPriceOfOptions;
|
||||
@override
|
||||
@JsonKey(name: "was_liked_by_me")
|
||||
bool get wasLikedByMe;
|
||||
|
||||
/// Create a copy of PlanModel
|
||||
|
||||
@@ -54,7 +54,7 @@ _$PlanModelImpl _$$PlanModelImplFromJson(Map<String, dynamic> json) =>
|
||||
isFavorite: json['is_favorite'] as bool,
|
||||
minPriceOfOptions: (json['min_price_of_options'] as num).toDouble(),
|
||||
maxPriceOfOptions: (json['max_price_of_options'] as num).toDouble(),
|
||||
wasLikedByMe: json['wasLikedByMe'] as bool? ?? false,
|
||||
wasLikedByMe: json['was_liked_by_me'] as bool,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$PlanModelImplToJson(_$PlanModelImpl instance) =>
|
||||
@@ -78,7 +78,7 @@ Map<String, dynamic> _$$PlanModelImplToJson(_$PlanModelImpl instance) =>
|
||||
'is_favorite': instance.isFavorite,
|
||||
'min_price_of_options': instance.minPriceOfOptions,
|
||||
'max_price_of_options': instance.maxPriceOfOptions,
|
||||
'wasLikedByMe': instance.wasLikedByMe,
|
||||
'was_liked_by_me': instance.wasLikedByMe,
|
||||
};
|
||||
|
||||
_$PacketOptionsImpl _$$PacketOptionsImplFromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_sta
|
||||
import 'package:eatvio_app/features/feed/repositories/feed_repository.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DietsCubit extends Cubit<DietsState>{
|
||||
class DietsCubit extends Cubit<DietsState> {
|
||||
final FeedRepository feedRepository;
|
||||
final DietRepository dietsRepository;
|
||||
int currentPage = 1;
|
||||
@@ -52,7 +52,6 @@ class DietsCubit extends Cubit<DietsState>{
|
||||
super.close();
|
||||
}
|
||||
|
||||
|
||||
void changeTypeFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(typeIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
@@ -64,7 +63,6 @@ class DietsCubit extends Cubit<DietsState>{
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void changeSortingType(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(sortingIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
@@ -76,7 +74,6 @@ class DietsCubit extends Cubit<DietsState>{
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void changeDurationFilter(int index) {
|
||||
emit(state.copyWith(filterState: state.filterState.copyWith(durationIndex: index), errorMessage: null));
|
||||
EasyDebounce.debounce(
|
||||
@@ -153,6 +150,7 @@ class DietsCubit extends Cubit<DietsState>{
|
||||
|
||||
Future<void> likeDiet(int id, int index) async {
|
||||
final currentDiets = state.dietsList;
|
||||
if (currentDiets.isEmpty) return;
|
||||
final dietsForModification = [...currentDiets];
|
||||
final currentDiet = currentDiets[index];
|
||||
if (currentDiet.wasLikedByMe) {
|
||||
|
||||
@@ -284,4 +284,58 @@ class PlansCubit extends Cubit<PlansState> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> likePlan(String slug, int index) async {
|
||||
final currentPlans = state.plans;
|
||||
final plansForModification = [...currentPlans];
|
||||
final currentPlan = currentPlans[index];
|
||||
if (currentPlan.wasLikedByMe) {
|
||||
plansForModification[index] = currentPlan.copyWith(
|
||||
totalLikes: currentPlan.totalLikes - 1,
|
||||
wasLikedByMe: false,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
plansForModification[index] = currentPlan.copyWith(
|
||||
totalLikes: currentPlan.totalLikes + 1,
|
||||
wasLikedByMe: true,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...plansForModification,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
errorMessage: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
EasyDebounce.debounce(
|
||||
'like_plan',
|
||||
Duration(milliseconds: 200),
|
||||
() async {
|
||||
try {
|
||||
await planRepository.likePlan(slug);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
emit(
|
||||
state.copyWith(
|
||||
plans: [
|
||||
...currentPlans,
|
||||
],
|
||||
isLoading: isLoading,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:eatvio_app/core/widgets/info_row.dart';
|
||||
import 'package:eatvio_app/core/widgets/feed_icon_button.dart';
|
||||
import 'package:eatvio_app/features/diets/presentation/widgets/info_bouble.dart';
|
||||
import 'package:eatvio_app/features/feed/models/plan_pagination_model.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/diets_cubit/diets_cubit.dart';
|
||||
import 'package:eatvio_app/core/theme/custom_text_style.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/plans_cubit/plans_cubit.dart';
|
||||
@@ -183,7 +182,7 @@ class _PlanWidgetState extends State<PlanWidget> {
|
||||
iconColor: widget.plan.wasLikedByMe ? appTheme.blueA400 : null,
|
||||
backgroundColor: widget.plan.wasLikedByMe ? appTheme.transparentBlue : null,
|
||||
onTap: () async {
|
||||
await context.read<DietsCubit>().likeDiet(widget.plan.id, widget.index);
|
||||
await context.read<PlansCubit>().likePlan(widget.plan.slug, widget.index);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
|
||||
@@ -363,13 +363,11 @@ class _AboutPlanScreenState extends State<AboutPlanScreen> {
|
||||
'assets/icons/heart.svg',
|
||||
width: 14,
|
||||
height: 12,
|
||||
|
||||
/// TODO задача для бека на добавление wasLokedByMe?
|
||||
colorFilter: ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn),
|
||||
colorFilter: state.planModel?.wasLikedByMe == true ? ColorFilter.mode(appTheme.blueA400, BlendMode.srcIn) : null,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
widget.plan.totalLikes.toString(),
|
||||
state.planModel?.totalLikes.toString() ?? '0',
|
||||
style: TextStyles.textXSSemibold.copyWith(color: appTheme.black),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -59,7 +59,6 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
late bool canSub;
|
||||
bool isFirstFetch = true;
|
||||
int _currentBullet = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -338,7 +337,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed('blogPostCreateScreen');
|
||||
GoRouter.of(context).pushNamed('blogPostCreateScreen');
|
||||
},
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Ink(
|
||||
@@ -911,15 +910,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
child: Text('Посмотреть'),
|
||||
),
|
||||
if (widget.author == null)
|
||||
PopupMenuItem(
|
||||
value: 'replace',
|
||||
child: Text('Заменить'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'replace',
|
||||
child: Text('Заменить'),
|
||||
),
|
||||
if (widget.author == null)
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Удалить'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Удалить'),
|
||||
),
|
||||
],
|
||||
child: Hero(
|
||||
tag: state.profile.profilePhotoUrl,
|
||||
@@ -1102,7 +1101,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_charts() {
|
||||
_charts() {
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/initialization_cubit/initialization_cubit.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart' as di;
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
@@ -26,8 +25,7 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<InitializationCubit>(create: (context) => sl<InitializationCubit>()..runInitialization()),
|
||||
providers: [
|
||||
BlocProvider<NewMessagesCubit>(create: (context) => sl<NewMessagesCubit>()),
|
||||
BlocProvider<NotificationsCubit>(create: (context) => sl<NotificationsCubit>()),
|
||||
BlocProvider<FeedCubit>(create: (context) => sl<FeedCubit>()..loadFeedBlogPostList()),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Mocks generated by Mockito 5.4.5 from annotations
|
||||
// Mocks generated by Mockito 5.4.4 from annotations
|
||||
// in eatvio_app/test/features/profile/services/profile_service_test.dart.
|
||||
// Do not manually edit this file.
|
||||
|
||||
@@ -25,7 +25,6 @@ import 'package:mockito/mockito.dart' as _i1;
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
// ignore_for_file: must_be_immutable
|
||||
// ignore_for_file: prefer_const_constructors
|
||||
// ignore_for_file: unnecessary_parenthesis
|
||||
// ignore_for_file: camel_case_types
|
||||
|
||||
Reference in New Issue
Block a user