feat(ios): Add App Intents support for Conduit interactions

This commit is contained in:
cogwheel0
2025-11-25 00:53:13 +05:30
parent 000b25e515
commit 6d56f5d160
8 changed files with 175 additions and 4 deletions

View File

@@ -362,6 +362,20 @@ class AppIntentCoordinator extends _$AppIntentCoordinator {
);
}
Future<void> openChatFromExternal({
String? prompt,
bool focusComposer = false,
bool resetChat = false,
}) {
return _prepareChatWithOptions(
prompt: prompt,
focusComposer: focusComposer,
resetChat: resetChat,
);
}
Future<void> startVoiceCallFromExternal() => _startVoiceCall();
Future<void> _prepareChatWithOptions({
String? prompt,
bool focusComposer = false,
@@ -412,7 +426,7 @@ class AppIntentCoordinator extends _$AppIntentCoordinator {
await navigator.push(
MaterialPageRoute(
builder: (_) => const VoiceCallPage(),
builder: (_) => const VoiceCallPage(startNewConversation: true),
fullscreenDialog: true,
),
);

View File

@@ -0,0 +1,108 @@
import 'dart:async';
import 'dart:io';
import 'package:conduit/l10n/app_localizations.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../providers/app_providers.dart';
import '../utils/debug_logger.dart';
import 'app_intents_service.dart';
import 'navigation_service.dart';
part 'quick_actions_service.g.dart';
const _quickActionNewChat = 'conduit_new_chat';
const _quickActionVoiceCall = 'conduit_voice_call';
@Riverpod(keepAlive: true)
class QuickActionsCoordinator extends _$QuickActionsCoordinator {
final QuickActions _quickActions = const QuickActions();
@override
FutureOr<void> build() {
if (kIsWeb) return Future<void>.value();
if (!Platform.isIOS && !Platform.isAndroid) {
return Future<void>.value();
}
_quickActions.initialize(_handleAction);
unawaited(_setShortcuts());
ref.listen<Locale?>(appLocaleProvider, (prev, next) {
unawaited(_setShortcuts());
});
}
Future<void> _setShortcuts() async {
final titles = _resolveTitles();
try {
await _quickActions.setShortcutItems([
ShortcutItem(type: _quickActionNewChat, localizedTitle: titles.newChat),
ShortcutItem(
type: _quickActionVoiceCall,
localizedTitle: titles.voiceCall,
),
]);
} catch (error, stackTrace) {
DebugLogger.error(
'quick-actions-register',
scope: 'platform',
error: error,
stackTrace: stackTrace,
);
}
}
_QuickActionTitles _resolveTitles() {
final context = NavigationService.context;
final l10n = context != null ? AppLocalizations.of(context) : null;
return _QuickActionTitles(
newChat: l10n?.newChat ?? 'New Chat',
voiceCall: l10n?.voiceCallTitle ?? 'Voice Call',
);
}
void _handleAction(String type) {
unawaited(_handleActionAsync(type));
}
Future<void> _handleActionAsync(String? type) async {
if (type == null || type.isEmpty) return;
await Future<void>.delayed(const Duration(milliseconds: 16));
switch (type) {
case _quickActionNewChat:
await ref
.read(appIntentCoordinatorProvider.notifier)
.openChatFromExternal(focusComposer: true, resetChat: true);
break;
case _quickActionVoiceCall:
try {
await ref
.read(appIntentCoordinatorProvider.notifier)
.startVoiceCallFromExternal();
} catch (error, stackTrace) {
DebugLogger.error(
'quick-actions-voice',
scope: 'platform',
error: error,
stackTrace: stackTrace,
);
}
break;
default:
DebugLogger.info('Unknown quick action: $type');
}
}
}
class _QuickActionTitles {
const _QuickActionTitles({required this.newChat, required this.voiceCall});
final String newChat;
final String voiceCall;
}