mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
Send errors with description from native side, only show app zaps
This commit is contained in:
+18
-6
@@ -287,7 +287,8 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
|
|||||||
appId: String,
|
appId: String,
|
||||||
status: String,
|
status: String,
|
||||||
message: String? = null,
|
message: String? = null,
|
||||||
errorCode: String? = null
|
errorCode: String? = null,
|
||||||
|
description: String? = null
|
||||||
) {
|
) {
|
||||||
val emitNow = emit@{
|
val emitNow = emit@{
|
||||||
// Update watchdog regardless of whether Dart is listening.
|
// Update watchdog regardless of whether Dart is listening.
|
||||||
@@ -310,6 +311,9 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
|
|||||||
if (errorCode != null) {
|
if (errorCode != null) {
|
||||||
event["errorCode"] = errorCode
|
event["errorCode"] = errorCode
|
||||||
}
|
}
|
||||||
|
if (description != null) {
|
||||||
|
event["description"] = description
|
||||||
|
}
|
||||||
Log.d(TAG, "Emitting to Dart: $event")
|
Log.d(TAG, "Emitting to Dart: $event")
|
||||||
sink.success(event)
|
sink.success(event)
|
||||||
}
|
}
|
||||||
@@ -496,8 +500,9 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
|
|||||||
emitInstallStatus(
|
emitInstallStatus(
|
||||||
packageName,
|
packageName,
|
||||||
InstallStatus.FAILED,
|
InstallStatus.FAILED,
|
||||||
verification.errorMessage,
|
verification.errorTitle,
|
||||||
verification.errorCode
|
verification.errorCode,
|
||||||
|
verification.errorDescription
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
verificationThreads.remove(packageName)
|
verificationThreads.remove(packageName)
|
||||||
@@ -648,14 +653,20 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
|
|||||||
|
|
||||||
private data class VerificationResult(
|
private data class VerificationResult(
|
||||||
val isSuccess: Boolean,
|
val isSuccess: Boolean,
|
||||||
val errorMessage: String = "",
|
val errorTitle: String = "",
|
||||||
|
val errorDescription: String? = null,
|
||||||
val errorCode: String? = null
|
val errorCode: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun verifyApk(file: File, expectedHash: String, expectedSize: Long): VerificationResult {
|
private fun verifyApk(file: File, expectedHash: String, expectedSize: Long): VerificationResult {
|
||||||
if (!isValidApkFormat(file)) {
|
if (!isValidApkFormat(file)) {
|
||||||
file.delete()
|
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")
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
@@ -671,7 +682,8 @@ class AndroidPackageManagerPlugin : FlutterPlugin, MethodCallHandler,
|
|||||||
if (actualHash.lowercase() != expectedHash.lowercase()) {
|
if (actualHash.lowercase() != expectedHash.lowercase()) {
|
||||||
return VerificationResult(
|
return VerificationResult(
|
||||||
false,
|
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
|
ErrorCode.HASH_MISMATCH
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,24 +205,22 @@ class _UserZapsList extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// Collect addressable tags for apps and metadata IDs separately
|
// Don't query zaps if user has no apps
|
||||||
final allAppTags = <String, Set<String>>{};
|
if (apps.isEmpty) {
|
||||||
final metadataIds = <String>{};
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect addressable tags for apps
|
||||||
|
final allAppTags = <String, Set<String>>{};
|
||||||
for (final app in apps) {
|
for (final app in apps) {
|
||||||
final appTags = app.event.addressableIdTagMap;
|
final appTags = app.event.addressableIdTagMap;
|
||||||
for (final entry in appTags.entries) {
|
for (final entry in appTags.entries) {
|
||||||
allAppTags[entry.key] = {...?allAppTags[entry.key], ...entry.value};
|
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
|
// Query zaps on apps (via #a tag)
|
||||||
final appZapsState = ref.watch(
|
final zapsState = ref.watch(
|
||||||
query<Zap>(
|
query<Zap>(
|
||||||
tags: allAppTags,
|
tags: allAppTags,
|
||||||
source: const LocalAndRemoteSource(relays: 'social'),
|
source: const LocalAndRemoteSource(relays: 'social'),
|
||||||
@@ -230,17 +228,7 @@ class _UserZapsList extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Query zaps on metadata (via #e tag) - separate query
|
final allZaps = zapsState.models;
|
||||||
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};
|
|
||||||
|
|
||||||
if (allZaps.isEmpty) {
|
if (allZaps.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
|
|||||||
@@ -99,10 +99,11 @@ final class AndroidPackageManager extends PackageManager {
|
|||||||
final statusRaw = event['status'] as String?;
|
final statusRaw = event['status'] as String?;
|
||||||
final message = event['message'] as String?;
|
final message = event['message'] as String?;
|
||||||
final errorCode = event['errorCode'] as String?;
|
final errorCode = event['errorCode'] as String?;
|
||||||
|
final description = event['description'] as String?;
|
||||||
final status = InstallStatusX.tryParse(statusRaw);
|
final status = InstallStatusX.tryParse(statusRaw);
|
||||||
|
|
||||||
debugPrint(
|
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) {
|
if (appId == null || statusRaw == null) {
|
||||||
@@ -196,6 +197,7 @@ final class AndroidPackageManager extends PackageManager {
|
|||||||
target: target,
|
target: target,
|
||||||
type: _errorCodeToFailureType(errorCode, message),
|
type: _errorCodeToFailureType(errorCode, message),
|
||||||
message: message ?? 'Installation failed',
|
message: message ?? 'Installation failed',
|
||||||
|
description: description,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -132,12 +132,14 @@ class Uninstalling extends InstallOperation {
|
|||||||
class OperationFailed extends InstallOperation {
|
class OperationFailed extends InstallOperation {
|
||||||
final FailureType type;
|
final FailureType type;
|
||||||
final String message;
|
final String message;
|
||||||
|
final String? description;
|
||||||
final String? filePath;
|
final String? filePath;
|
||||||
|
|
||||||
const OperationFailed({
|
const OperationFailed({
|
||||||
required super.target,
|
required super.target,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.message,
|
required this.message,
|
||||||
|
this.description,
|
||||||
this.filePath,
|
this.filePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -275,24 +275,14 @@ class _ZappersListSection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final latestMetadata = app.latestFileMetadata;
|
return _ZappersListSectionWithMetadata(app: app);
|
||||||
if (latestMetadata == null) return const SizedBox.shrink();
|
|
||||||
|
|
||||||
return _ZappersListSectionWithMetadata(
|
|
||||||
app: app,
|
|
||||||
metadataId: latestMetadata.id,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ZappersListSectionWithMetadata extends ConsumerWidget {
|
class _ZappersListSectionWithMetadata extends ConsumerWidget {
|
||||||
const _ZappersListSectionWithMetadata({
|
const _ZappersListSectionWithMetadata({required this.app});
|
||||||
required this.app,
|
|
||||||
required this.metadataId,
|
|
||||||
});
|
|
||||||
|
|
||||||
final App app;
|
final App app;
|
||||||
final String metadataId;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -305,19 +295,7 @@ class _ZappersListSectionWithMetadata extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Query zaps on metadata (via #e tag)
|
final allZaps = zapsState.models;
|
||||||
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};
|
|
||||||
|
|
||||||
if (allZaps.isEmpty) return const SizedBox.shrink();
|
if (allZaps.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
|||||||
@@ -604,7 +604,11 @@ class InstallButton extends ConsumerWidget {
|
|||||||
final operation = ref.read(installOperationProvider(app.identifier));
|
final operation = ref.read(installOperationProvider(app.identifier));
|
||||||
if (operation is! OperationFailed) return;
|
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).
|
// Always clear error after showing it (reckless mode removed).
|
||||||
final pm = ref.read(packageManagerProvider.notifier);
|
final pm = ref.read(packageManagerProvider.notifier);
|
||||||
@@ -617,6 +621,9 @@ class InstallButton extends ConsumerWidget {
|
|||||||
'Certificate mismatch',
|
'Certificate mismatch',
|
||||||
description: 'The app signature does not match. Force update required.',
|
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
|
// Other errors are shown when user taps the error button
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user