mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 12:38:24 +00:00
Align skeletons, logo
This commit is contained in:
+3
-6
@@ -20,6 +20,7 @@ import 'package:zapstore/services/package_manager/android_package_manager.dart';
|
|||||||
import 'package:zapstore/services/package_manager/dummy_package_manager.dart';
|
import 'package:zapstore/services/package_manager/dummy_package_manager.dart';
|
||||||
import 'package:zapstore/services/market_intent_service.dart';
|
import 'package:zapstore/services/market_intent_service.dart';
|
||||||
import 'package:zapstore/utils/extensions.dart';
|
import 'package:zapstore/utils/extensions.dart';
|
||||||
|
import 'package:zapstore/widgets/breathing_logo.dart';
|
||||||
|
|
||||||
/// Global provider container for error reporting (accessible outside widget tree)
|
/// Global provider container for error reporting (accessible outside widget tree)
|
||||||
late final ProviderContainer _providerContainer;
|
late final ProviderContainer _providerContainer;
|
||||||
@@ -188,12 +189,8 @@ class ZapstoreHome extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const BreathingLogo(size: 120),
|
||||||
Icons.rocket_launch,
|
const SizedBox(height: 24),
|
||||||
size: 64,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('Zapstore', style: context.textTheme.headlineMedium),
|
Text('Zapstore', style: context.textTheme.headlineMedium),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -38,10 +38,6 @@ class ProfileScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
// Debug Messages Section
|
|
||||||
const _DebugMessagesSection(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Settings Heading
|
// Settings Heading
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
@@ -53,18 +49,6 @@ class ProfileScreen extends ConsumerWidget {
|
|||||||
// Lightning Wallet Section
|
// Lightning Wallet Section
|
||||||
const NWCConnectionCard(),
|
const NWCConnectionCard(),
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Advanced Heading
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Text('Advanced', style: context.textTheme.headlineSmall),
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Relay Management Section - disabled due to API changes
|
|
||||||
// const _RelayManagementSection(),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Data Management Section
|
// Data Management Section
|
||||||
@@ -74,6 +58,11 @@ class ProfileScreen extends ConsumerWidget {
|
|||||||
|
|
||||||
// About Section
|
// About Section
|
||||||
_AboutSection(),
|
_AboutSection(),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Debug Messages Section
|
||||||
|
const _DebugMessagesSection(),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -233,9 +222,6 @@ class _AuthenticationSection extends ConsumerWidget {
|
|||||||
Future<void> _signOut(BuildContext context, WidgetRef ref) async {
|
Future<void> _signOut(BuildContext context, WidgetRef ref) async {
|
||||||
try {
|
try {
|
||||||
await ref.read(amberSignerProvider).signOut();
|
await ref.read(amberSignerProvider).signOut();
|
||||||
if (context.mounted) {
|
|
||||||
context.showInfo('Signed out successfully');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.showError('Sign out failed: $e');
|
context.showError('Sign out failed: $e');
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ class CategorizedAppsNotifier extends StateNotifier<CategorizedApps> {
|
|||||||
},
|
},
|
||||||
and: (app) => {
|
and: (app) => {
|
||||||
app.latestRelease,
|
app.latestRelease,
|
||||||
if (app.latestRelease.value != null)
|
app.latestRelease.value?.latestMetadata,
|
||||||
app.latestRelease.value!.latestMetadata,
|
|
||||||
},
|
},
|
||||||
source: const LocalAndRemoteSource(
|
source: const LocalAndRemoteSource(
|
||||||
relays: 'AppCatalog',
|
relays: 'AppCatalog',
|
||||||
|
|||||||
+113
-32
@@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:models/models.dart';
|
import 'package:models/models.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:markdown/markdown.dart' as md;
|
||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:zapstore/utils/extensions.dart';
|
import 'package:zapstore/utils/extensions.dart';
|
||||||
@@ -57,6 +58,13 @@ class AppCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
// Use the working author (provided, relationship, or fallback)
|
// Use the working author (provided, relationship, or fallback)
|
||||||
final actualAuthor = author ?? relationshipAuthor ?? fallbackAuthor;
|
final actualAuthor = author ?? relationshipAuthor ?? fallbackAuthor;
|
||||||
|
final descriptionStyle = context.textTheme.bodyMedium?.copyWith(
|
||||||
|
height: 1.5,
|
||||||
|
color: AppColors.darkOnSurfaceSecondary,
|
||||||
|
);
|
||||||
|
final descriptionText = app!.description.isNotEmpty
|
||||||
|
? _stripMarkdown(app!.description)
|
||||||
|
: 'No description available';
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -88,40 +96,57 @@ class AppCard extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// App Name and Version Row
|
// App Name and Version
|
||||||
Row(
|
if (showUpdateButton) ...[
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// When showing update button: name on top, version below
|
||||||
children: [
|
Text(
|
||||||
Expanded(
|
app!.name ?? app!.identifier,
|
||||||
child: Text(
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
app!.name ?? app!.identifier,
|
fontWeight: FontWeight.w900,
|
||||||
style: context.textTheme.titleMedium?.copyWith(
|
letterSpacing: 0.1,
|
||||||
fontWeight: FontWeight.w900,
|
|
||||||
letterSpacing: 0.1,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
overflow: TextOverflow.ellipsis,
|
||||||
VersionPillWidget(
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: VersionPillWidget(
|
||||||
app: app!,
|
app: app!,
|
||||||
showUpdateArrow: showUpdateArrow,
|
showUpdateArrow: showUpdateArrow,
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
] else ...[
|
||||||
|
// Default: name and version in same row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
app!.name ?? app!.identifier,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
letterSpacing: 0.1,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
VersionPillWidget(
|
||||||
|
app: app!,
|
||||||
|
showUpdateArrow: showUpdateArrow,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// App Description with improved readability
|
// App Description rendered as plain text (markdown stripped)
|
||||||
Text(
|
Text(
|
||||||
app!.description.isNotEmpty
|
descriptionText,
|
||||||
? app!.description
|
style: descriptionStyle,
|
||||||
: 'No description available',
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
height: 1.5,
|
|
||||||
color: AppColors.darkOnSurfaceSecondary,
|
|
||||||
),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
@@ -268,13 +293,59 @@ class AppCard extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses markdown and returns plain text without formatting artifacts.
|
||||||
|
String _stripMarkdown(String input) {
|
||||||
|
final doc = md.Document(encodeHtml: false);
|
||||||
|
final nodes = doc.parseLines(input.split('\n'));
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
|
||||||
|
void writeNode(md.Node node) {
|
||||||
|
if (node is md.Text) {
|
||||||
|
buffer.write(node.text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node is md.Element) {
|
||||||
|
// Preserve spacing for block-level elements
|
||||||
|
final isBlock = const {
|
||||||
|
'p',
|
||||||
|
'li',
|
||||||
|
'ul',
|
||||||
|
'ol',
|
||||||
|
'blockquote',
|
||||||
|
'h1',
|
||||||
|
'h2',
|
||||||
|
'h3',
|
||||||
|
'h4',
|
||||||
|
'h5',
|
||||||
|
'h6',
|
||||||
|
}.contains(node.tag);
|
||||||
|
|
||||||
|
if (node.tag == 'br') buffer.write('\n');
|
||||||
|
|
||||||
|
for (final child in node.children ?? []) {
|
||||||
|
writeNode(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlock) buffer.write('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final node in nodes) {
|
||||||
|
writeNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString().replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSkeleton(BuildContext context) {
|
Widget _buildSkeleton(BuildContext context) {
|
||||||
return SkeletonizerConfig(
|
return SkeletonizerConfig(
|
||||||
data: AppColors.getSkeletonizerConfig(Theme.of(context).brightness),
|
data: AppColors.getSkeletonizerConfig(Theme.of(context).brightness),
|
||||||
child: Skeletonizer(
|
child: Skeletonizer(
|
||||||
enabled: true,
|
enabled: true,
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
// Match actual content: vertical: 6, not 8
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
@@ -287,6 +358,7 @@ class AppCard extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// App icon skeleton
|
// App icon skeleton
|
||||||
Container(
|
Container(
|
||||||
@@ -326,7 +398,7 @@ class AppCard extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
// Description skeleton
|
// Description skeleton - 3 lines to match actual maxLines: 3
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -338,10 +410,19 @@ class AppCard extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 6),
|
||||||
Container(
|
Container(
|
||||||
height: 16,
|
height: 16,
|
||||||
width: 200,
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.darkSkeletonBase,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Container(
|
||||||
|
height: 16,
|
||||||
|
width: 140,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.darkSkeletonBase,
|
color: AppColors.darkSkeletonBase,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
@@ -349,9 +430,9 @@ class AppCard extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Author skeleton (only if showSignedBy is true)
|
// Author skeleton (only if showSignedBy is true) - match Gap(14) spacing
|
||||||
if (showSignedBy) ...[
|
if (showSignedBy) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 14),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class AppPackContainer extends HookConsumerWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Skeleton pills
|
// Skeleton pills - match actual pill structure with "by (author)" section
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -83,15 +83,7 @@ class AppPackContainer extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
3,
|
3,
|
||||||
(index) => Container(
|
(index) => _buildSkeletonPill(context),
|
||||||
width: 120,
|
|
||||||
height: 32,
|
|
||||||
margin: const EdgeInsets.only(right: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColors.darkSkeletonBase,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -99,13 +91,72 @@ class AppPackContainer extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Skeleton grid
|
// Skeleton grid - match actual spacing (16, not 24)
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 16),
|
||||||
_buildSelectedAppsGrid(context, []), // Empty list shows skeletons
|
_buildSelectedAppsGrid(context, []), // Empty list shows skeletons
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Skeleton pill that matches actual pill dimensions including "by (author)" section
|
||||||
|
static Widget _buildSkeletonPill(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 16),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainerHighest
|
||||||
|
.withValues(alpha: 0.8),
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Pack name skeleton
|
||||||
|
Container(
|
||||||
|
height: 16,
|
||||||
|
width: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.darkSkeletonBase,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// "by" text skeleton
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Container(
|
||||||
|
height: 14,
|
||||||
|
width: 16,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.darkSkeletonBase,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
// Author avatar skeleton
|
||||||
|
Container(
|
||||||
|
width: 18,
|
||||||
|
height: 18,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.darkSkeletonBase,
|
||||||
|
borderRadius: BorderRadius.circular(9),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
// Author name skeleton
|
||||||
|
Container(
|
||||||
|
height: 14,
|
||||||
|
width: 50,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.darkSkeletonBase,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildErrorState(BuildContext context, Object exception) {
|
Widget _buildErrorState(BuildContext context, Object exception) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
|
||||||
|
/// An animated breathing logo widget used for loading states
|
||||||
|
/// Smoothly scales the Zapstore logo in a "breathe in, breathe out" motion
|
||||||
|
class BreathingLogo extends HookWidget {
|
||||||
|
final double size;
|
||||||
|
const BreathingLogo({super.key, this.size = 200});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = useAnimationController(
|
||||||
|
duration: const Duration(milliseconds: 1500),
|
||||||
|
)..repeat(reverse: true);
|
||||||
|
|
||||||
|
final scaleAnimation = useMemoized(
|
||||||
|
() => Tween<double>(begin: 0.85, end: 1.0).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: controller,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
[controller],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: scaleAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: scaleAnimation.value,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/logo.png',
|
||||||
|
height: size,
|
||||||
|
width: size,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -183,10 +183,15 @@ class LatestReleasesContainer extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('Latest Releases', style: context.textTheme.headlineMedium),
|
// Match actual content: headlineSmall, not headlineMedium
|
||||||
TextButton(
|
Text('Latest Releases', style: context.textTheme.headlineSmall),
|
||||||
onPressed: null, // Disabled during loading
|
// Match actual content: show button but make it invisible to reserve space
|
||||||
child: const Text('See more'),
|
Opacity(
|
||||||
|
opacity: 0,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: null,
|
||||||
|
child: const Text('See more'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
|
|
||||||
/// An animated spinning logo widget used for loading states
|
|
||||||
/// Continuously rotates the Zapstore logo image
|
|
||||||
class SpinningLogo extends HookWidget {
|
|
||||||
final double size;
|
|
||||||
const SpinningLogo({super.key, this.size = 200});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final controller = useAnimationController(
|
|
||||||
duration: const Duration(seconds: 2),
|
|
||||||
)..repeat();
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: AnimatedBuilder(
|
|
||||||
animation: controller,
|
|
||||||
builder: (context, child) {
|
|
||||||
return Transform.rotate(
|
|
||||||
angle: controller.value * 2.0 * 3.14159,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Image.asset(
|
|
||||||
'assets/images/logo-fg.png',
|
|
||||||
height: size,
|
|
||||||
width: size,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
@@ -610,7 +610,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: markdown
|
name: markdown
|
||||||
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
|
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ dependencies:
|
|||||||
connectivity_plus: ^7.0.0
|
connectivity_plus: ^7.0.0
|
||||||
app_links: ^6.4.1
|
app_links: ^6.4.1
|
||||||
flutter_secure_storage: ^10.0.0
|
flutter_secure_storage: ^10.0.0
|
||||||
|
markdown: ^7.3.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
models:
|
models:
|
||||||
|
|||||||
Reference in New Issue
Block a user