Merge branch 'accessability-pass' into accessibility-pass-semanticslabels
@ -17,7 +17,7 @@
|
||||
297F6FD12AD06E0F00FF159E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 297F6FD02AD06E0F00FF159E /* Assets.xcassets */; };
|
||||
297F6FD32AD06E0F00FF159E /* WonderousWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 297F6FCF2AD06E0D00FF159E /* WonderousWidget.intentdefinition */; };
|
||||
297F6FD42AD06E0F00FF159E /* WonderousWidget.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 297F6FCF2AD06E0D00FF159E /* WonderousWidget.intentdefinition */; };
|
||||
297F6FD72AD06E0F00FF159E /* Wonderous WidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 297F6FC52AD06E0D00FF159E /* Wonderous WidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
297F6FD72AD06E0F00FF159E /* WonderousWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 297F6FC52AD06E0D00FF159E /* WonderousWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
297FD5742AE18011008D8BFE /* WonderousWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297FD5732AE18011008D8BFE /* WonderousWidgetView.swift */; };
|
||||
297FD5762AE19BD9008D8BFE /* WonderWidgetViewComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 297FD5752AE19BD9008D8BFE /* WonderWidgetViewComponents.swift */; };
|
||||
323DE3CFA8490EAB3C4E249C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A44ACC5DE81A9C3E5BDA151 /* Pods_Runner.framework */; };
|
||||
@ -52,7 +52,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
297F6FD72AD06E0F00FF159E /* Wonderous WidgetExtension.appex in Embed Foundation Extensions */,
|
||||
297F6FD72AD06E0F00FF159E /* WonderousWidgetExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -75,7 +75,7 @@
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
296251242AE7410D00D574FF /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||
2978ECDC2B62D00C00E36CE8 /* FlutterAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlutterAssets.swift; sourceTree = "<group>"; };
|
||||
297F6FC52AD06E0D00FF159E /* Wonderous WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; name = "Wonderous WidgetExtension.appex"; path = WonderousWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
297F6FC52AD06E0D00FF159E /* WonderousWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; name = "WonderousWidgetExtension.appex"; path = WonderousWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
297F6FC62AD06E0D00FF159E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||
297F6FC82AD06E0D00FF159E /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
297F6FCB2AD06E0D00FF159E /* WonderousWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WonderousWidgetBundle.swift; sourceTree = "<group>"; };
|
||||
@ -187,7 +187,7 @@
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
E214FC8227C5A18D005F78FB /* wondersUITests.xctest */,
|
||||
297F6FC52AD06E0D00FF159E /* Wonderous WidgetExtension.appex */,
|
||||
297F6FC52AD06E0D00FF159E /* WonderousWidgetExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -235,7 +235,7 @@
|
||||
);
|
||||
name = WonderousWidgetExtension;
|
||||
productName = WonderousWidgetExtension;
|
||||
productReference = 297F6FC52AD06E0D00FF159E /* Wonderous WidgetExtension.appex */;
|
||||
productReference = 297F6FC52AD06E0D00FF159E /* WonderousWidgetExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
@ -612,7 +612,7 @@
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = WonderousWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Wonderous Widget";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "WonderousWidget";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -652,7 +652,7 @@
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = WonderousWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Wonderous Widget";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "WonderousWidget";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@ -681,15 +681,15 @@
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "Wonderous WidgetExtension.entitlements";
|
||||
CODE_SIGN_ENTITLEMENTS = "WonderousWidgetExtension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = S3TL5AY6Y3;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Wonderous Widget/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Wonderous Widget";
|
||||
INFOPLIST_FILE = "WonderousWidget/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "WonderousWidget";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -1,6 +1,6 @@
|
||||
//
|
||||
// FlutterUtils.swift
|
||||
// Wonderous WidgetExtension
|
||||
// WonderousWidgetExtension
|
||||
//
|
||||
// Created by Shawn on 2023-10-19.
|
||||
//
|
||||
|
45
lib/logic/common/animate_utils.dart
Normal file
@ -0,0 +1,45 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class NeverAnimate extends Animate {
|
||||
NeverAnimate({super.key, super.child});
|
||||
|
||||
@override
|
||||
State<NeverAnimate> createState() => _NeverAnimateState();
|
||||
}
|
||||
|
||||
class _NeverAnimateState extends State<NeverAnimate> {
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
|
||||
extension MaybeAnimateExtension on Widget {
|
||||
Animate maybeAnimate({
|
||||
Key? key,
|
||||
List<Effect>? effects,
|
||||
AnimateCallback? onInit,
|
||||
AnimateCallback? onPlay,
|
||||
AnimateCallback? onComplete,
|
||||
bool? autoPlay,
|
||||
Duration? delay,
|
||||
AnimationController? controller,
|
||||
Adapter? adapter,
|
||||
double? target,
|
||||
double? value,
|
||||
}) => $styles.disableAnimations
|
||||
? NeverAnimate(child: this)
|
||||
: Animate(
|
||||
key: key,
|
||||
effects: effects,
|
||||
onInit: onInit,
|
||||
onPlay: onPlay,
|
||||
onComplete: onComplete,
|
||||
autoPlay: autoPlay,
|
||||
delay: delay,
|
||||
controller: controller,
|
||||
adapter: adapter,
|
||||
target: target,
|
||||
value: value,
|
||||
child: this,
|
||||
);
|
||||
}
|
@ -42,24 +42,20 @@ class ScreenPaths {
|
||||
|
||||
// Routes that are used multiple times
|
||||
AppRoute get _artifactRoute => AppRoute(
|
||||
'artifact/:artifactId',
|
||||
(s) => ArtifactDetailsScreen(artifactId: s.pathParameters['artifactId']!),
|
||||
);
|
||||
'artifact/:artifactId',
|
||||
(s) => ArtifactDetailsScreen(artifactId: s.pathParameters['artifactId']!),
|
||||
);
|
||||
|
||||
AppRoute get _timelineRoute {
|
||||
return AppRoute(
|
||||
'timeline',
|
||||
(s) => TimelineScreen(type: _tryParseWonderType(s.uri.queryParameters['type']!)),
|
||||
);
|
||||
}
|
||||
AppRoute get _timelineRoute => AppRoute(
|
||||
'timeline',
|
||||
(s) => TimelineScreen(type: _tryParseWonderType(s.uri.queryParameters['type']!)),
|
||||
);
|
||||
|
||||
AppRoute get _collectionRoute {
|
||||
return AppRoute(
|
||||
'collection',
|
||||
(s) => CollectionScreen(fromId: s.uri.queryParameters['id'] ?? ''),
|
||||
routes: [_artifactRoute],
|
||||
);
|
||||
}
|
||||
AppRoute get _collectionRoute => AppRoute(
|
||||
'collection',
|
||||
(s) => CollectionScreen(fromId: s.uri.queryParameters['id'] ?? ''),
|
||||
routes: [_artifactRoute],
|
||||
);
|
||||
|
||||
/// Routing table, matches string paths to UI Screens, optionally parses params from the paths
|
||||
final appRouter = GoRouter(
|
||||
@ -132,7 +128,7 @@ class AppRoute extends GoRoute {
|
||||
body: builder(state),
|
||||
resizeToAvoidBottomInset: false,
|
||||
);
|
||||
if (useFade) {
|
||||
if (useFade || $styles.disableAnimations) {
|
||||
return CustomTransitionPage(
|
||||
key: state.pageKey,
|
||||
child: pageContent,
|
||||
|
@ -1,12 +1,13 @@
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
export 'colors.dart';
|
||||
|
||||
@immutable
|
||||
class AppStyle {
|
||||
AppStyle({Size? screenSize}) {
|
||||
AppStyle({Size? screenSize, this.disableAnimations = false}) {
|
||||
if (screenSize == null) {
|
||||
scale = 1;
|
||||
return;
|
||||
@ -24,6 +25,7 @@ class AppStyle {
|
||||
}
|
||||
|
||||
late final double scale;
|
||||
late final bool disableAnimations;
|
||||
|
||||
/// The current theme colors for the app
|
||||
final AppColors colors = AppColors();
|
||||
@ -40,7 +42,7 @@ class AppStyle {
|
||||
late final _Text text = _Text(scale);
|
||||
|
||||
/// Animation Durations
|
||||
final _Times times = _Times();
|
||||
late final _Times times = _Times();
|
||||
|
||||
/// Shared sizes
|
||||
late final _Sizes sizes = _Sizes();
|
||||
@ -133,10 +135,12 @@ class _Text {
|
||||
|
||||
@immutable
|
||||
class _Times {
|
||||
final Duration fast = Duration(milliseconds: 300);
|
||||
final Duration med = Duration(milliseconds: 600);
|
||||
final Duration slow = Duration(milliseconds: 900);
|
||||
final Duration pageTransition = Duration(milliseconds: 200);
|
||||
_Times();
|
||||
late final Duration fast = 300.animateMs;
|
||||
late final Duration med = 600.animateMs;
|
||||
late final Duration slow = 900.animateMs;
|
||||
late final Duration extraSlow = 1300.animateMs;
|
||||
late final Duration pageTransition = 200.animateMs;
|
||||
}
|
||||
|
||||
@immutable
|
||||
|
@ -15,7 +15,7 @@ class WondersAppScaffold extends StatelessWidget {
|
||||
// Set default timing for animations in the app
|
||||
Animate.defaultDuration = _style.times.fast;
|
||||
// Create a style object that will be passed down the widget tree
|
||||
_style = AppStyle(screenSize: context.sizePx);
|
||||
_style = AppStyle(screenSize: context.sizePx, disableAnimations: mq.disableAnimations);
|
||||
return KeyedSubtree(
|
||||
key: ValueKey($styles.scale),
|
||||
child: Theme(
|
||||
|
@ -1,8 +1,10 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/collectibles_logic.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/collectible_data.dart';
|
||||
import 'package:wonders/ui/common/opening_card.dart';
|
||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
import 'package:wonders/ui/screens/collectible_found/collectible_found_screen.dart';
|
||||
|
||||
class CollectibleItem extends StatelessWidget with GetItMixin {
|
||||
@ -53,8 +55,8 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.shimmer(delay: 4000.ms, duration: $styles.times.med * 3)
|
||||
.maybeAnimate(onPlay: (controller) => controller.repeat())
|
||||
.shimmer(delay: 4000.delayMs, duration: $styles.times.med * 3)
|
||||
.shake(curve: Curves.easeInOutCubic, hz: 4)
|
||||
.scale(begin: Offset(1.0, 1.0), end: Offset(1.1, 1.1), duration: $styles.times.med)
|
||||
.then(delay: $styles.times.med)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
class CompassDivider extends StatelessWidget {
|
||||
const CompassDivider({super.key, required this.isExpanded, this.duration, this.linesColor, this.compassColor});
|
||||
@ -10,7 +11,7 @@ class CompassDivider extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Duration duration = this.duration ?? 1500.ms;
|
||||
Duration duration = this.duration ?? 1500.animateMs;
|
||||
Widget buildHzAnimatedDivider({bool alignLeft = false}) {
|
||||
return TweenAnimationBuilder<double>(
|
||||
duration: duration,
|
||||
|
@ -53,7 +53,7 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
|
||||
|
||||
void _animateToPage(int page) {
|
||||
if (page >= 0 || page < widget.urls.length) {
|
||||
_controller.animateToPage(page, duration: 300.ms, curve: Curves.easeOut);
|
||||
_controller.animateToPage(page, duration: $styles.times.fast, curve: Curves.easeOut);
|
||||
}
|
||||
}
|
||||
|
||||
|
6
lib/ui/common/utils/duration_utils.dart
Normal file
@ -0,0 +1,6 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
|
||||
extension DurationExtensions on int {
|
||||
Duration get delayMs => $styles.disableAnimations ? 0.ms : Duration(milliseconds: this);
|
||||
Duration get animateMs => $styles.disableAnimations ? 1.ms : Duration(milliseconds: this);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/highlight_data.dart';
|
||||
import 'package:wonders/ui/common/app_icons.dart';
|
||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
|
@ -57,7 +57,7 @@ class _BottomTextContent extends StatelessWidget {
|
||||
),
|
||||
]
|
||||
],
|
||||
).animate(key: ValueKey(artifact.artifactId)).fadeIn(),
|
||||
).maybeAnimate(key: ValueKey(artifact.artifactId)).fadeIn(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/artifact_data.dart';
|
||||
import 'package:wonders/ui/common/compass_divider.dart';
|
||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
||||
import 'package:wonders/ui/common/gradient_container.dart';
|
||||
import 'package:wonders/ui/common/modals/fullscreen_url_img_viewer.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
part 'widgets/_artifact_image_btn.dart';
|
||||
part 'widgets/_info_column.dart';
|
||||
@ -85,6 +87,6 @@ class _ArtifactDetailsScreenState extends State<ArtifactDetailsScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
).animate().fadeIn();
|
||||
).maybeAnimate().fadeIn();
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class _InfoColumn extends StatelessWidget {
|
||||
Text(
|
||||
data.culture.toUpperCase(),
|
||||
style: $styles.text.titleFont.copyWith(color: $styles.colors.accent1),
|
||||
).animate().fade(delay: 150.ms, duration: 600.ms),
|
||||
).maybeAnimate().fade(delay: 150.delayMs, duration: 600.animateMs),
|
||||
Gap($styles.insets.xs),
|
||||
],
|
||||
Semantics(
|
||||
@ -29,11 +29,11 @@ class _InfoColumn extends StatelessWidget {
|
||||
style: $styles.text.h2.copyWith(color: $styles.colors.offWhite, height: 1.2),
|
||||
maxLines: 5,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).animate().fade(delay: 250.ms, duration: 600.ms),
|
||||
).maybeAnimate().fade(delay: 250.delayMs, duration: 600.animateMs),
|
||||
),
|
||||
Gap($styles.insets.lg),
|
||||
Animate().toggle(
|
||||
delay: 500.ms,
|
||||
delay: 500.delayMs,
|
||||
builder: (_, value, __) {
|
||||
return CompassDivider(isExpanded: !value, duration: $styles.times.med);
|
||||
}),
|
||||
@ -49,8 +49,8 @@ class _InfoColumn extends StatelessWidget {
|
||||
_InfoRow($strings.artifactDetailsLabelDimension, data.dimension),
|
||||
_InfoRow($strings.artifactDetailsLabelClassification, data.classification),
|
||||
]
|
||||
.animate(interval: 100.ms)
|
||||
.fadeIn(delay: 600.ms, duration: $styles.times.med)
|
||||
.animate(interval: 100.delayMs)
|
||||
.fadeIn(delay: 600.delayMs, duration: $styles.times.med)
|
||||
.slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
||||
],
|
||||
),
|
||||
@ -58,7 +58,7 @@ class _InfoColumn extends StatelessWidget {
|
||||
Text(
|
||||
$strings.homeMenuAboutMet,
|
||||
style: $styles.text.caption.copyWith(color: $styles.colors.accent2),
|
||||
).animate(delay: 1.5.seconds).fadeIn().slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
||||
).maybeAnimate(delay: 1500.delayMs).fadeIn().slide(begin: Offset(0.2, 0), curve: Curves.easeOut),
|
||||
Gap($styles.insets.offset),
|
||||
],
|
||||
),
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'package:particle_field/particle_field.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/collectible_data.dart';
|
||||
import 'package:wonders/ui/common/app_backdrop.dart';
|
||||
import 'package:wonders/ui/common/centered_box.dart';
|
||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
part 'widgets/_animated_ribbon.dart';
|
||||
part 'widgets/_celebration_particles.dart';
|
||||
@ -21,7 +23,7 @@ class CollectibleFoundScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return RepaintBoundary(
|
||||
child: _buildIntro(context).animate().swap(
|
||||
delay: $styles.times.fast * 3.5,
|
||||
delay: 1050.animateMs,
|
||||
builder: (_, __) => _buildDetail(context),
|
||||
),
|
||||
);
|
||||
|
@ -38,7 +38,7 @@ class _AnimatedRibbon extends StatelessWidget {
|
||||
if (flip) end = Transform.scale(scaleX: -1, child: end);
|
||||
double m = flip ? 1 : -1;
|
||||
return end
|
||||
.animate()
|
||||
.move(begin: Offset(m * 8, 2), end: Offset(m * 32, 10), duration: 400.ms, curve: Curves.easeOut);
|
||||
.maybeAnimate()
|
||||
.move(begin: Offset(m * 8, 2), end: Offset(m * 32, 10), duration: 400.animateMs, curve: Curves.easeOut);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,13 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/collectibles_logic.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/collectible_data.dart';
|
||||
import 'package:wonders/logic/data/wonder_data.dart';
|
||||
import 'package:wonders/ui/common/centered_box.dart';
|
||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
import 'package:wonders/ui/common/modals/app_modals.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
part 'widgets/_collectible_image.dart';
|
||||
part 'widgets/_collection_footer.dart';
|
||||
@ -38,7 +40,7 @@ class _CollectionScreenState extends State<CollectionScreen> with GetItStateMixi
|
||||
|
||||
void _scrollToTarget([bool animate = true]) {
|
||||
if (_scrollKey.currentContext != null) {
|
||||
Scrollable.ensureVisible(_scrollKey.currentContext!, alignment: 0.15, duration: animate ? 300.ms : 0.ms);
|
||||
Scrollable.ensureVisible(_scrollKey.currentContext!, alignment: 0.15, duration: (animate ? 300 : 0).animateMs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ class _CollectionFooter extends StatelessWidget {
|
||||
color: $styles.colors.accent1,
|
||||
borderRadius: BorderRadius.circular(1000),
|
||||
),
|
||||
).animate().fade(duration: 1500.ms, curve: Curves.easeOutExpo).custom(
|
||||
).maybeAnimate().fade(duration: 1500.animateMs, curve: Curves.easeOutExpo).custom(
|
||||
builder: (_, m, child) => FractionallySizedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
widthFactor: m * count / total,
|
||||
|
@ -10,7 +10,7 @@ class _CollectionListCard extends StatelessWidget with GetItMixin {
|
||||
|
||||
void _showDetails(BuildContext context, CollectibleData collectible) {
|
||||
context.go(ScreenPaths.artifact(collectible.artifactId));
|
||||
Future.delayed(300.ms).then((_) => collectiblesLogic.setState(collectible.id, CollectibleState.explored));
|
||||
Future.delayed(300.delayMs).then((_) => collectiblesLogic.setState(collectible.id, CollectibleState.explored));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_circular_text/circular_text.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/common/platform_info.dart';
|
||||
import 'package:wonders/logic/common/string_utils.dart';
|
||||
import 'package:wonders/logic/data/wonder_data.dart';
|
||||
@ -23,6 +24,7 @@ import 'package:wonders/ui/common/scaling_list_item.dart';
|
||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||
import 'package:wonders/ui/common/themed_text.dart';
|
||||
import 'package:wonders/ui/common/utils/context_utils.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.dart';
|
||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration_config.dart';
|
||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
|
||||
|
@ -67,7 +67,7 @@ class _AppBar extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
).animate(delay: $styles.times.pageTransition + 500.ms).fadeIn(duration: $styles.times.slow),
|
||||
).maybeAnimate(delay: $styles.times.pageTransition + 500.delayMs).fadeIn(duration: $styles.times.slow),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -34,7 +34,7 @@ class _CircularTitleBar extends StatelessWidget {
|
||||
BottomCenter(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: 20),
|
||||
child: Image.asset('${ImagePaths.common}/${icons[index]}').animate(key: ValueKey(index)).fade().scale(
|
||||
child: Image.asset('${ImagePaths.common}/${icons[index]}').maybeAnimate(key: ValueKey(index)).fade().scale(
|
||||
begin: Offset(.5, .5), end: Offset(1, 1), curve: Curves.easeOutBack, duration: $styles.times.med),
|
||||
),
|
||||
),
|
||||
|
@ -31,7 +31,7 @@ class _ScrollingContent extends StatelessWidget {
|
||||
label: value,
|
||||
child: ExcludeSemantics(
|
||||
child: skipCaps
|
||||
? Text(_fixNewlines(value), style: bodyStyle)
|
||||
? Text(_fixNewlines(value), style: bodyStyle )
|
||||
: DropCapText(
|
||||
_fixNewlines(value).substring(1),
|
||||
dropCap: DropCap(
|
||||
|
@ -27,7 +27,7 @@ class _TitleText extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Divider(
|
||||
color: data.type.fgColor,
|
||||
).animate().scale(curve: Curves.easeOut, delay: 500.ms),
|
||||
).maybeAnimate().scale(curve: Curves.easeOut, delay: 500.delayMs),
|
||||
),
|
||||
Semantics(
|
||||
header: true,
|
||||
@ -35,12 +35,12 @@ class _TitleText extends StatelessWidget {
|
||||
child: Text(
|
||||
data.subTitle.toUpperCase(),
|
||||
style: $styles.text.title2,
|
||||
).animate().fade(delay: 100.ms),
|
||||
).maybeAnimate().fade(delay: 100.delayMs),
|
||||
),
|
||||
Expanded(
|
||||
child: Divider(
|
||||
color: data.type.fgColor,
|
||||
).animate().scale(curve: Curves.easeOut, delay: 500.ms),
|
||||
).maybeAnimate().scale(curve: Curves.easeOut, delay: 500.delayMs),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -17,7 +17,7 @@ class _VerticalSwipeController {
|
||||
void handleTapCancelled() => isPointerDown.value = false;
|
||||
|
||||
void handleVerticalSwipeCancelled() {
|
||||
swipeReleaseAnim.duration = swipeAmt.value.seconds * .5;
|
||||
swipeReleaseAnim.duration = $styles.disableAnimations ? 1.ms : swipeAmt.value.seconds * .5;
|
||||
swipeReleaseAnim.reverse(from: swipeAmt.value);
|
||||
isPointerDown.value = false;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/wonder_data.dart';
|
||||
import 'package:wonders/ui/common/app_icons.dart';
|
||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
@ -8,6 +9,7 @@ import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||
import 'package:wonders/ui/common/previous_next_navigation.dart';
|
||||
import 'package:wonders/ui/common/themed_text.dart';
|
||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
import 'package:wonders/ui/screens/home_menu/home_menu.dart';
|
||||
import 'package:wonders/ui/wonder_illustrations/common/animated_clouds.dart';
|
||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_illustration.dart';
|
||||
@ -112,7 +114,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
void _showDetailsPage() async {
|
||||
_swipeOverride = _swipeController.swipeAmt.value;
|
||||
context.go(ScreenPaths.wonderDetails(currentWonder.type, tabIndex: 0));
|
||||
await Future.delayed(100.ms);
|
||||
await Future.delayed(100.delayMs);
|
||||
_swipeOverride = null;
|
||||
_fadeInOnNextBuild = true;
|
||||
}
|
||||
@ -122,7 +124,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
for (var a in _fadeAnims) {
|
||||
a.value = 0;
|
||||
}
|
||||
await Future.delayed(300.ms);
|
||||
await Future.delayed(300.delayMs);
|
||||
for (var a in _fadeAnims) {
|
||||
a.forward();
|
||||
}
|
||||
@ -158,7 +160,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
||||
/// Controls that float on top of the various illustrations
|
||||
_buildFloatingUi(),
|
||||
],
|
||||
).animate().fadeIn(),
|
||||
).maybeAnimate().fadeIn(),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/wonder_data.dart';
|
||||
import 'package:wonders/ui/common/app_backdrop.dart';
|
||||
import 'package:wonders/ui/common/app_icons.dart';
|
||||
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||
import 'package:wonders/ui/common/controls/locale_switcher.dart';
|
||||
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
import 'package:wonders/ui/common/wonderous_logo.dart';
|
||||
import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart';
|
||||
|
||||
@ -68,7 +70,7 @@ class _HomeMenuState extends State<HomeMenu> {
|
||||
Gap(50),
|
||||
Gap($styles.insets.md),
|
||||
_buildIconGrid(context)
|
||||
.animate()
|
||||
.maybeAnimate()
|
||||
.fade(duration: $styles.times.fast)
|
||||
.scale(begin: Offset(.8, .8), curve: Curves.easeOut),
|
||||
Gap($styles.insets.lg),
|
||||
@ -127,9 +129,9 @@ class _HomeMenuState extends State<HomeMenu> {
|
||||
valueListenable: settingsLogic.currentLocale,
|
||||
builder: (_, __, ___) {
|
||||
return SeparatedColumn(
|
||||
separatorBuilder: () => Divider(thickness: 1.5, height: 1).animate().scale(
|
||||
separatorBuilder: () => Divider(thickness: 1.5, height: 1).maybeAnimate().scale(
|
||||
duration: $styles.times.slow,
|
||||
delay: $styles.times.pageTransition + 200.ms,
|
||||
delay: $styles.times.pageTransition + 200.delayMs,
|
||||
curve: Curves.easeOutBack,
|
||||
),
|
||||
children: [
|
||||
@ -147,8 +149,8 @@ class _HomeMenuState extends State<HomeMenu> {
|
||||
onPressed: () => _handleAboutPressed(context),
|
||||
),
|
||||
]
|
||||
.animate(interval: 50.ms)
|
||||
.fade(delay: $styles.times.pageTransition + 50.ms)
|
||||
.animate(interval: 50.delayMs)
|
||||
.fade(delay: $styles.times.pageTransition + 50.delayMs)
|
||||
.slide(begin: Offset(0, .1), curve: Curves.easeOut),
|
||||
);
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ import 'package:wonders/ui/common/previous_next_navigation.dart';
|
||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||
import 'package:wonders/ui/common/themed_text.dart';
|
||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
class IntroScreen extends StatefulWidget {
|
||||
const IntroScreen({super.key});
|
||||
@ -60,7 +61,7 @@ class _IntroScreenState extends State<IntroScreen> {
|
||||
final int current = _pageController.page!.round();
|
||||
if (_isOnLastPage && dir > 0) return;
|
||||
if (_isOnFirstPage && dir < 0) return;
|
||||
_pageController.animateToPage(current + dir, duration: 250.ms, curve: Curves.easeIn);
|
||||
_pageController.animateToPage(current + dir, duration: $styles.times.fast, curve: Curves.easeIn);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -85,7 +86,7 @@ class _IntroScreenState extends State<IntroScreen> {
|
||||
color: $styles.colors.black,
|
||||
child: SafeArea(
|
||||
child: Animate(
|
||||
delay: 500.ms,
|
||||
delay: 500.delayMs,
|
||||
effects: const [FadeEffect()],
|
||||
child: PreviousNextNavigation(
|
||||
maxWidth: 600,
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/data/unsplash_photo_data.dart';
|
||||
import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
||||
import 'package:wonders/ui/common/controls/eight_way_swipe_detector.dart';
|
||||
@ -258,7 +259,7 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
||||
imgUrl,
|
||||
fit: BoxFit.cover,
|
||||
size: UnsplashPhotoSize.large,
|
||||
).animate().fade(),
|
||||
).maybeAnimate().fade(),
|
||||
);
|
||||
|
||||
return MergeSemantics(
|
||||
|
@ -3,6 +3,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/common/debouncer.dart';
|
||||
import 'package:wonders/logic/common/string_utils.dart';
|
||||
import 'package:wonders/logic/data/timeline_data.dart';
|
||||
@ -15,6 +16,7 @@ import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||
import 'package:wonders/ui/common/list_gradient.dart';
|
||||
import 'package:wonders/ui/common/timeline_event_card.dart';
|
||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
import 'package:wonders/ui/common/wonders_timeline_builder.dart';
|
||||
|
||||
part 'widgets/_animated_era_text.dart';
|
||||
|
@ -11,6 +11,6 @@ class _AnimatedEraText extends StatelessWidget {
|
||||
return Semantics(
|
||||
liveRegion: true,
|
||||
child: Text(era, style: style),
|
||||
).animate(key: ValueKey(era)).fadeIn().slide(begin: Offset(0, .2));
|
||||
).maybeAnimate(key: ValueKey(era)).fadeIn().slide(begin: Offset(0, .2));
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class _EventPopups extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _EventPopupsState extends State<_EventPopups> {
|
||||
final _debouncer = Debouncer(500.ms);
|
||||
final _debouncer = Debouncer(500.animateMs);
|
||||
TimelineEvent? _eventToShow;
|
||||
|
||||
@override
|
||||
|
@ -66,7 +66,7 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
||||
child: Stack(
|
||||
children: [
|
||||
// Main content area
|
||||
_buildScrollingArea(context).animate().fadeIn(),
|
||||
_buildScrollingArea(context).maybeAnimate().fadeIn(),
|
||||
|
||||
// Dashed line with a year that changes as we scroll
|
||||
IgnorePointerWithSemantics(
|
||||
|
@ -25,7 +25,7 @@ class _ScrollingViewportController extends ChangeNotifier {
|
||||
final data = wondersLogic.getData(w);
|
||||
final pos = calculateScrollPosFromYear(data.startYr);
|
||||
scroller.jumpTo(pos - 200);
|
||||
scroller.animateTo(pos, duration: 1.35.seconds, curve: Curves.easeOutCubic);
|
||||
scroller.animateTo(pos, duration: $styles.times.extraSlow, curve: Curves.easeOutCubic);
|
||||
scroller.addListener(_updateCurrentYear);
|
||||
}
|
||||
});
|
||||
|
@ -49,11 +49,11 @@ class _EventsListState extends State<_EventsList> {
|
||||
|
||||
final listItems = <Widget>[];
|
||||
for (var e in events.entries) {
|
||||
final delay = 100.ms + (100 * listItems.length).ms;
|
||||
final delay = (100 + (100 * listItems.length)).delayMs;
|
||||
listItems.add(
|
||||
TimelineEventCard(year: e.key, text: e.value, darkMode: true)
|
||||
.animate()
|
||||
.fade(delay: delay, duration: $styles.times.med * 1.5)
|
||||
.maybeAnimate()
|
||||
.fade(delay: delay, duration: $styles.times.slow)
|
||||
.slide(begin: Offset(0, 1), curve: Curves.easeOutBack),
|
||||
);
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class _WonderImageWithTimeline extends StatelessWidget {
|
||||
_buildDot(context),
|
||||
Text(StringUtils.getEra(data.startYr), style: textStyle),
|
||||
],
|
||||
).animate().fade(delay: $styles.times.pageTransition);
|
||||
).maybeAnimate().fade(delay: $styles.times.pageTransition);
|
||||
}
|
||||
|
||||
Widget _buildDot(BuildContext context) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/logic/common/animate_utils.dart';
|
||||
import 'package:wonders/logic/common/platform_info.dart';
|
||||
import 'package:wonders/logic/common/string_utils.dart';
|
||||
import 'package:wonders/logic/data/wonder_data.dart';
|
||||
@ -12,6 +13,7 @@ import 'package:wonders/ui/common/ignore_pointer.dart';
|
||||
import 'package:wonders/ui/common/list_gradient.dart';
|
||||
import 'package:wonders/ui/common/themed_text.dart';
|
||||
import 'package:wonders/ui/common/timeline_event_card.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
import 'package:wonders/ui/common/wonders_timeline_builder.dart';
|
||||
import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
|
||||
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:wonders/common_libs.dart';
|
||||
import 'package:wonders/ui/common/utils/context_utils.dart';
|
||||
import 'package:wonders/ui/common/utils/duration_utils.dart';
|
||||
|
||||
// TODO: Clouds should fade in and out
|
||||
// Shows a set of clouds that animated onto stage.
|
||||
@ -21,7 +22,7 @@ class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin {
|
||||
class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProviderStateMixin, GetItStateMixin {
|
||||
late List<_Cloud> _clouds = [];
|
||||
List<_Cloud> _oldClouds = [];
|
||||
late final AnimationController _anim = AnimationController(vsync: this, duration: 1500.ms);
|
||||
late final AnimationController _anim = AnimationController(vsync: this, duration: 1500.animateMs);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -136,7 +136,7 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
|
||||
children: [
|
||||
if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)),
|
||||
if (uiImage != null) ...[
|
||||
widget.enableHero ? Hero(tag: '$type-${widget.fileName}', child: content!) : content!,
|
||||
widget.enableHero && !$styles.disableAnimations ? Hero(tag: '$type-${widget.fileName}', child: content!) : content!,
|
||||
],
|
||||
if (widget.top != null) Positioned.fill(child: widget.top!.call(context)),
|
||||
],
|
||||
|
@ -31,5 +31,63 @@
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "screenshots/screen1.png",
|
||||
"sizes": "800x600",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide",
|
||||
"label": "Wonderous Welcome Screen"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/screen2.png",
|
||||
"sizes": "800x600",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide",
|
||||
"label": "Read about 8 amazing man-made wonders"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/screen3.png",
|
||||
"sizes": "800x600",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide",
|
||||
"label": "Enjoy photos and videos from all around the world"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/screen4.png",
|
||||
"sizes": "800x600",
|
||||
"type": "image/png",
|
||||
"form_factor": "wide",
|
||||
"label": "Explore artifacts and relics from the past"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile1.png",
|
||||
"sizes": "864x1872",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow",
|
||||
"label": "Wonderous Welcome Screen"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile2.png",
|
||||
"sizes": "864x1872",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow",
|
||||
"label": "Read about 8 amazing man-made wonders"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile3.png",
|
||||
"sizes": "864x1872",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow",
|
||||
"label": "Enjoy photos and videos from all around the world"
|
||||
},
|
||||
{
|
||||
"src": "screenshots/mobile4.png",
|
||||
"sizes": "864x1872",
|
||||
"type": "image/png",
|
||||
"form_factor": "narrow",
|
||||
"label": "Explore artifacts and relics from the past"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
BIN
web/screenshots/mobile1.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
web/screenshots/mobile2.png
Normal file
After Width: | Height: | Size: 987 KiB |
BIN
web/screenshots/mobile3.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
web/screenshots/mobile4.png
Normal file
After Width: | Height: | Size: 854 KiB |
BIN
web/screenshots/screen1.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
web/screenshots/screen2.png
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
web/screenshots/screen3.png
Normal file
After Width: | Height: | Size: 628 KiB |
BIN
web/screenshots/screen4.png
Normal file
After Width: | Height: | Size: 606 KiB |