Send errors with description from native side, only show app zaps

This commit is contained in:
franzap
2025-12-25 20:35:00 -03:00
parent fc566a4345
commit e4a2634a44
6 changed files with 43 additions and 54 deletions
@@ -287,7 +287,8 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
appId: String,
status: String,
message: String? = null,
errorCode: String? = null
errorCode: String? = null,
description: String? = null
) {
val emitNow = emit@{
// Update watchdog regardless of whether Dart is listening.
@@ -310,6 +311,9 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
if (errorCode != null) {
event["errorCode"] = errorCode
}
if (description != null) {
event["description"] = description
}
Log.d(TAG, "Emitting to Dart: $event")
sink.success(event)
}
@@ -496,8 +500,9 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
emitInstallStatus(
packageName,
InstallStatus.FAILED,
verification.errorMessage,
verification.errorCode
verification.errorTitle,
verification.errorCode,
verification.errorDescription
)
}
verificationThreads.remove(packageName)
@@ -648,14 +653,20 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
private data class VerificationResult(
val isSuccess: Boolean,
val errorMessage: String = "",
val errorTitle: String = "",
val errorDescription: String? = null,
val errorCode: String? = null
)
private fun verifyApk(file: File, expectedHash: String, expectedSize: Long): VerificationResult {
if (!isValidApkFormat(file)) {
file.delete()
return VerificationResult(false, "Invalid APK file format", ErrorCode.INVALID_FILE)
return VerificationResult(
false,
"Invalid APK file",
"The downloaded file is not a valid APK format.",
ErrorCode.INVALID_FILE
)
}
val digest = MessageDigest.getInstance("SHA-256")
@@ -671,7 +682,8 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
if (actualHash.lowercase() != expectedHash.lowercase()) {
return VerificationResult(
false,
"Hash verification failed. Expected: $expectedHash, Actual: $actualHash",
"Hash verification failed",
"The downloaded file hash does not match.\n\nExpected: $expectedHash\nActual: $actualHash",
ErrorCode.HASH_MISMATCH
)
}
+9 -21
View File
@@ -205,24 +205,22 @@ class _UserZapsList extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// Collect addressable tags for apps and metadata IDs separately
final allAppTags = <String, Set<String>>{};
final metadataIds = <String>{};
// Don't query zaps if user has no apps
if (apps.isEmpty) {
return const SizedBox.shrink();
}
// Collect addressable tags for apps
final allAppTags = <String, Set<String>>{};
for (final app in apps) {
final appTags = app.event.addressableIdTagMap;
for (final entry in appTags.entries) {
allAppTags[entry.key] = {...?allAppTags[entry.key], ...entry.value};
}
final metadata = app.latestFileMetadata;
if (metadata != null) {
metadataIds.add(metadata.id);
}
}
// Query zaps on apps (via #a tag) - separate from metadata zaps
final appZapsState = ref.watch(
// Query zaps on apps (via #a tag)
final zapsState = ref.watch(
query<Zap>(
tags: allAppTags,
source: const LocalAndRemoteSource(relays: 'social'),
@@ -230,17 +228,7 @@ class _UserZapsList extends HookConsumerWidget {
),
);
// Query zaps on metadata (via #e tag) - separate query
final metadataZapsState = ref.watch(
query<Zap>(
tags: {'#e': metadataIds},
source: const LocalAndRemoteSource(relays: 'social'),
subscriptionPrefix: 'user-metadata-zaps',
),
);
// Combine zaps from both queries
final allZaps = {...appZapsState.models, ...metadataZapsState.models};
final allZaps = zapsState.models;
if (allZaps.isEmpty) {
return const SizedBox.shrink();
@@ -99,10 +99,11 @@ final class AndroidPackageManager extends PackageManager {
final statusRaw = event['status'] as String?;
final message = event['message'] as String?;
final errorCode = event['errorCode'] as String?;
final description = event['description'] as String?;
final status = InstallStatusX.tryParse(statusRaw);
debugPrint(
'[PackageManager] Received event: appId=$appId, status=$status, msg=$message, errorCode=$errorCode',
'[PackageManager] Received event: appId=$appId, status=$status, msg=$message, errorCode=$errorCode, desc=$description',
);
if (appId == null || statusRaw == null) {
@@ -196,6 +197,7 @@ final class AndroidPackageManager extends PackageManager {
target: target,
type: _errorCodeToFailureType(errorCode, message),
message: message ?? 'Installation failed',
description: description,
filePath: filePath,
),
);
@@ -132,12 +132,14 @@ class Uninstalling extends InstallOperation {
class OperationFailed extends InstallOperation {
final FailureType type;
final String message;
final String? description;
final String? filePath;
const OperationFailed({
required super.target,
required this.type,
required this.message,
this.description,
this.filePath,
});
+3 -25
View File
@@ -275,24 +275,14 @@ class _ZappersListSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final latestMetadata = app.latestFileMetadata;
if (latestMetadata == null) return const SizedBox.shrink();
return _ZappersListSectionWithMetadata(
app: app,
metadataId: latestMetadata.id,
);
return _ZappersListSectionWithMetadata(app: app);
}
}
class _ZappersListSectionWithMetadata extends ConsumerWidget {
const _ZappersListSectionWithMetadata({
required this.app,
required this.metadataId,
});
const _ZappersListSectionWithMetadata({required this.app});
final App app;
final String metadataId;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -305,19 +295,7 @@ class _ZappersListSectionWithMetadata extends ConsumerWidget {
),
);
// Query zaps on metadata (via #e tag)
final zapsOnMetadataState = ref.watch(
query<Zap>(
tags: {
'#e': {metadataId},
},
source: const LocalAndRemoteSource(relays: 'social'),
subscriptionPrefix: 'metadata-zaps',
),
);
// Combine zaps from both queries
final allZaps = {...zapsState.models, ...zapsOnMetadataState.models};
final allZaps = zapsState.models;
if (allZaps.isEmpty) return const SizedBox.shrink();
+8 -1
View File
@@ -604,7 +604,11 @@ class InstallButton extends ConsumerWidget {
final operation = ref.read(installOperationProvider(app.identifier));
if (operation is! OperationFailed) return;
context.showError(operation.message, actions: const []);
context.showError(
operation.message,
description: operation.description,
actions: const [],
);
// Always clear error after showing it (reckless mode removed).
final pm = ref.read(packageManagerProvider.notifier);
@@ -617,6 +621,9 @@ class InstallButton extends ConsumerWidget {
'Certificate mismatch',
description: 'The app signature does not match. Force update required.',
);
} else if (operation.description != null) {
// Errors with descriptions (from Kotlin) are shown as toasts
context.showError(operation.message, description: operation.description);
}
// Other errors are shown when user taps the error button
}