mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
220 lines
6.8 KiB
Dart
220 lines
6.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:models/models.dart';
|
|
import 'package:zapstore/utils/extensions.dart';
|
|
import '../services/package_manager/package_manager.dart';
|
|
import '../theme.dart';
|
|
|
|
/// Version pill widget showing the current release version
|
|
/// Displays version in a colored pill format similar to the old design
|
|
/// Can also show dual versions (installed vs available) for updates
|
|
class VersionPillWidget extends HookConsumerWidget {
|
|
final App app;
|
|
final bool showUpdateArrow;
|
|
final String? forceVersion;
|
|
|
|
const VersionPillWidget({
|
|
super.key,
|
|
required this.app,
|
|
this.showUpdateArrow = false,
|
|
this.forceVersion,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
// Watch package manager state for reactivity
|
|
final installedPackages = ref.watch(packageManagerProvider);
|
|
|
|
// Resolve installed and available versions using AppExt
|
|
final installedVersion = app.installedPackage?.version;
|
|
final availableVersion = app.latestFileMetadata?.version;
|
|
final updateAvailable = app.hasUpdate;
|
|
final downgradeAvailable = app.hasDowngrade;
|
|
|
|
// Dual version mode (when an update OR downgrade is available)
|
|
if (showUpdateArrow &&
|
|
(updateAvailable || downgradeAvailable) &&
|
|
installedVersion != null &&
|
|
availableVersion != null) {
|
|
return _buildDualVersionPills(
|
|
context,
|
|
ref,
|
|
installedPackages,
|
|
installedVersion,
|
|
availableVersion,
|
|
isDowngrade: downgradeAvailable,
|
|
);
|
|
}
|
|
|
|
// Single version mode: use forced version if provided, otherwise show installed version if installed, otherwise available version
|
|
final version = forceVersion ?? installedVersion ?? availableVersion;
|
|
|
|
if (version == null || version.isEmpty) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
return _buildVersionPill(
|
|
context,
|
|
ref,
|
|
installedPackages,
|
|
version,
|
|
AppColors.darkPillBackground,
|
|
Colors.white,
|
|
);
|
|
}
|
|
|
|
Widget _buildDualVersionPills(
|
|
BuildContext context,
|
|
WidgetRef ref,
|
|
List<PackageInfo> installedPackages,
|
|
String installedVersion,
|
|
String availableVersion, {
|
|
bool isDowngrade = false,
|
|
}) {
|
|
// Get version codes
|
|
final installedVersionCode = app.installedPackage?.versionCode;
|
|
final availableVersionCode = app.latestFileMetadata?.versionCode;
|
|
|
|
// When version strings are equal but version codes differ, show version codes in parentheses
|
|
final showVersionCodes = installedVersion == availableVersion &&
|
|
installedVersionCode != null &&
|
|
availableVersionCode != null &&
|
|
installedVersionCode != availableVersionCode;
|
|
|
|
return Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Current version pill (muted colors for installed version)
|
|
Flexible(
|
|
child: _buildVersionPill(
|
|
context,
|
|
ref,
|
|
installedPackages,
|
|
installedVersion,
|
|
Theme.of(context).colorScheme.outline.withValues(alpha: 0.3),
|
|
Theme.of(context).colorScheme.onSurface,
|
|
isInstalledVersion: true,
|
|
versionCode: showVersionCodes ? installedVersionCode : null,
|
|
),
|
|
),
|
|
|
|
// Arrow icon (always arrow, forbidden icon is in the pill itself)
|
|
Icon(
|
|
Icons.arrow_right,
|
|
size: 16,
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
|
|
// Available version pill (highlighted for update, greyed for downgrade)
|
|
Flexible(
|
|
child: _buildVersionPill(
|
|
context,
|
|
ref,
|
|
installedPackages,
|
|
availableVersion,
|
|
isDowngrade
|
|
? Theme.of(context).colorScheme.outline.withValues(alpha: 0.3)
|
|
: AppColors.darkPillBackground,
|
|
isDowngrade
|
|
? Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5)
|
|
: Colors.white,
|
|
isDowngrade: isDowngrade,
|
|
versionCode: showVersionCodes ? availableVersionCode : null,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildVersionPill(
|
|
BuildContext context,
|
|
WidgetRef ref,
|
|
List<PackageInfo> installedPackages,
|
|
String version,
|
|
Color backgroundColor,
|
|
Color textColor, {
|
|
bool isInstalledVersion = false,
|
|
bool isDowngrade = false,
|
|
int? versionCode,
|
|
}) {
|
|
// Build display version with optional version code in parentheses
|
|
String displayVersion = _displayVersion(version, versionCode: versionCode);
|
|
|
|
// Determine app status for icon
|
|
Widget? statusIcon;
|
|
if (!isInstalledVersion) {
|
|
if (isDowngrade) {
|
|
// Downgrade forbidden
|
|
statusIcon = Icon(Icons.block, size: 12, color: textColor);
|
|
} else if (!app.isInstalled) {
|
|
// Can install
|
|
statusIcon = const Icon(
|
|
Icons.download_rounded,
|
|
size: 12,
|
|
color: Colors.white,
|
|
);
|
|
} else {
|
|
if (app.hasUpdate) {
|
|
// Can update
|
|
statusIcon = const Icon(
|
|
Icons.update_outlined,
|
|
size: 12,
|
|
color: Colors.white,
|
|
);
|
|
} else {
|
|
// Is updated
|
|
statusIcon = const Icon(Icons.check, size: 12, color: Colors.white);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use grey colors for "do not disturb" states (downgrade, certificate mismatch)
|
|
final finalBackgroundColor = backgroundColor;
|
|
final finalTextColor = textColor;
|
|
|
|
final double verticalPadding = isInstalledVersion ? 5.0 * 1.05 : 5.0;
|
|
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 9, vertical: verticalPadding),
|
|
decoration: BoxDecoration(
|
|
color: finalBackgroundColor,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Flexible(
|
|
child: Text(
|
|
displayVersion,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: context.textTheme.labelSmall?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: finalTextColor,
|
|
),
|
|
),
|
|
),
|
|
if (statusIcon != null) ...[
|
|
const SizedBox(width: 4),
|
|
IconTheme.merge(
|
|
data: const IconThemeData(size: 12),
|
|
child: statusIcon,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _displayVersion(String version, {int? versionCode}) {
|
|
// Show full version - let Flexible + TextOverflow.ellipsis handle truncation only when needed
|
|
// If versionCode is provided, append it in parentheses
|
|
if (versionCode != null) {
|
|
return '$version ($versionCode)';
|
|
}
|
|
return version;
|
|
}
|
|
|
|
// No width enforcement; both pills share the same text style
|
|
}
|