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

chore: dash animation spike #4

Merged
merged 18 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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