Skip to content
This repository has been archived by the owner on Jan 9, 2024. It is now read-only.

Commit

Permalink
chore: dash animation spike (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
omartinma authored Nov 21, 2023
1 parent 2e1e3b3 commit c262c9d
Show file tree
Hide file tree
Showing 24 changed files with 579 additions and 116 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/app_ui.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: app_ui

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
pull_request:
paths:
- "packages/app_ui/**"
- ".github/workflows/app_ui.yaml"
branches:
- main

jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
flutter_version: 3.16.0
working_directory: packages/app_ui
Binary file added assets/animations/dash_animation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions devtools_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extensions:
4 changes: 2 additions & 2 deletions lib/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ class AppBlocObserver extends BlocObserver {
}

Future<void> bootstrap(FutureOr<Widget> Function() builder) async {
WidgetsFlutterBinding.ensureInitialized();

FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack);
};

Bloc.observer = const AppBlocObserver();

// Add cross-flavor configuration here

runApp(await builder());
}
1 change: 0 additions & 1 deletion lib/counter/counter.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export 'cubit/counter_cubit.dart';
export 'view/counter_page.dart';
8 changes: 0 additions & 8 deletions lib/counter/cubit/counter_cubit.dart

This file was deleted.

50 changes: 24 additions & 26 deletions lib/counter/view/counter_page.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import 'package:dash_ai_search/counter/counter.dart';
import 'package:app_ui/app_ui.dart';
import 'package:dash_ai_search/l10n/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CounterPage extends StatelessWidget {
const CounterPage({super.key});

@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: const CounterView(),
);
return const CounterView();
}
}

Expand All @@ -21,35 +17,37 @@ class CounterView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final screenSize = MediaQuery.sizeOf(context);
return Scaffold(
appBar: AppBar(title: Text(l10n.counterAppBarTitle)),
body: const Center(child: CounterText()),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().increment(),
child: const Icon(Icons.add),
),
const SizedBox(height: 8),
FloatingActionButton(
onPressed: () => context.read<CounterCubit>().decrement(),
child: const Icon(Icons.remove),
),
],
body: Center(
child: Container(
alignment: Alignment.center,
height: screenSize.height / 2,
width: screenSize.height / 2,
child: const DashAnimation(),
),
),
);
}
}

class CounterText extends StatelessWidget {
const CounterText({super.key});
class DashAnimation extends StatelessWidget {
@visibleForTesting
const DashAnimation({super.key});

static const dashSize = Size(800, 800);

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final count = context.select((CounterCubit cubit) => cubit.state);
return Text('$count', style: theme.textTheme.displayLarge);
return const AnimatedSprite(
showLoadingIndicator: false,
sprites: Sprites(
asset: 'dash_animation.png',
size: dashSize,
frames: 34,
stepTime: 0.07,
),
);
}
}
44 changes: 44 additions & 0 deletions packages/app_ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# VSCode related
.vscode/*

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
pubspec.lock

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Test related
coverage
12 changes: 12 additions & 0 deletions packages/app_ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# App UI

[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason)
[![License: MIT][license_badge]][license_link]

UI Toolkit

[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
1 change: 1 addition & 0 deletions packages/app_ui/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.5.1.0.yaml
20 changes: 20 additions & 0 deletions packages/app_ui/coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/app_ui/lib/app_ui.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'src/widgets/widgets.dart';
159 changes: 159 additions & 0 deletions packages/app_ui/lib/src/widgets/animated_sprite.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import 'dart:async';
import 'dart:ui' as ui show Image;

import 'package:app_ui/app_ui.dart';
import 'package:flame/components.dart' hide Timer;
import 'package:flame/flame.dart';
import 'package:flame/sprite.dart';
import 'package:flame/widgets.dart';
import 'package:flutter/material.dart';

/// {@template sprites}
/// Object which contains meta data for a collection of sprites.
/// {@endtemplate}
class Sprites {
/// {@macro sprites}
const Sprites({
required this.asset,
required this.size,
required this.frames,
this.stepTime = 0.1,
});

/// The sprite sheet asset name.
/// This should be the name of the file within
/// the `assets/images` directory.
final String asset;

/// The size an individual sprite within the sprite sheet
final Size size;

/// The number of frames within the sprite sheet.
final int frames;

/// Number of seconds per frame. Defaults to 0.1.
final double stepTime;
}

/// The animation mode which determines when the animation plays.
enum AnimationMode {
/// Animations plays on a loop
loop,

/// Animations plays immediately once
oneTime
}

/// {@template animated_sprite}
/// A widget which renders an animated sprite
/// given a collection of sprites.
/// {@endtemplate}
class AnimatedSprite extends StatefulWidget {
/// {@macro animated_sprite}
const AnimatedSprite({
required this.sprites,
super.key,
this.mode = AnimationMode.loop,
this.showLoadingIndicator = true,
this.loadingIndicatorColor = Colors.white,
});

/// The collection of sprites which will be animated.
final Sprites sprites;

/// The mode of animation (`trigger`, `loop` or `oneTime`).
final AnimationMode mode;

/// Where should display a loading indicator while loading the sprite
final bool showLoadingIndicator;

/// Color for loading indicator
final Color loadingIndicatorColor;

@override
State<AnimatedSprite> createState() => _AnimatedSpriteState();
}

enum _AnimatedSpriteStatus { loading, loaded, failure }

extension on _AnimatedSpriteStatus {
/// Returns true for `_AnimatedSpriteStatus.loaded`.
bool get isLoaded => this == _AnimatedSpriteStatus.loaded;
}

class _AnimatedSpriteState extends State<AnimatedSprite> {
late SpriteAnimation _animation;
Timer? _timer;
var _status = _AnimatedSpriteStatus.loading;
var _isPlaying = false;
late SpriteAnimationTicker _spriteAnimationTicker;

@override
void initState() {
super.initState();
_loadAnimation();
}

@override
void dispose() {
_timer?.cancel();
super.dispose();
}

Future<void> _loadAnimation() async {
late ui.Image image;
try {
final images = Flame.images..prefix = 'assets/animations/';
image = await images.load(widget.sprites.asset);
} catch (_) {
setState(() => _status = _AnimatedSpriteStatus.failure);
return;
}

_animation = SpriteSheet(
image: image,
srcSize: Vector2(widget.sprites.size.width, widget.sprites.size.height),
).createAnimation(
row: 0,
stepTime: widget.sprites.stepTime,
to: widget.sprites.frames,
loop: widget.mode == AnimationMode.loop,
);
_spriteAnimationTicker = _animation.createTicker();

setState(() {
_status = _AnimatedSpriteStatus.loaded;
if (widget.mode == AnimationMode.loop ||
widget.mode == AnimationMode.oneTime) {
_isPlaying = true;
}
});
}

@override
Widget build(BuildContext context) {
return AppAnimatedCrossFade(
firstChild: widget.showLoadingIndicator
? SizedBox.fromSize(
size: const Size(20, 20),
child: AppCircularProgressIndicator(
strokeWidth: 2,
color: widget.loadingIndicatorColor,
),
)
: const SizedBox(),
secondChild: SizedBox.expand(
child: _status.isLoaded
? SpriteAnimationWidget(
animation: _animation,
playing: _isPlaying,
animationTicker: _spriteAnimationTicker,
)
: const SizedBox(),
),
crossFadeState: _status.isLoaded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
);
}
}
Loading

0 comments on commit c262c9d

Please sign in to comment.