refactor: Update Twitter OAuth 2.0 PKCE flow for dynamic redirect URI and public client authentication, replace SVG back button with a native icon, and enhance Twitter login error reporting.
This commit is contained in:
@@ -7,7 +7,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:real_estate_mobile/core/constants/app_colors.dart';
|
||||
import 'package:real_estate_mobile/features/profile/data/profile_repository.dart';
|
||||
|
||||
@@ -343,17 +342,31 @@ class _AgentEditProfileScreenState
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Header with circular back button (SVG) matching Figma
|
||||
// Header with back button
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => context.pop(),
|
||||
child: SvgPicture.asset(
|
||||
'assets/icons/back_circle_icon.svg',
|
||||
width: 32,
|
||||
height: 32,
|
||||
onTap: () {
|
||||
if (Navigator.of(context).canPop()) {
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
context.go('/home');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryDark.withValues(alpha: 0.08),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
color: AppColors.primaryDark,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
@@ -66,9 +66,10 @@ class _TwitterLoginScreenState extends State<TwitterLoginScreen> {
|
||||
final userData = await _authService.exchangeCodeForUser(code);
|
||||
if (mounted) Navigator.pop(context, userData);
|
||||
} catch (e) {
|
||||
debugPrint('Twitter token exchange error: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('X login failed. Please try again.')),
|
||||
SnackBar(content: Text('X login failed: ${e.toString().length > 100 ? e.toString().substring(0, 100) : e}')),
|
||||
);
|
||||
Navigator.pop(context, null);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
MessagingNotifier(this._repository, this._ref)
|
||||
: super(const MessagingState()) {
|
||||
_initSocketListeners();
|
||||
loadUnreadCount();
|
||||
}
|
||||
|
||||
String? get _currentUserId => _ref.read(authProvider).user?.id;
|
||||
@@ -132,9 +133,15 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
return c;
|
||||
}).toList();
|
||||
|
||||
// Recalculate total unread count
|
||||
final totalUnread = updatedConversations.fold<int>(
|
||||
0, (sum, c) => sum + c.unreadCount,
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
messages: updatedMessages,
|
||||
conversations: updatedConversations,
|
||||
unreadCount: totalUnread,
|
||||
);
|
||||
}));
|
||||
|
||||
@@ -575,9 +582,13 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
..remove(conversationId);
|
||||
final updatedConversations =
|
||||
state.conversations.where((c) => c.id != conversationId).toList();
|
||||
final totalUnread = updatedConversations.fold<int>(
|
||||
0, (sum, c) => sum + c.unreadCount,
|
||||
);
|
||||
state = state.copyWith(
|
||||
messages: updatedMessages,
|
||||
conversations: updatedConversations,
|
||||
unreadCount: totalUnread,
|
||||
clearActiveConversation: true,
|
||||
);
|
||||
} catch (_) {
|
||||
|
||||
@@ -171,8 +171,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
_showConfirmDialog(
|
||||
title: 'Clear Chat',
|
||||
message: 'Are you sure you want to clear all messages? This cannot be undone.',
|
||||
onConfirm: () {
|
||||
ref.read(messagingProvider.notifier).clearChat(widget.conversationId);
|
||||
onConfirm: () async {
|
||||
await ref.read(messagingProvider.notifier).clearChat(widget.conversationId);
|
||||
},
|
||||
);
|
||||
break;
|
||||
@@ -194,9 +194,10 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
_showConfirmDialog(
|
||||
title: 'Delete Conversation',
|
||||
message: 'Are you sure you want to delete this conversation? This cannot be undone.',
|
||||
onConfirm: () {
|
||||
ref.read(messagingProvider.notifier).deleteConversation(widget.conversationId);
|
||||
context.go('/messages');
|
||||
onConfirm: () async {
|
||||
final notifier = ref.read(messagingProvider.notifier);
|
||||
await notifier.deleteConversation(widget.conversationId);
|
||||
if (mounted) context.go('/messages');
|
||||
},
|
||||
isDanger: true,
|
||||
);
|
||||
@@ -207,7 +208,7 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
void _showConfirmDialog({
|
||||
required String title,
|
||||
required String message,
|
||||
required VoidCallback onConfirm,
|
||||
required Future<void> Function() onConfirm,
|
||||
bool isDanger = false,
|
||||
}) {
|
||||
showDialog(
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:real_estate_mobile/core/storage/secure_storage.dart';
|
||||
import 'package:real_estate_mobile/features/messaging/data/socket_service.dart';
|
||||
import 'package:real_estate_mobile/features/notifications/data/models/notification_model.dart';
|
||||
import 'package:real_estate_mobile/features/notifications/data/notification_repository.dart';
|
||||
|
||||
@@ -14,10 +15,16 @@ final unreadCountProvider =
|
||||
class UnreadCountNotifier extends StateNotifier<int> {
|
||||
final NotificationRepository _repo;
|
||||
Timer? _timer;
|
||||
final List<StreamSubscription> _subs = [];
|
||||
|
||||
UnreadCountNotifier(this._repo) : super(0) {
|
||||
refresh();
|
||||
_timer = Timer.periodic(const Duration(seconds: 30), (_) => refresh());
|
||||
// Refresh count immediately when socket events arrive
|
||||
final socket = SocketService();
|
||||
_subs.add(socket.onNewMessage.listen((_) => refresh()));
|
||||
_subs.add(socket.onConnectionRequest.listen((_) => refresh()));
|
||||
_subs.add(socket.onConnectionResponse.listen((_) => refresh()));
|
||||
}
|
||||
|
||||
Future<void> refresh() async {
|
||||
@@ -52,6 +59,9 @@ class UnreadCountNotifier extends StateNotifier<int> {
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
for (final sub in _subs) {
|
||||
sub.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user