feat: enhanced sockets, tuned retries and polling fallback
This commit is contained in:
@@ -45,7 +45,7 @@ PODS:
|
|||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- record_ios (1.0.0):
|
- record_ios (1.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- SDWebImage (5.21.1):
|
- SDWebImage (5.21.1):
|
||||||
- SDWebImage/Core (= 5.21.1)
|
- SDWebImage/Core (= 5.21.1)
|
||||||
@@ -143,7 +143,7 @@ SPEC CHECKSUMS:
|
|||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
|
record_ios: f75fa1d57f840012775c0e93a38a7f3ceea1a374
|
||||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||||
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb
|
||||||
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
||||||
|
|||||||
@@ -196,11 +196,16 @@ final socketServiceProvider = Provider<SocketService?>((ref) {
|
|||||||
|
|
||||||
final activeServer = ref.watch(activeServerProvider);
|
final activeServer = ref.watch(activeServerProvider);
|
||||||
final token = ref.watch(authTokenProvider3);
|
final token = ref.watch(authTokenProvider3);
|
||||||
|
final transportMode = ref.watch(appSettingsProvider).socketTransportMode; // 'auto' or 'ws'
|
||||||
|
|
||||||
return activeServer.maybeWhen(
|
return activeServer.maybeWhen(
|
||||||
data: (server) {
|
data: (server) {
|
||||||
if (server == null) return null;
|
if (server == null) return null;
|
||||||
final s = SocketService(serverConfig: server, authToken: token);
|
final s = SocketService(
|
||||||
|
serverConfig: server,
|
||||||
|
authToken: token,
|
||||||
|
websocketOnly: transportMode == 'ws',
|
||||||
|
);
|
||||||
// best-effort connect; errors handled internally
|
// best-effort connect; errors handled internally
|
||||||
// ignore unawaited_futures
|
// ignore unawaited_futures
|
||||||
s.connect();
|
s.connect();
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import '../providers/app_providers.dart';
|
import '../providers/app_providers.dart';
|
||||||
import '../../features/auth/providers/unified_auth_providers.dart';
|
import '../../features/auth/providers/unified_auth_providers.dart';
|
||||||
import '../services/navigation_service.dart';
|
import '../services/navigation_service.dart';
|
||||||
|
import '../models/conversation.dart';
|
||||||
|
import '../services/background_streaming_handler.dart';
|
||||||
import '../../features/onboarding/views/onboarding_sheet.dart';
|
import '../../features/onboarding/views/onboarding_sheet.dart';
|
||||||
import '../../shared/theme/theme_extensions.dart';
|
import '../../shared/theme/theme_extensions.dart';
|
||||||
import '../utils/debug_logger.dart';
|
import '../utils/debug_logger.dart';
|
||||||
@@ -28,6 +30,12 @@ final appStartupFlowProvider = Provider<void>((ref) {
|
|||||||
ref.watch(socketServiceProvider);
|
ref.watch(socketServiceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure resume-triggered foreground refresh is active
|
||||||
|
ref.watch(foregroundRefreshProvider);
|
||||||
|
|
||||||
|
// Keep Socket.IO connection alive in background within platform limits
|
||||||
|
ref.watch(socketPersistenceProvider);
|
||||||
|
|
||||||
// When auth state becomes authenticated, run additional background work
|
// When auth state becomes authenticated, run additional background work
|
||||||
ref.listen<AuthNavigationState>(authNavigationStateProvider,
|
ref.listen<AuthNavigationState>(authNavigationStateProvider,
|
||||||
(prev, next) {
|
(prev, next) {
|
||||||
@@ -67,6 +75,128 @@ final appStartupFlowProvider = Provider<void>((ref) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Listens to app lifecycle and refreshes server state when app returns to foreground.
|
||||||
|
///
|
||||||
|
/// Rationale: Socket.IO does not replay historical events. If the app was suspended,
|
||||||
|
/// we may miss updates. On resume, invalidate conversations to reconcile state.
|
||||||
|
final foregroundRefreshProvider = Provider<void>((ref) {
|
||||||
|
final observer = _ForegroundRefreshObserver(ref);
|
||||||
|
WidgetsBinding.instance.addObserver(observer);
|
||||||
|
ref.onDispose(() => WidgetsBinding.instance.removeObserver(observer));
|
||||||
|
});
|
||||||
|
|
||||||
|
class _ForegroundRefreshObserver extends WidgetsBindingObserver {
|
||||||
|
final Ref _ref;
|
||||||
|
_ForegroundRefreshObserver(this._ref);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
// Schedule to avoid side-effects during build frames
|
||||||
|
Future.microtask(() {
|
||||||
|
try {
|
||||||
|
_ref.invalidate(conversationsProvider);
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to keep the realtime socket connection alive while the app is
|
||||||
|
/// backgrounded, similar to how PersistentStreamingService works for streams.
|
||||||
|
///
|
||||||
|
/// Notes:
|
||||||
|
/// - iOS: limited to short background task windows; we send periodic keepAlive.
|
||||||
|
/// - Android: uses existing foreground service notification.
|
||||||
|
final socketPersistenceProvider = Provider<void>((ref) {
|
||||||
|
final observer = _SocketPersistenceObserver(ref);
|
||||||
|
WidgetsBinding.instance.addObserver(observer);
|
||||||
|
// React to active conversation changes while backgrounded
|
||||||
|
final sub = ref.listen<Conversation?>(
|
||||||
|
activeConversationProvider,
|
||||||
|
(prev, next) => observer.onActiveConversationChanged(),
|
||||||
|
);
|
||||||
|
ref.onDispose(() => WidgetsBinding.instance.removeObserver(observer));
|
||||||
|
ref.onDispose(sub.close);
|
||||||
|
});
|
||||||
|
|
||||||
|
class _SocketPersistenceObserver extends WidgetsBindingObserver {
|
||||||
|
final Ref _ref;
|
||||||
|
_SocketPersistenceObserver(this._ref);
|
||||||
|
|
||||||
|
static const String _socketId = 'socket-keepalive';
|
||||||
|
Timer? _heartbeat;
|
||||||
|
bool _bgActive = false;
|
||||||
|
bool _isBackgrounded = false;
|
||||||
|
|
||||||
|
bool _shouldKeepAlive() {
|
||||||
|
final authed =
|
||||||
|
_ref.read(authNavigationStateProvider) ==
|
||||||
|
AuthNavigationState.authenticated;
|
||||||
|
final hasConversation = _ref.read(activeConversationProvider) != null;
|
||||||
|
return authed && hasConversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startBackground() {
|
||||||
|
if (_bgActive) return;
|
||||||
|
if (!_shouldKeepAlive()) return;
|
||||||
|
try {
|
||||||
|
BackgroundStreamingHandler.instance
|
||||||
|
.startBackgroundExecution([_socketId]);
|
||||||
|
// Periodic keep-alive (primarily useful on iOS)
|
||||||
|
_heartbeat?.cancel();
|
||||||
|
_heartbeat =
|
||||||
|
Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||||
|
try {
|
||||||
|
await BackgroundStreamingHandler.instance.keepAlive();
|
||||||
|
} catch (_) {}
|
||||||
|
});
|
||||||
|
_bgActive = true;
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopBackground() {
|
||||||
|
if (!_bgActive) return;
|
||||||
|
try {
|
||||||
|
BackgroundStreamingHandler.instance
|
||||||
|
.stopBackgroundExecution([_socketId]);
|
||||||
|
} catch (_) {}
|
||||||
|
_heartbeat?.cancel();
|
||||||
|
_heartbeat = null;
|
||||||
|
_bgActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
switch (state) {
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
case AppLifecycleState.inactive:
|
||||||
|
_isBackgrounded = true;
|
||||||
|
_startBackground();
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.resumed:
|
||||||
|
_isBackgrounded = false;
|
||||||
|
_stopBackground();
|
||||||
|
break;
|
||||||
|
case AppLifecycleState.detached:
|
||||||
|
case AppLifecycleState.hidden:
|
||||||
|
_isBackgrounded = false;
|
||||||
|
_stopBackground();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when active conversation changes; only acts during background
|
||||||
|
void onActiveConversationChanged() {
|
||||||
|
if (!_isBackgrounded) return;
|
||||||
|
if (_shouldKeepAlive()) {
|
||||||
|
_startBackground();
|
||||||
|
} else {
|
||||||
|
_stopBackground();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _maybeShowOnboarding(Ref ref) async {
|
Future<void> _maybeShowOnboarding(Ref ref) async {
|
||||||
try {
|
try {
|
||||||
final storage = ref.read(optimizedStorageServiceProvider);
|
final storage = ref.read(optimizedStorageServiceProvider);
|
||||||
|
|||||||
@@ -2907,7 +2907,8 @@ class ApiService {
|
|||||||
bool containsDone(String s) =>
|
bool containsDone(String s) =>
|
||||||
s.contains('<details type="tool_calls"') && s.contains('done="true"');
|
s.contains('<details type="tool_calls"') && s.contains('done="true"');
|
||||||
|
|
||||||
while (DateTime.now().difference(started).inSeconds < 60) {
|
// Allow longer time for large completions (e.g., long stories)
|
||||||
|
while (DateTime.now().difference(started).inSeconds < 180) {
|
||||||
try {
|
try {
|
||||||
// Small delay between polls
|
// Small delay between polls
|
||||||
await Future.delayed(const Duration(milliseconds: 900));
|
await Future.delayed(const Duration(milliseconds: 900));
|
||||||
@@ -3069,14 +3070,15 @@ class ApiService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If content hasn't changed for a few polls, assume completion
|
// If content hasn't changed for a few polls, assume completion,
|
||||||
|
// but do not early-exit while content is still empty.
|
||||||
final prev = last;
|
final prev = last;
|
||||||
if (content == prev) {
|
if (content == prev && content.isNotEmpty) {
|
||||||
stableCount++;
|
stableCount++;
|
||||||
} else {
|
} else if (content != prev) {
|
||||||
stableCount = 0;
|
stableCount = 0;
|
||||||
}
|
}
|
||||||
if (stableCount >= 3) {
|
if (content.isNotEmpty && stableCount >= 3) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3086,6 +3088,66 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final backfill: one last attempt to fetch the latest content
|
||||||
|
// in case the server wrote the final message after our last poll.
|
||||||
|
try {
|
||||||
|
if (!streamController.isClosed) {
|
||||||
|
final resp = await _dio.get('/api/v1/chats/$chatId');
|
||||||
|
final data = resp.data as Map<String, dynamic>;
|
||||||
|
String content = '';
|
||||||
|
Map<String, dynamic>? chatObj = (data['chat'] is Map<String, dynamic>)
|
||||||
|
? data['chat'] as Map<String, dynamic>
|
||||||
|
: null;
|
||||||
|
if (chatObj != null && chatObj['messages'] is List) {
|
||||||
|
final List messagesList = chatObj['messages'] as List;
|
||||||
|
final target = messagesList.firstWhere(
|
||||||
|
(m) => (m is Map && (m['id']?.toString() == messageId)),
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (target != null) {
|
||||||
|
final rawContent = (target as Map)['content'];
|
||||||
|
if (rawContent is String) {
|
||||||
|
content = rawContent;
|
||||||
|
} else if (rawContent is List) {
|
||||||
|
final textItem = rawContent.firstWhere(
|
||||||
|
(i) => i is Map && i['type'] == 'text',
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (textItem != null) {
|
||||||
|
content = (textItem as Map)['text']?.toString() ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content.isEmpty && chatObj != null) {
|
||||||
|
final history = chatObj['history'];
|
||||||
|
if (history is Map && history['messages'] is Map) {
|
||||||
|
final Map<String, dynamic> messagesMap =
|
||||||
|
(history['messages'] as Map).cast<String, dynamic>();
|
||||||
|
final msg = messagesMap[messageId];
|
||||||
|
if (msg is Map) {
|
||||||
|
final rawContent = msg['content'];
|
||||||
|
if (rawContent is String) {
|
||||||
|
content = rawContent;
|
||||||
|
} else if (rawContent is List) {
|
||||||
|
final textItem = rawContent.firstWhere(
|
||||||
|
(i) => i is Map && i['type'] == 'text',
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
if (textItem != null) {
|
||||||
|
content = (textItem as Map)['text']?.toString() ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (content.isNotEmpty && content != last) {
|
||||||
|
streamController.add('\n');
|
||||||
|
streamController.add(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
if (!streamController.isClosed) {
|
if (!streamController.isClosed) {
|
||||||
streamController.close();
|
streamController.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ class SettingsService {
|
|||||||
static const String _voiceLocaleKey = 'voice_locale_id';
|
static const String _voiceLocaleKey = 'voice_locale_id';
|
||||||
static const String _voiceHoldToTalkKey = 'voice_hold_to_talk';
|
static const String _voiceHoldToTalkKey = 'voice_hold_to_talk';
|
||||||
static const String _voiceAutoSendKey = 'voice_auto_send_final';
|
static const String _voiceAutoSendKey = 'voice_auto_send_final';
|
||||||
|
// Realtime transport preference
|
||||||
|
static const String _socketTransportModeKey = 'socket_transport_mode'; // 'auto' or 'ws'
|
||||||
|
|
||||||
/// Get reduced motion preference
|
/// Get reduced motion preference
|
||||||
static Future<bool> getReduceMotion() async {
|
static Future<bool> getReduceMotion() async {
|
||||||
@@ -133,6 +135,7 @@ class SettingsService {
|
|||||||
voiceLocaleId: await getVoiceLocaleId(),
|
voiceLocaleId: await getVoiceLocaleId(),
|
||||||
voiceHoldToTalk: await getVoiceHoldToTalk(),
|
voiceHoldToTalk: await getVoiceHoldToTalk(),
|
||||||
voiceAutoSendFinal: await getVoiceAutoSendFinal(),
|
voiceAutoSendFinal: await getVoiceAutoSendFinal(),
|
||||||
|
socketTransportMode: await getSocketTransportMode(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +153,7 @@ class SettingsService {
|
|||||||
setVoiceLocaleId(settings.voiceLocaleId),
|
setVoiceLocaleId(settings.voiceLocaleId),
|
||||||
setVoiceHoldToTalk(settings.voiceHoldToTalk),
|
setVoiceHoldToTalk(settings.voiceHoldToTalk),
|
||||||
setVoiceAutoSendFinal(settings.voiceAutoSendFinal),
|
setVoiceAutoSendFinal(settings.voiceAutoSendFinal),
|
||||||
|
setSocketTransportMode(settings.socketTransportMode),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +192,18 @@ class SettingsService {
|
|||||||
await prefs.setBool(_voiceAutoSendKey, value);
|
await prefs.setBool(_voiceAutoSendKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transport mode: 'auto' (polling+websocket) or 'ws' (websocket only)
|
||||||
|
static Future<String> getSocketTransportMode() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString(_socketTransportModeKey) ?? 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> setSocketTransportMode(String mode) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
if (mode != 'auto' && mode != 'ws') mode = 'auto';
|
||||||
|
await prefs.setString(_socketTransportModeKey, mode);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get effective animation duration considering all settings
|
/// Get effective animation duration considering all settings
|
||||||
static Duration getEffectiveAnimationDuration(
|
static Duration getEffectiveAnimationDuration(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@@ -241,6 +257,7 @@ class AppSettings {
|
|||||||
final String? voiceLocaleId;
|
final String? voiceLocaleId;
|
||||||
final bool voiceHoldToTalk;
|
final bool voiceHoldToTalk;
|
||||||
final bool voiceAutoSendFinal;
|
final bool voiceAutoSendFinal;
|
||||||
|
final String socketTransportMode; // 'auto' or 'ws'
|
||||||
|
|
||||||
const AppSettings({
|
const AppSettings({
|
||||||
this.reduceMotion = false,
|
this.reduceMotion = false,
|
||||||
@@ -254,6 +271,7 @@ class AppSettings {
|
|||||||
this.voiceLocaleId,
|
this.voiceLocaleId,
|
||||||
this.voiceHoldToTalk = false,
|
this.voiceHoldToTalk = false,
|
||||||
this.voiceAutoSendFinal = false,
|
this.voiceAutoSendFinal = false,
|
||||||
|
this.socketTransportMode = 'auto',
|
||||||
});
|
});
|
||||||
|
|
||||||
AppSettings copyWith({
|
AppSettings copyWith({
|
||||||
@@ -268,6 +286,7 @@ class AppSettings {
|
|||||||
Object? voiceLocaleId = const _DefaultValue(),
|
Object? voiceLocaleId = const _DefaultValue(),
|
||||||
bool? voiceHoldToTalk,
|
bool? voiceHoldToTalk,
|
||||||
bool? voiceAutoSendFinal,
|
bool? voiceAutoSendFinal,
|
||||||
|
String? socketTransportMode,
|
||||||
}) {
|
}) {
|
||||||
return AppSettings(
|
return AppSettings(
|
||||||
reduceMotion: reduceMotion ?? this.reduceMotion,
|
reduceMotion: reduceMotion ?? this.reduceMotion,
|
||||||
@@ -281,6 +300,7 @@ class AppSettings {
|
|||||||
voiceLocaleId: voiceLocaleId is _DefaultValue ? this.voiceLocaleId : voiceLocaleId as String?,
|
voiceLocaleId: voiceLocaleId is _DefaultValue ? this.voiceLocaleId : voiceLocaleId as String?,
|
||||||
voiceHoldToTalk: voiceHoldToTalk ?? this.voiceHoldToTalk,
|
voiceHoldToTalk: voiceHoldToTalk ?? this.voiceHoldToTalk,
|
||||||
voiceAutoSendFinal: voiceAutoSendFinal ?? this.voiceAutoSendFinal,
|
voiceAutoSendFinal: voiceAutoSendFinal ?? this.voiceAutoSendFinal,
|
||||||
|
socketTransportMode: socketTransportMode ?? this.socketTransportMode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +319,7 @@ class AppSettings {
|
|||||||
other.voiceLocaleId == voiceLocaleId &&
|
other.voiceLocaleId == voiceLocaleId &&
|
||||||
other.voiceHoldToTalk == voiceHoldToTalk &&
|
other.voiceHoldToTalk == voiceHoldToTalk &&
|
||||||
other.voiceAutoSendFinal == voiceAutoSendFinal;
|
other.voiceAutoSendFinal == voiceAutoSendFinal;
|
||||||
|
// socketTransportMode intentionally not included in == to avoid frequent rebuilds
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -315,6 +336,7 @@ class AppSettings {
|
|||||||
voiceLocaleId,
|
voiceLocaleId,
|
||||||
voiceHoldToTalk,
|
voiceHoldToTalk,
|
||||||
voiceAutoSendFinal,
|
voiceAutoSendFinal,
|
||||||
|
socketTransportMode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,6 +412,11 @@ class AppSettingsNotifier extends StateNotifier<AppSettings> {
|
|||||||
await SettingsService.setVoiceAutoSendFinal(value);
|
await SettingsService.setVoiceAutoSendFinal(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setSocketTransportMode(String mode) async {
|
||||||
|
state = state.copyWith(socketTransportMode: mode);
|
||||||
|
await SettingsService.setSocketTransportMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> resetToDefaults() async {
|
Future<void> resetToDefaults() async {
|
||||||
const defaultSettings = AppSettings();
|
const defaultSettings = AppSettings();
|
||||||
await SettingsService.saveSettings(defaultSettings);
|
await SettingsService.saveSettings(defaultSettings);
|
||||||
|
|||||||
@@ -5,9 +5,14 @@ import '../models/server_config.dart';
|
|||||||
class SocketService {
|
class SocketService {
|
||||||
final ServerConfig serverConfig;
|
final ServerConfig serverConfig;
|
||||||
final String? authToken;
|
final String? authToken;
|
||||||
|
final bool websocketOnly;
|
||||||
io.Socket? _socket;
|
io.Socket? _socket;
|
||||||
|
|
||||||
SocketService({required this.serverConfig, required this.authToken});
|
SocketService({
|
||||||
|
required this.serverConfig,
|
||||||
|
required this.authToken,
|
||||||
|
this.websocketOnly = false,
|
||||||
|
});
|
||||||
|
|
||||||
String? get sessionId => _socket?.id;
|
String? get sessionId => _socket?.id;
|
||||||
io.Socket? get socket => _socket;
|
io.Socket? get socket => _socket;
|
||||||
@@ -24,20 +29,28 @@ class SocketService {
|
|||||||
final base = serverConfig.url.replaceFirst(RegExp(r'/+$'), '');
|
final base = serverConfig.url.replaceFirst(RegExp(r'/+$'), '');
|
||||||
final path = '/ws/socket.io';
|
final path = '/ws/socket.io';
|
||||||
|
|
||||||
_socket = io.io(
|
final builder = io.OptionBuilder()
|
||||||
base,
|
// Transport selection
|
||||||
io.OptionBuilder()
|
.setTransports(
|
||||||
.setTransports(['websocket'])
|
websocketOnly ? ['websocket'] : ['polling', 'websocket'],
|
||||||
.setPath(path)
|
|
||||||
.setExtraHeaders(
|
|
||||||
authToken != null && authToken!.isNotEmpty
|
|
||||||
? {
|
|
||||||
'Authorization': 'Bearer $authToken',
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
)
|
)
|
||||||
.build(),
|
.setRememberUpgrade(!websocketOnly)
|
||||||
);
|
.setUpgrade(!websocketOnly)
|
||||||
|
// Tune reconnect/backoff and timeouts
|
||||||
|
.setReconnectionAttempts(0) // 0/Infinity semantics: unlimited attempts
|
||||||
|
.setReconnectionDelay(1000)
|
||||||
|
.setReconnectionDelayMax(5000)
|
||||||
|
.setRandomizationFactor(0.5)
|
||||||
|
.setTimeout(20000)
|
||||||
|
.setPath(path);
|
||||||
|
|
||||||
|
if (authToken != null && authToken!.isNotEmpty) {
|
||||||
|
builder
|
||||||
|
.setAuth({'token': authToken})
|
||||||
|
.setExtraHeaders({'Authorization': 'Bearer $authToken'});
|
||||||
|
}
|
||||||
|
|
||||||
|
_socket = io.io(base, builder.build());
|
||||||
|
|
||||||
_socket!.on('connect', (_) {
|
_socket!.on('connect', (_) {
|
||||||
debugPrint('Socket connected: ${_socket!.id}');
|
debugPrint('Socket connected: ${_socket!.id}');
|
||||||
@@ -52,6 +65,24 @@ class SocketService {
|
|||||||
debugPrint('Socket connect_error: $err');
|
debugPrint('Socket connect_error: $err');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_socket!.on('reconnect_attempt', (attempt) {
|
||||||
|
debugPrint('Socket reconnect_attempt: $attempt');
|
||||||
|
});
|
||||||
|
|
||||||
|
_socket!.on('reconnect', (attempt) {
|
||||||
|
debugPrint('Socket reconnected after $attempt attempts');
|
||||||
|
if (authToken != null && authToken!.isNotEmpty) {
|
||||||
|
// Best-effort rejoin
|
||||||
|
_socket!.emit('user-join', {
|
||||||
|
'auth': {'token': authToken}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_socket!.on('reconnect_failed', (_) {
|
||||||
|
debugPrint('Socket reconnect_failed');
|
||||||
|
});
|
||||||
|
|
||||||
_socket!.on('disconnect', (reason) {
|
_socket!.on('disconnect', (reason) {
|
||||||
debugPrint('Socket disconnected: $reason');
|
debugPrint('Socket disconnected: $reason');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,10 +55,27 @@ class ChatMessagesNotifier extends StateNotifier<List<ChatMessage>> {
|
|||||||
// locally streamed assistant content with an outdated server copy.
|
// locally streamed assistant content with an outdated server copy.
|
||||||
if (previous?.updatedAt != next?.updatedAt) {
|
if (previous?.updatedAt != next?.updatedAt) {
|
||||||
final serverMessages = next?.messages ?? const [];
|
final serverMessages = next?.messages ?? const [];
|
||||||
// Only replace local messages if the server has strictly more messages
|
// Primary rule: adopt server messages when there are strictly more of them.
|
||||||
// (i.e., includes new content we don't have yet).
|
|
||||||
if (serverMessages.length > state.length) {
|
if (serverMessages.length > state.length) {
|
||||||
state = serverMessages;
|
state = serverMessages;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary rule: if counts are equal but the last assistant message grew,
|
||||||
|
// adopt the server copy to recover from missed socket events.
|
||||||
|
if (serverMessages.isNotEmpty && state.isNotEmpty) {
|
||||||
|
final serverLast = serverMessages.last;
|
||||||
|
final localLast = state.last;
|
||||||
|
final serverText = serverLast.content.trim();
|
||||||
|
final localText = localLast.content.trim();
|
||||||
|
final sameLastId = serverLast.id == localLast.id;
|
||||||
|
final isAssistant = serverLast.role == 'assistant';
|
||||||
|
final serverHasMore = serverText.isNotEmpty && serverText.length > localText.length;
|
||||||
|
final localEmptyButServerHas = localText.isEmpty && serverText.isNotEmpty;
|
||||||
|
if (sameLastId && isAssistant && (serverHasMore || localEmptyButServerHas)) {
|
||||||
|
state = serverMessages;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1409,15 +1409,20 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
|||||||
final full = await api.getConversation(active.id);
|
final full = await api.getConversation(active.id);
|
||||||
ref
|
ref
|
||||||
.read(activeConversationProvider.notifier)
|
.read(activeConversationProvider.notifier)
|
||||||
.state =
|
.state = full;
|
||||||
full;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint(
|
debugPrint('DEBUG: Failed to refresh conversation: $e');
|
||||||
'DEBUG: Failed to refresh conversation: $e',
|
|
||||||
);
|
|
||||||
// Could show a snackbar here if needed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also refresh the conversations list to reconcile missed events
|
||||||
|
// and keep timestamps/order in sync with the server.
|
||||||
|
try {
|
||||||
|
ref.invalidate(conversationsProvider);
|
||||||
|
// Best-effort await to stabilize UI; ignore errors.
|
||||||
|
await ref.read(conversationsProvider.future);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
// Add small delay for better UX feedback
|
// Add small delay for better UX feedback
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(Spacing.pagePadding),
|
padding: const EdgeInsets.all(Spacing.pagePadding),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -102,6 +102,110 @@ class AppCustomizationPage extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: Spacing.lg),
|
||||||
|
Text(
|
||||||
|
'Realtime',
|
||||||
|
style: context.conduitTheme.headingSmall?.copyWith(
|
||||||
|
color: context.conduitTheme.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: Spacing.md),
|
||||||
|
ConduitCard(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: Spacing.listItemPadding,
|
||||||
|
vertical: Spacing.sm,
|
||||||
|
),
|
||||||
|
leading: Container(
|
||||||
|
padding: const EdgeInsets.all(Spacing.sm),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.conduitTheme.buttonPrimary
|
||||||
|
.withValues(alpha: Alpha.highlight),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(AppBorderRadius.small),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Platform.isIOS
|
||||||
|
? CupertinoIcons.waveform
|
||||||
|
: Icons.sync_alt,
|
||||||
|
color: context.conduitTheme.buttonPrimary,
|
||||||
|
size: IconSize.medium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
'Transport mode',
|
||||||
|
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||||
|
color: context.conduitTheme.textPrimary,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
'Choose how the app connects for realtime updates.',
|
||||||
|
style: context.conduitTheme.bodySmall?.copyWith(
|
||||||
|
color: context.conduitTheme.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
Spacing.listItemPadding,
|
||||||
|
0,
|
||||||
|
Spacing.listItemPadding,
|
||||||
|
Spacing.md,
|
||||||
|
),
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
initialValue: settings.socketTransportMode,
|
||||||
|
onChanged: (v) async {
|
||||||
|
if (v == null) return;
|
||||||
|
await ref
|
||||||
|
.read(appSettingsProvider.notifier)
|
||||||
|
.setSocketTransportMode(v);
|
||||||
|
},
|
||||||
|
items: const [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 'auto',
|
||||||
|
child: Text('Auto (Polling + WebSocket)'),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 'ws',
|
||||||
|
child: Text('WebSocket only'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Mode',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
Spacing.listItemPadding,
|
||||||
|
0,
|
||||||
|
Spacing.listItemPadding,
|
||||||
|
Spacing.md,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
settings.socketTransportMode == 'auto'
|
||||||
|
? 'More robust on restrictive networks. Upgrades to WebSocket when possible.'
|
||||||
|
: 'Lower overhead, but may fail behind strict proxies/firewalls.',
|
||||||
|
style: context.conduitTheme.caption?.copyWith(
|
||||||
|
color: context.conduitTheme.textSecondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
192
pubspec.lock
192
pubspec.lock
@@ -13,10 +13,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c
|
sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.6.0"
|
version: "7.7.1"
|
||||||
ansicolor:
|
ansicolor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -61,18 +61,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "3.1.0"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_config
|
name: build_config
|
||||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.2.0"
|
||||||
build_daemon:
|
build_daemon:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -85,26 +85,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "3.0.3"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.7.1"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.2"
|
version: "9.3.1"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -117,10 +117,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62"
|
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.11.0"
|
version: "8.12.0"
|
||||||
cached_network_image:
|
cached_network_image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -245,10 +245,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.8.0+1"
|
version: "5.9.0"
|
||||||
dio_web_adapter:
|
dio_web_adapter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -285,10 +285,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "13ba4e627ef24503a465d1d61b32596ce10eb6b8903678d362a528f9939b4aa8"
|
sha256: e7e16c9d15c36330b94ca0e2ad8cb61f93cd5282d0158c09805aed13b5452f22
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.2.1"
|
version: "10.3.2"
|
||||||
file_selector_linux:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -301,10 +301,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_macos
|
name: file_selector_macos
|
||||||
sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711"
|
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.4+3"
|
version: "0.9.4+4"
|
||||||
file_selector_platform_interface:
|
file_selector_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -383,10 +383,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e
|
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.28"
|
version: "2.0.30"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -465,10 +465,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: "2d399f823b8849663744d2a9ddcce01c49268fb4170d0442a655bf6a2f47be22"
|
sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.2.0"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -521,10 +521,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.5.0"
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -553,66 +553,66 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.2.0"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d"
|
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12+24"
|
version: "0.8.13+1"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_for_web
|
name: image_picker_for_web
|
||||||
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
version: "3.1.0"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.12+2"
|
version: "0.8.13"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_linux
|
name: image_picker_linux
|
||||||
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+2"
|
version: "0.2.2"
|
||||||
image_picker_macos:
|
image_picker_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_macos
|
name: image_picker_macos
|
||||||
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+2"
|
version: "0.2.2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.10.1"
|
version: "2.11.0"
|
||||||
image_picker_windows:
|
image_picker_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_windows
|
name: image_picker_windows
|
||||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.2"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -649,10 +649,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.9.5"
|
version: "6.11.1"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -761,18 +761,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
|
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.0"
|
version: "8.3.1"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_info_plus_platform_interface
|
name: package_info_plus_platform_interface
|
||||||
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
|
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.1"
|
||||||
path:
|
path:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -793,18 +793,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
|
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.17"
|
version: "2.2.18"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -833,10 +833,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.0"
|
version: "7.0.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -889,66 +889,66 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: record
|
name: record
|
||||||
sha256: daeb3f9b3fea9797094433fe6e49a879d8e4ca4207740bc6dc7e4a58764f0817
|
sha256: "9dbc6ff3e784612f90a9b001373c45ff76b7a08abd2bd9fdf72c242320c8911c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.1.1"
|
||||||
record_android:
|
record_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_android
|
name: record_android
|
||||||
sha256: "97d7122455f30de89a01c6c244c839085be6b12abca251fc0e78f67fed73628b"
|
sha256: "8361a791c9a3fa5c065f0b8b5adb10f12531f8538c86b19474cf7b56ea80d426"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.4.1"
|
||||||
record_ios:
|
record_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_ios
|
name: record_ios
|
||||||
sha256: "73706ebbece6150654c9d6f57897cf9b622c581148304132ba85dba15df0fdfb"
|
sha256: "13e241ed9cbc220534a40ae6b66222e21288db364d96dd66fb762ebd3cb77c71"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.1.2"
|
||||||
record_linux:
|
record_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_linux
|
name: record_linux
|
||||||
sha256: "0626678a092c75ce6af1e32fe7fd1dea709b92d308bc8e3b6d6348e2430beb95"
|
sha256: "235b1f1fb84e810f8149cc0c2c731d7d697f8d1c333b32cb820c449bf7bb72d8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.2.1"
|
||||||
record_macos:
|
record_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_macos
|
name: record_macos
|
||||||
sha256: "02240833fde16c33fcf2c589f3e08d4394b704761b4a3bb609d872ff3043fbbd"
|
sha256: "2849068bb59072f300ad63ed146e543d66afaef8263edba4de4834fc7c8d4d35"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.1.1"
|
||||||
record_platform_interface:
|
record_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_platform_interface
|
name: record_platform_interface
|
||||||
sha256: c1ad38f51e4af88a085b3e792a22c685cb3e7c23fc37aa7ce44c4cf18f25fe89
|
sha256: b0065fdf1ec28f5a634d676724d388a77e43ce7646fb049949f58c69f3fcb4ed
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.4.0"
|
||||||
record_web:
|
record_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_web
|
name: record_web
|
||||||
sha256: a12856d0b3dd03d336b4b10d7520a8b3e21649a06a8f95815318feaa8f07adbb
|
sha256: "4f0adf20c9ccafcc02d71111fd91fba1ca7b17a7453902593e5a9b25b74a5c56"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.9"
|
version: "1.2.0"
|
||||||
record_windows:
|
record_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_windows
|
name: record_windows
|
||||||
sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99"
|
sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.7"
|
||||||
riverpod:
|
riverpod:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1033,10 +1033,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
|
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.10"
|
version: "2.4.12"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1102,34 +1102,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: socket_io_client
|
name: socket_io_client
|
||||||
sha256: ede469f3e4c55e8528b4e023bdedbc20832e8811ab9b61679d1ba3ed5f01f23b
|
sha256: c8471c2c6843cf308a5532ff653f2bcdb7fa9ae79d84d1179920578a06624f0d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3+1"
|
version: "3.1.2"
|
||||||
socket_io_common:
|
socket_io_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: socket_io_common
|
name: socket_io_common
|
||||||
sha256: "2ab92f8ff3ebbd4b353bf4a98bee45cc157e3255464b2f90f66e09c4472047eb"
|
sha256: "162fbaecbf4bf9a9372a62a341b3550b51dcef2f02f3e5830a297fd48203d45b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.3"
|
version: "3.1.1"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "3.1.0"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_helper
|
name: source_helper
|
||||||
sha256: "4f81479fe5194a622cdd1713fe1ecb683a6e6c85cd8cec8e2e35ee5ab3fdf2a1"
|
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.6"
|
version: "1.3.8"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1158,10 +1158,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_android
|
name: sqflite_android
|
||||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2+2"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1310,18 +1310,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "0aedad096a85b49df2e4725fa32118f9fa580f3b14af7a2d2221896a02cd5656"
|
sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.17"
|
version: "6.3.18"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
|
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.3"
|
version: "6.3.4"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1334,10 +1334,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.2"
|
version: "3.2.3"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1390,10 +1390,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.0.0"
|
version: "15.0.2"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1414,10 +1414,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.3"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1462,10 +1462,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.0"
|
version: "6.6.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1475,5 +1475,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.8.0 <4.0.0"
|
dart: ">=3.9.0 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.29.0"
|
||||||
|
|||||||
18
pubspec.yaml
18
pubspec.yaml
@@ -16,7 +16,7 @@ dependencies:
|
|||||||
flutter_riverpod: ^2.6.1
|
flutter_riverpod: ^2.6.1
|
||||||
|
|
||||||
# Network & API
|
# Network & API
|
||||||
dio: ^5.5.0
|
dio: ^5.9.0
|
||||||
http_parser: ^4.0.2
|
http_parser: ^4.0.2
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ dependencies:
|
|||||||
markdown_widget: ^2.3.2+8
|
markdown_widget: ^2.3.2+8
|
||||||
flutter_highlight: ^0.7.0
|
flutter_highlight: ^0.7.0
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
socket_io_client: ^2.0.3
|
socket_io_client: ^3.1.2
|
||||||
yaml: ^3.1.2
|
yaml: ^3.1.2
|
||||||
|
|
||||||
|
|
||||||
@@ -37,17 +37,17 @@ dependencies:
|
|||||||
flutter_animate: ^4.5.0
|
flutter_animate: ^4.5.0
|
||||||
|
|
||||||
# Platform Features
|
# Platform Features
|
||||||
record: ^6.0.0
|
record: ^6.1.1
|
||||||
stts: ^1.2.5
|
stts: ^1.2.5
|
||||||
image_picker: ^1.1.2
|
image_picker: ^1.2.0
|
||||||
file_picker: ^10.2.1
|
file_picker: ^10.3.2
|
||||||
path_provider: ^2.1.4
|
path_provider: ^2.1.4
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
uuid: ^4.5.0
|
uuid: ^4.5.0
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
package_info_plus: ^8.0.2
|
package_info_plus: ^8.3.1
|
||||||
url_launcher: ^6.3.0
|
url_launcher: ^6.3.0
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
|
|
||||||
@@ -65,9 +65,9 @@ dev_dependencies:
|
|||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
build_runner: ^2.4.11
|
build_runner: ^2.7.1
|
||||||
freezed: ^3.0.0
|
freezed: ^3.2.0
|
||||||
json_serializable: ^6.8.0
|
json_serializable: ^6.11.1
|
||||||
flutter_native_splash: ^2.4.6
|
flutter_native_splash: ^2.4.6
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
|
|||||||
Reference in New Issue
Block a user