Merge pull request #60 from gskinnerTeam/feature-v2-polish
Feature v2 polish
This commit is contained in:
commit
e107cd287f
@ -11,11 +11,12 @@ class AppLogic {
|
|||||||
/// The router will use this to prevent redirects while bootstrapping.
|
/// The router will use this to prevent redirects while bootstrapping.
|
||||||
bool isBootstrapComplete = false;
|
bool isBootstrapComplete = false;
|
||||||
|
|
||||||
bool get isDesktopOrTablet => PlatformInfo.isDesktopOrWeb || deviceSize.shortestSide > 480;
|
bool get isLandscapeEnabled =>
|
||||||
|
PlatformInfo.isDesktopOrWeb || deviceSize.shortestSide > 500;
|
||||||
|
|
||||||
/// Support portrait and landscape on desktop, web and tablets. Stick to portrait for phones.
|
/// Support portrait and landscape on desktop, web and tablets. Stick to portrait for phones.
|
||||||
/// A return value of null indicated both orientations are supported.
|
/// A return value of null indicated both orientations are supported.
|
||||||
Axis? get supportedOrientations => isDesktopOrTablet ? null : Axis.vertical;
|
Axis? get supportedOrientations => isLandscapeEnabled ? null : Axis.vertical;
|
||||||
|
|
||||||
Size get deviceSize {
|
Size get deviceSize {
|
||||||
final w = WidgetsBinding.instance.platformDispatcher.views.first;
|
final w = WidgetsBinding.instance.platformDispatcher.views.first;
|
||||||
@ -25,9 +26,8 @@ class AppLogic {
|
|||||||
/// Initialize the app and all main actors.
|
/// Initialize the app and all main actors.
|
||||||
/// Loads settings, sets up services etc.
|
/// Loads settings, sets up services etc.
|
||||||
Future<void> bootstrap() async {
|
Future<void> bootstrap() async {
|
||||||
debugPrint('bootstrap app, deviceSize: $deviceSize, isTablet: $isDesktopOrTablet');
|
debugPrint(
|
||||||
// Default error handler
|
'bootstrap app, deviceSize: $deviceSize, isTablet: $isLandscapeEnabled');
|
||||||
FlutterError.onError = _handleFlutterError;
|
|
||||||
|
|
||||||
// Set min-sizes for desktop apps
|
// Set min-sizes for desktop apps
|
||||||
if (PlatformInfo.isDesktop) {
|
if (PlatformInfo.isDesktop) {
|
||||||
@ -89,13 +89,10 @@ class AppLogic {
|
|||||||
SystemChrome.setPreferredOrientations(orientations);
|
SystemChrome.setPreferredOrientations(orientations);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleFlutterError(FlutterErrorDetails details) {
|
Future<T?> showFullscreenDialogRoute<T>(BuildContext context, Widget child,
|
||||||
FlutterError.dumpErrorToConsole(details);
|
{bool transparent = false}) async {
|
||||||
}
|
|
||||||
|
|
||||||
Future<T?> showFullscreenDialogRoute<T>(BuildContext context, Widget child) async {
|
|
||||||
return await Navigator.of(context).push<T>(
|
return await Navigator.of(context).push<T>(
|
||||||
PageRoutes.dialog<T>(child, $styles.times.pageTransition),
|
PageRoutes.dialog<T>(child, duration: $styles.times.pageTransition),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ class CollectibleItem extends StatelessWidget with GetItMixin {
|
|||||||
|
|
||||||
void _handleTap(BuildContext context) async {
|
void _handleTap(BuildContext context) async {
|
||||||
final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider);
|
final screen = CollectibleFoundScreen(collectible: collectible, imageProvider: _imageProvider);
|
||||||
appLogic.showFullscreenDialogRoute(context, screen);
|
appLogic.showFullscreenDialogRoute(context, screen, transparent: true);
|
||||||
AppHaptics.mediumImpact();
|
AppHaptics.mediumImpact();
|
||||||
|
|
||||||
// wait to update the state, to ensure the hero works properly:
|
// wait to update the state, to ensure the hero works properly:
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
|
|
||||||
class SimpleHeader extends StatelessWidget {
|
class AppHeader extends StatelessWidget {
|
||||||
const SimpleHeader(this.title,
|
const AppHeader(
|
||||||
{Key? key, this.subtitle, this.showBackBtn = true, this.isTransparent = false, this.onBack, this.trailing})
|
{Key? key,
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.showBackBtn = true,
|
||||||
|
this.isTransparent = false,
|
||||||
|
this.onBack,
|
||||||
|
this.trailing})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
final String title;
|
final String? title;
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
final bool showBackBtn;
|
final bool showBackBtn;
|
||||||
final bool isTransparent;
|
final bool isTransparent;
|
||||||
@ -40,10 +46,12 @@ class SimpleHeader extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (title != null)
|
||||||
Text(
|
Text(
|
||||||
title.toUpperCase(),
|
title!.toUpperCase(),
|
||||||
textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false),
|
textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false),
|
||||||
style: $styles.text.h4.copyWith(color: $styles.colors.offWhite, fontWeight: FontWeight.w500),
|
style:
|
||||||
|
$styles.text.h4.copyWith(color: $styles.colors.offWhite, fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
if (subtitle != null)
|
if (subtitle != null)
|
||||||
Text(
|
Text(
|
@ -123,8 +123,8 @@ class _ScrollDecoratorState extends State<ScrollDecorator> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
content = widget.builder(currentController);
|
content = widget.builder(currentController);
|
||||||
return ListenableBuilder(
|
return AnimatedBuilder(
|
||||||
listenable: currentController,
|
animation: currentController,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
|
@ -8,8 +8,8 @@ class FadeColorTransition extends StatelessWidget {
|
|||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ListenableBuilder(
|
Widget build(BuildContext context) => AnimatedBuilder(
|
||||||
listenable: animation,
|
animation: animation,
|
||||||
builder: (_, __) => Container(color: color.withOpacity(animation.value)),
|
builder: (_, __) => Container(color: color.withOpacity(animation.value)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
|
|
||||||
/// Replacement for the built in [AnimatedBuilder] because that name is semantically confusing.
|
|
||||||
class ListenableBuilder extends AnimatedBuilder {
|
|
||||||
const ListenableBuilder({
|
|
||||||
super.key,
|
|
||||||
required Listenable listenable,
|
|
||||||
required super.builder,
|
|
||||||
super.child,
|
|
||||||
}) : super(animation: listenable);
|
|
||||||
}
|
|
@ -87,7 +87,11 @@ class _BaseContentModal extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return IntrinsicHeight(
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: $styles.sizes.maxContentWidth3,
|
||||||
|
child: Padding(
|
||||||
padding: EdgeInsets.all($styles.insets.lg),
|
padding: EdgeInsets.all($styles.insets.lg),
|
||||||
child: LightText(
|
child: LightText(
|
||||||
child: SeparatedColumn(
|
child: SeparatedColumn(
|
||||||
@ -102,6 +106,9 @@ class _BaseContentModal extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ class _FullscreenUrlImgViewerState extends State<FullscreenUrlImgViewer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget content = ListenableBuilder(
|
Widget content = AnimatedBuilder(
|
||||||
listenable: _isZoomed,
|
animation: _isZoomed,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1;
|
final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1;
|
||||||
return PageView.builder(
|
return PageView.builder(
|
||||||
|
16
lib/ui/common/pop_navigator_underlay.dart
Normal file
16
lib/ui/common/pop_navigator_underlay.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:wonders/common_libs.dart';
|
||||||
|
|
||||||
|
class PopNavigatorUnderlay extends StatelessWidget {
|
||||||
|
const PopNavigatorUnderlay({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ExcludeSemantics(
|
||||||
|
child: AppBtn.basic(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
semanticLabel: '',
|
||||||
|
child: const SizedBox.expand(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
class PageRoutes {
|
class PageRoutes {
|
||||||
static const Duration kDefaultDuration = Duration(milliseconds: 300);
|
static const Duration kDefaultDuration = Duration(milliseconds: 300);
|
||||||
|
|
||||||
static Route<T> dialog<T>(Widget child, [Duration duration = kDefaultDuration, bool opaque = false]) {
|
static Route<T> dialog<T>(Widget child, {Duration duration = kDefaultDuration, bool opaque = false}) {
|
||||||
|
// Use cupertino routes for all dialogs so we get the 'swipe right to go back' behavior
|
||||||
|
if (opaque) {
|
||||||
|
return CupertinoPageRoute(builder: (_) => child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SB: Removed this in favor of Cupertino routes, we could restore with a `useFade` option
|
||||||
return PageRouteBuilder<T>(
|
return PageRouteBuilder<T>(
|
||||||
transitionDuration: duration,
|
transitionDuration: duration,
|
||||||
reverseTransitionDuration: duration,
|
reverseTransitionDuration: duration,
|
||||||
|
@ -4,7 +4,7 @@ import 'package:wonders/common_libs.dart';
|
|||||||
import 'package:wonders/logic/data/highlight_data.dart';
|
import 'package:wonders/logic/data/highlight_data.dart';
|
||||||
import 'package:wonders/ui/common/app_icons.dart';
|
import 'package:wonders/ui/common/app_icons.dart';
|
||||||
import 'package:wonders/ui/common/controls/app_page_indicator.dart';
|
import 'package:wonders/ui/common/controls/app_page_indicator.dart';
|
||||||
import 'package:wonders/ui/common/controls/simple_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||||
|
|
||||||
part 'widgets/_blurred_image_bg.dart';
|
part 'widgets/_blurred_image_bg.dart';
|
||||||
@ -122,8 +122,8 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// Header
|
/// Header
|
||||||
SimpleHeader(
|
AppHeader(
|
||||||
$strings.artifactsTitleArtifacts,
|
title: $strings.artifactsTitleArtifacts,
|
||||||
showBackBtn: false,
|
showBackBtn: false,
|
||||||
isTransparent: true,
|
isTransparent: true,
|
||||||
trailing: (context) => CircleBtn(
|
trailing: (context) => CircleBtn(
|
||||||
|
@ -5,8 +5,8 @@ import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
|
|||||||
import 'package:wonders/ui/common/gradient_container.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/modals/fullscreen_url_img_viewer.dart';
|
||||||
|
|
||||||
part 'widgets/_content.dart';
|
part 'widgets/_info_column.dart';
|
||||||
part 'widgets/_header.dart';
|
part 'widgets/_image_btn.dart';
|
||||||
|
|
||||||
class ArtifactDetailsScreen extends StatefulWidget {
|
class ArtifactDetailsScreen extends StatefulWidget {
|
||||||
const ArtifactDetailsScreen({Key? key, required this.artifactId}) : super(key: key);
|
const ArtifactDetailsScreen({Key? key, required this.artifactId}) : super(key: key);
|
||||||
@ -36,8 +36,8 @@ class _ArtifactDetailsScreenState extends State<ArtifactDetailsScreen> {
|
|||||||
} else {
|
} else {
|
||||||
content = hzMode
|
content = hzMode
|
||||||
? Row(children: [
|
? Row(children: [
|
||||||
Expanded(child: _Header(data: data!)),
|
Expanded(child: _ImageBtn(data: data!)),
|
||||||
Expanded(child: Center(child: SizedBox(width: 600, child: _Content(data: data)))),
|
Expanded(child: Center(child: SizedBox(width: 600, child: _InfoColumn(data: data)))),
|
||||||
])
|
])
|
||||||
: CustomScrollView(
|
: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
@ -47,9 +47,9 @@ class _ArtifactDetailsScreenState extends State<ArtifactDetailsScreen> {
|
|||||||
leading: SizedBox.shrink(),
|
leading: SizedBox.shrink(),
|
||||||
expandedHeight: context.heightPx * .5,
|
expandedHeight: context.heightPx * .5,
|
||||||
collapsedHeight: context.heightPx * .35,
|
collapsedHeight: context.heightPx * .35,
|
||||||
flexibleSpace: _Header(data: data!),
|
flexibleSpace: _ImageBtn(data: data!),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: _Content(data: data)),
|
SliverToBoxAdapter(child: _InfoColumn(data: data)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
part of '../artifact_details_screen.dart';
|
part of '../artifact_details_screen.dart';
|
||||||
|
|
||||||
class _Header extends StatelessWidget {
|
class _ImageBtn extends StatelessWidget {
|
||||||
const _Header({Key? key, required this.data}) : super(key: key);
|
const _ImageBtn({Key? key, required this.data}) : super(key: key);
|
||||||
final ArtifactData data;
|
final ArtifactData data;
|
||||||
|
|
||||||
@override
|
@override
|
@ -1,7 +1,7 @@
|
|||||||
part of '../artifact_details_screen.dart';
|
part of '../artifact_details_screen.dart';
|
||||||
|
|
||||||
class _Content extends StatelessWidget {
|
class _InfoColumn extends StatelessWidget {
|
||||||
const _Content({Key? key, required this.data}) : super(key: key);
|
const _InfoColumn({Key? key, required this.data}) : super(key: key);
|
||||||
final ArtifactData data;
|
final ArtifactData data;
|
||||||
|
|
||||||
@override
|
@override
|
@ -3,7 +3,7 @@ import 'package:wonders/common_libs.dart';
|
|||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
import 'package:wonders/logic/data/wonder_data.dart';
|
||||||
import 'package:wonders/logic/data/wonders_data/search/search_data.dart';
|
import 'package:wonders/logic/data/wonders_data/search/search_data.dart';
|
||||||
import 'package:wonders/ui/common/app_icons.dart';
|
import 'package:wonders/ui/common/app_icons.dart';
|
||||||
import 'package:wonders/ui/common/controls/simple_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/static_text_scale.dart';
|
import 'package:wonders/ui/common/static_text_scale.dart';
|
||||||
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
import 'package:wonders/ui/common/utils/app_haptics.dart';
|
||||||
import 'package:wonders/ui/screens/artifact/artifact_search/time_range_selector/expanding_time_range_selector.dart';
|
import 'package:wonders/ui/screens/artifact/artifact_search/time_range_selector/expanding_time_range_selector.dart';
|
||||||
@ -38,7 +38,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_search();
|
_updateResults();
|
||||||
panelController.addListener(() {
|
panelController.addListener(() {
|
||||||
AppHaptics.lightImpact();
|
AppHaptics.lightImpact();
|
||||||
});
|
});
|
||||||
@ -47,16 +47,18 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
|||||||
|
|
||||||
void _handleSearchSubmitted(String query) {
|
void _handleSearchSubmitted(String query) {
|
||||||
_query = query;
|
_query = query;
|
||||||
_search();
|
_updateResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTimelineChanged(double start, double end) {
|
void _handleTimelineChanged(double start, double end) {
|
||||||
_startYear = start;
|
_startYear = start;
|
||||||
_endYear = end;
|
_endYear = end;
|
||||||
_filter();
|
_updateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _search() {
|
void _handleResultPressed(SearchData o) => context.push(ScreenPaths.artifact(o.id.toString()));
|
||||||
|
|
||||||
|
void _updateResults() {
|
||||||
if (_query.isEmpty) {
|
if (_query.isEmpty) {
|
||||||
_searchResults = wonder.searchData;
|
_searchResults = wonder.searchData;
|
||||||
} else {
|
} else {
|
||||||
@ -66,10 +68,10 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
|||||||
_searchResults = wonder.searchData.where((o) => o.title.contains(q) || o.keywords.contains(q)).toList();
|
_searchResults = wonder.searchData.where((o) => o.title.contains(q) || o.keywords.contains(q)).toList();
|
||||||
}
|
}
|
||||||
vizController.value = _searchResults;
|
vizController.value = _searchResults;
|
||||||
_filter();
|
_updateFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _filter() {
|
void _updateFilter() {
|
||||||
_filteredResults = _searchResults.where((o) => o.year >= _startYear && o.year <= _endYear).toList();
|
_filteredResults = _searchResults.where((o) => o.year >= _startYear && o.year <= _endYear).toList();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@ -79,11 +81,11 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
|||||||
// tone down the orange just a bit:
|
// tone down the orange just a bit:
|
||||||
vizController.color = Color.lerp($styles.colors.accent1, $styles.colors.black, 0.2)!;
|
vizController.color = Color.lerp($styles.colors.accent1, $styles.colors.black, 0.2)!;
|
||||||
Widget content = GestureDetector(
|
Widget content = GestureDetector(
|
||||||
onTap: () => WidgetsBinding.instance.focusManager.primaryFocus?.unfocus(),
|
onTap: FocusManager.instance.primaryFocus?.unfocus,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
SimpleHeader($strings.artifactsSearchTitleBrowse, subtitle: wonder.title),
|
AppHeader(title: $strings.artifactsSearchTitleBrowse, subtitle: wonder.title),
|
||||||
Gap($styles.insets.xs),
|
Gap($styles.insets.xs),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
|
padding: EdgeInsets.symmetric(horizontal: $styles.insets.sm),
|
||||||
@ -101,7 +103,7 @@ class _ArtifactSearchScreenState extends State<ArtifactSearchScreen> with GetItS
|
|||||||
? _buildEmptyIndicator(context)
|
? _buildEmptyIndicator(context)
|
||||||
: _ResultsGrid(
|
: _ResultsGrid(
|
||||||
searchResults: _filteredResults,
|
searchResults: _filteredResults,
|
||||||
onPressed: (o) => context.push(ScreenPaths.artifact(o.id.toString())),
|
onPressed: _handleResultPressed,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -34,8 +34,16 @@ class _SearchInput extends StatelessWidget {
|
|||||||
Widget _buildSuggestionsView(BuildContext context, onSelected, Iterable<String> results, BoxConstraints constraints) {
|
Widget _buildSuggestionsView(BuildContext context, onSelected, Iterable<String> results, BoxConstraints constraints) {
|
||||||
List<Widget> items = results.map((str) => _buildSuggestion(context, str, () => onSelected(str))).toList();
|
List<Widget> items = results.map((str) => _buildSuggestion(context, str, () => onSelected(str))).toList();
|
||||||
items.insert(0, _buildSuggestionTitle(context));
|
items.insert(0, _buildSuggestionTitle(context));
|
||||||
|
return Stack(
|
||||||
return TopLeft(
|
children: [
|
||||||
|
ExcludeSemantics(
|
||||||
|
child: AppBtn.basic(
|
||||||
|
onPressed: FocusManager.instance.primaryFocus!.unfocus,
|
||||||
|
semanticLabel: '',
|
||||||
|
child: SizedBox.expand(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TopLeft(
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: EdgeInsets.only(top: $styles.insets.xxs),
|
margin: EdgeInsets.only(top: $styles.insets.xxs),
|
||||||
width: constraints.maxWidth,
|
width: constraints.maxWidth,
|
||||||
@ -64,6 +72,8 @@ class _SearchInput extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import 'package:particle_field/particle_field.dart';
|
import 'package:particle_field/particle_field.dart';
|
||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
import 'package:wonders/logic/data/collectible_data.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/centered_box.dart';
|
||||||
|
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
|
||||||
|
|
||||||
part 'widgets/_animated_ribbon.dart';
|
part 'widgets/_animated_ribbon.dart';
|
||||||
part 'widgets/_celebration_particles.dart';
|
part 'widgets/_celebration_particles.dart';
|
||||||
@ -49,8 +51,19 @@ class CollectibleFoundScreen extends StatelessWidget {
|
|||||||
Widget _buildDetail(BuildContext context) {
|
Widget _buildDetail(BuildContext context) {
|
||||||
Duration t = $styles.times.fast;
|
Duration t = $styles.times.fast;
|
||||||
return Stack(key: ValueKey('detail'), children: [
|
return Stack(key: ValueKey('detail'), children: [
|
||||||
Animate().custom(duration: t, builder: (context, ratio, _) => _buildGradient(context, 1, ratio)),
|
/// Background
|
||||||
|
AppBackdrop(
|
||||||
|
strength: .5,
|
||||||
|
child: Container(color: $styles.colors.greyStrong.withOpacity(.96)),
|
||||||
|
).animate().fadeIn(),
|
||||||
|
|
||||||
|
/// Particles
|
||||||
_CelebrationParticles(fadeMs: (t * 6).inMilliseconds),
|
_CelebrationParticles(fadeMs: (t * 6).inMilliseconds),
|
||||||
|
|
||||||
|
/// invisible close btn
|
||||||
|
PopNavigatorUnderlay(),
|
||||||
|
|
||||||
|
/// Content
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: CenteredBox(
|
child: CenteredBox(
|
||||||
width: $styles.sizes.maxContentWidth3,
|
width: $styles.sizes.maxContentWidth3,
|
||||||
|
@ -5,7 +5,7 @@ import 'package:wonders/logic/collectibles_logic.dart';
|
|||||||
import 'package:wonders/logic/data/collectible_data.dart';
|
import 'package:wonders/logic/data/collectible_data.dart';
|
||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
import 'package:wonders/logic/data/wonder_data.dart';
|
||||||
import 'package:wonders/ui/common/centered_box.dart';
|
import 'package:wonders/ui/common/centered_box.dart';
|
||||||
import 'package:wonders/ui/common/controls/simple_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/modals/app_modals.dart';
|
import 'package:wonders/ui/common/modals/app_modals.dart';
|
||||||
|
|
||||||
part 'widgets/_collectible_image.dart';
|
part 'widgets/_collectible_image.dart';
|
||||||
@ -63,7 +63,7 @@ class _CollectionScreenState extends State<CollectionScreen> with GetItStateMixi
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
SimpleHeader($strings.collectionTitleCollection),
|
AppHeader(title: $strings.collectionTitleCollection),
|
||||||
_NewlyDiscoveredItemsBtn(count: discovered, onPressed: _scrollToTarget),
|
_NewlyDiscoveredItemsBtn(count: discovered, onPressed: _scrollToTarget),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: _CollectionList(
|
child: _CollectionList(
|
||||||
|
@ -164,8 +164,8 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// Home Btn
|
/// Home Btn
|
||||||
ListenableBuilder(
|
AnimatedBuilder(
|
||||||
listenable: _scroller,
|
animation: _scroller,
|
||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
return AnimatedOpacity(
|
return AnimatedOpacity(
|
||||||
opacity: _scrollPos.value > 0 ? 0 : 1,
|
opacity: _scrollPos.value > 0 ? 0 : 1,
|
||||||
|
@ -94,8 +94,8 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(_) {
|
Widget build(_) {
|
||||||
return ListenableBuilder(
|
return AnimatedBuilder(
|
||||||
listenable: _anim,
|
animation: _anim,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
var rot = _prevIndex > widget.index ? -pi : pi;
|
var rot = _prevIndex > widget.index ? -pi : pi;
|
||||||
return Transform.rotate(
|
return Transform.rotate(
|
||||||
|
@ -49,8 +49,8 @@ class _TitleText extends StatelessWidget {
|
|||||||
/// Wonder title text
|
/// Wonder title text
|
||||||
Semantics(
|
Semantics(
|
||||||
sortKey: OrdinalSortKey(0),
|
sortKey: OrdinalSortKey(0),
|
||||||
child: ListenableBuilder(
|
child: AnimatedBuilder(
|
||||||
listenable: scroller,
|
animation: scroller,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
final yPos = ContextUtils.getGlobalPos(context)?.dy ?? 0;
|
final yPos = ContextUtils.getGlobalPos(context)?.dy ?? 0;
|
||||||
bool enableHero = yPos > -100;
|
bool enableHero = yPos > -100;
|
||||||
@ -71,8 +71,8 @@ class _TitleText extends StatelessWidget {
|
|||||||
ExcludeSemantics(
|
ExcludeSemantics(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: $styles.insets.md),
|
padding: EdgeInsets.symmetric(horizontal: $styles.insets.md),
|
||||||
child: ListenableBuilder(
|
child: AnimatedBuilder(
|
||||||
listenable: scroller,
|
animation: scroller,
|
||||||
builder: (_, __) => CompassDivider(
|
builder: (_, __) => CompassDivider(
|
||||||
isExpanded: scroller.position.pixels <= 0,
|
isExpanded: scroller.position.pixels <= 0,
|
||||||
linesColor: data.type.fgColor,
|
linesColor: data.type.fgColor,
|
||||||
|
@ -11,7 +11,7 @@ class _TopIllustration extends StatelessWidget {
|
|||||||
WonderIllustration(type, config: WonderIllustrationConfig.bg(enableAnims: false, shortMode: true)),
|
WonderIllustration(type, config: WonderIllustrationConfig.bg(enableAnims: false, shortMode: true)),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
bottom: 50,
|
bottom: 50,
|
||||||
child: AnimatedClouds(wonderType: type, enableAnimations: false, opacity: .5),
|
child: AnimatedClouds(wonderType: type, enableAnimations: false, opacity: .5, cloudSize: 400),
|
||||||
),
|
),
|
||||||
Transform.translate(
|
Transform.translate(
|
||||||
// Small bump down to make sure we cover the edge between the editorial page and the sky.
|
// Small bump down to make sure we cover the edge between the editorial page and the sky.
|
||||||
|
@ -62,6 +62,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
WonderType? pickedWonder = await appLogic.showFullscreenDialogRoute<WonderType>(
|
WonderType? pickedWonder = await appLogic.showFullscreenDialogRoute<WonderType>(
|
||||||
context,
|
context,
|
||||||
HomeMenu(data: currentWonder),
|
HomeMenu(data: currentWonder),
|
||||||
|
transparent: true,
|
||||||
);
|
);
|
||||||
setState(() => _isMenuOpen = false);
|
setState(() => _isMenuOpen = false);
|
||||||
if (pickedWonder != null) {
|
if (pickedWonder != null) {
|
||||||
|
@ -5,6 +5,7 @@ import 'package:wonders/logic/data/wonder_data.dart';
|
|||||||
import 'package:wonders/ui/common/app_backdrop.dart';
|
import 'package:wonders/ui/common/app_backdrop.dart';
|
||||||
import 'package:wonders/ui/common/app_icons.dart';
|
import 'package:wonders/ui/common/app_icons.dart';
|
||||||
import 'package:wonders/ui/common/controls/locale_switcher.dart';
|
import 'package:wonders/ui/common/controls/locale_switcher.dart';
|
||||||
|
import 'package:wonders/ui/common/pop_navigator_underlay.dart';
|
||||||
import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart';
|
import 'package:wonders/ui/screens/home_menu/about_dialog_content.dart';
|
||||||
|
|
||||||
class HomeMenu extends StatelessWidget {
|
class HomeMenu extends StatelessWidget {
|
||||||
@ -48,11 +49,11 @@ class HomeMenu extends StatelessWidget {
|
|||||||
/// Backdrop / Underlay
|
/// Backdrop / Underlay
|
||||||
AppBackdrop(
|
AppBackdrop(
|
||||||
strength: .5,
|
strength: .5,
|
||||||
child: Container(
|
child: Container(color: $styles.colors.greyStrong.withOpacity(.7)),
|
||||||
color: $styles.colors.greyStrong.withOpacity(.7),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
PopNavigatorUnderlay(),
|
||||||
|
|
||||||
SafeArea(
|
SafeArea(
|
||||||
child: PaddedRow(
|
child: PaddedRow(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
|
@ -107,15 +107,14 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
|
|
||||||
Future<void> _handleImageTapped(int index) async {
|
Future<void> _handleImageTapped(int index) async {
|
||||||
if (_index == index) {
|
if (_index == index) {
|
||||||
int? newIndex = await Navigator.push(
|
|
||||||
context,
|
|
||||||
CupertinoPageRoute(builder: (_) {
|
|
||||||
final urls = _photoIds.value.map((e) {
|
final urls = _photoIds.value.map((e) {
|
||||||
return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.med);
|
return UnsplashPhotoData.getSelfHostedUrl(e, UnsplashPhotoSize.med);
|
||||||
}).toList();
|
}).toList();
|
||||||
return FullscreenUrlImgViewer(urls: urls, index: _index);
|
int? newIndex = await appLogic.showFullscreenDialogRoute(
|
||||||
}),
|
context,
|
||||||
|
FullscreenUrlImgViewer(urls: urls, index: _index),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newIndex != null) {
|
if (newIndex != null) {
|
||||||
_setIndex(newIndex, skipAnimation: true);
|
_setIndex(newIndex, skipAnimation: true);
|
||||||
}
|
}
|
||||||
@ -137,13 +136,10 @@ class _PhotoGalleryState extends State<PhotoGallery> {
|
|||||||
return Center(child: AppLoadingIndicator());
|
return Center(child: AppLoadingIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
Size imgSize = context.isLandscape
|
Size imgSize = Size(context.widthPx * .5, context.heightPx * .5);
|
||||||
? Size(context.widthPx * .5, context.heightPx * .66)
|
|
||||||
: Size(context.widthPx * .66, context.heightPx * .5);
|
|
||||||
imgSize = (widget.imageSize ?? imgSize) * _scale;
|
imgSize = (widget.imageSize ?? imgSize) * _scale;
|
||||||
// Get transform offset for the current _index
|
// Get transform offset for the current _index
|
||||||
final padding = $styles.insets.md;
|
final padding = $styles.insets.md;
|
||||||
|
|
||||||
var gridOffset = _calculateCurrentOffset(padding, imgSize);
|
var gridOffset = _calculateCurrentOffset(padding, imgSize);
|
||||||
gridOffset += Offset(0, -context.mq.padding.top / 2);
|
gridOffset += Offset(0, -context.mq.padding.top / 2);
|
||||||
final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration;
|
final offsetTweenDuration = _skipNextOffsetTween ? Duration.zero : swipeDuration;
|
||||||
|
@ -9,7 +9,7 @@ import 'package:wonders/logic/data/timeline_data.dart';
|
|||||||
import 'package:wonders/logic/data/wonder_data.dart';
|
import 'package:wonders/logic/data/wonder_data.dart';
|
||||||
import 'package:wonders/ui/common/blend_mask.dart';
|
import 'package:wonders/ui/common/blend_mask.dart';
|
||||||
import 'package:wonders/ui/common/centered_box.dart';
|
import 'package:wonders/ui/common/centered_box.dart';
|
||||||
import 'package:wonders/ui/common/controls/simple_header.dart';
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/dashed_line.dart';
|
import 'package:wonders/ui/common/dashed_line.dart';
|
||||||
import 'package:wonders/ui/common/list_gradient.dart';
|
import 'package:wonders/ui/common/list_gradient.dart';
|
||||||
import 'package:wonders/ui/common/timeline_event_card.dart';
|
import 'package:wonders/ui/common/timeline_event_card.dart';
|
||||||
@ -55,7 +55,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
|||||||
padding: EdgeInsets.only(bottom: 0),
|
padding: EdgeInsets.only(bottom: 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SimpleHeader($strings.timelineTitleGlobalTimeline),
|
AppHeader(title: $strings.timelineTitleGlobalTimeline),
|
||||||
|
|
||||||
/// Vertically scrolling timeline, manages a ScrollController.
|
/// Vertically scrolling timeline, manages a ScrollController.
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -57,8 +57,8 @@ class _BottomScrubber extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// Visible area, follows the position of scroller
|
/// Visible area, follows the position of scroller
|
||||||
ListenableBuilder(
|
AnimatedBuilder(
|
||||||
listenable: scroller,
|
animation: scroller,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
ScrollPosition? pos;
|
ScrollPosition? pos;
|
||||||
if (scroller.hasClients) pos = scroller.position;
|
if (scroller.hasClients) pos = scroller.position;
|
||||||
|
@ -61,8 +61,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
|||||||
// Dashed line with a year that changes as we scroll
|
// Dashed line with a year that changes as we scroll
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
ignoringSemantics: false,
|
ignoringSemantics: false,
|
||||||
child: ListenableBuilder(
|
child: AnimatedBuilder(
|
||||||
listenable: controller.scroller,
|
animation: controller.scroller,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
return _DashedDividerWithYear(controller.calculateYearFromScrollPos());
|
return _DashedDividerWithYear(controller.calculateYearFromScrollPos());
|
||||||
},
|
},
|
||||||
@ -79,8 +79,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
|||||||
Widget buildTimelineSection(WonderData data) {
|
Widget buildTimelineSection(WonderData data) {
|
||||||
return ClipRRect(
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
child: ListenableBuilder(
|
child: AnimatedBuilder(
|
||||||
listenable: controller.scroller,
|
animation: controller.scroller,
|
||||||
builder: (_, __) => TimelineSection(
|
builder: (_, __) => TimelineSection(
|
||||||
data,
|
data,
|
||||||
controller.calculateYearFromScrollPos(),
|
controller.calculateYearFromScrollPos(),
|
||||||
@ -127,8 +127,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// Event Markers, rebuilds on scroll
|
/// Event Markers, rebuilds on scroll
|
||||||
ListenableBuilder(
|
AnimatedBuilder(
|
||||||
listenable: controller.scroller,
|
animation: controller.scroller,
|
||||||
builder: (_, __) => _EventMarkers(
|
builder: (_, __) => _EventMarkers(
|
||||||
controller.calculateYearFromScrollPos(),
|
controller.calculateYearFromScrollPos(),
|
||||||
onEventChanged: _handleEventMarkerChanged,
|
onEventChanged: _handleEventMarkerChanged,
|
||||||
|
@ -4,7 +4,11 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
|||||||
static double bottomPadding = 0;
|
static double bottomPadding = 0;
|
||||||
static double buttonInset = 12;
|
static double buttonInset = 12;
|
||||||
|
|
||||||
const WonderDetailsTabMenu({Key? key, required this.tabController, this.showBg = false, required this.wonderType})
|
const WonderDetailsTabMenu(
|
||||||
|
{Key? key,
|
||||||
|
required this.tabController,
|
||||||
|
this.showBg = false,
|
||||||
|
required this.wonderType})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final TabController tabController;
|
final TabController tabController;
|
||||||
@ -32,7 +36,10 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
// Buttons
|
// Buttons
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: $styles.insets.sm, right: $styles.insets.xxs, bottom: bottomPadding),
|
padding: EdgeInsets.only(
|
||||||
|
left: $styles.insets.sm,
|
||||||
|
right: $styles.insets.xxs,
|
||||||
|
bottom: bottomPadding),
|
||||||
// TabButtons are a Stack with a row of icon buttons, and an illustrated home button sitting on top.
|
// TabButtons are a Stack with a row of icon buttons, and an illustrated home button sitting on top.
|
||||||
// The home buttons shows / hides itself based on `showHomeBtn`
|
// The home buttons shows / hides itself based on `showHomeBtn`
|
||||||
// The row contains an animated placeholder gap which makes room for the icon as it transitions in.
|
// The row contains an animated placeholder gap which makes room for the icon as it transitions in.
|
||||||
@ -52,13 +59,21 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
|||||||
borderSize: showBg ? 6 : 2,
|
borderSize: showBg ? 6 : 2,
|
||||||
),
|
),
|
||||||
_TabBtn(0, tabController,
|
_TabBtn(0, tabController,
|
||||||
iconImg: 'editorial', label: $strings.wonderDetailsTabLabelInformation, color: iconColor),
|
iconImg: 'editorial',
|
||||||
|
label: $strings.wonderDetailsTabLabelInformation,
|
||||||
|
color: iconColor),
|
||||||
_TabBtn(1, tabController,
|
_TabBtn(1, tabController,
|
||||||
iconImg: 'photos', label: $strings.wonderDetailsTabLabelImages, color: iconColor),
|
iconImg: 'photos',
|
||||||
|
label: $strings.wonderDetailsTabLabelImages,
|
||||||
|
color: iconColor),
|
||||||
_TabBtn(2, tabController,
|
_TabBtn(2, tabController,
|
||||||
iconImg: 'artifacts', label: $strings.wonderDetailsTabLabelArtifacts, color: iconColor),
|
iconImg: 'artifacts',
|
||||||
|
label: $strings.wonderDetailsTabLabelArtifacts,
|
||||||
|
color: iconColor),
|
||||||
_TabBtn(3, tabController,
|
_TabBtn(3, tabController,
|
||||||
iconImg: 'timeline', label: $strings.wonderDetailsTabLabelEvents, color: iconColor),
|
iconImg: 'timeline',
|
||||||
|
label: $strings.wonderDetailsTabLabelEvents,
|
||||||
|
color: iconColor),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -72,7 +87,11 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _WonderHomeBtn extends StatelessWidget {
|
class _WonderHomeBtn extends StatelessWidget {
|
||||||
const _WonderHomeBtn({Key? key, required this.size, required this.wonderType, required this.borderSize})
|
const _WonderHomeBtn(
|
||||||
|
{Key? key,
|
||||||
|
required this.size,
|
||||||
|
required this.wonderType,
|
||||||
|
required this.borderSize})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final double size;
|
final double size;
|
||||||
@ -94,7 +113,8 @@ class _WonderHomeBtn extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(99),
|
borderRadius: BorderRadius.circular(99),
|
||||||
color: wonderType.fgColor,
|
color: wonderType.fgColor,
|
||||||
image: DecorationImage(image: AssetImage(wonderType.homeBtn), fit: BoxFit.fill),
|
image: DecorationImage(
|
||||||
|
image: AssetImage(wonderType.homeBtn), fit: BoxFit.fill),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -111,6 +131,9 @@ class _TabBtn extends StatelessWidget {
|
|||||||
required this.label,
|
required this.label,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
static const double _minWidth = 50;
|
||||||
|
static const double _maxWidth = 120;
|
||||||
|
|
||||||
final int index;
|
final int index;
|
||||||
final TabController tabController;
|
final TabController tabController;
|
||||||
final String iconImg;
|
final String iconImg;
|
||||||
@ -120,23 +143,29 @@ class _TabBtn extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool selected = tabController.index == index;
|
bool selected = tabController.index == index;
|
||||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
final MaterialLocalizations localizations =
|
||||||
final iconImgPath = '${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
|
MaterialLocalizations.of(context);
|
||||||
String tabLabel = localizations.tabLabel(tabIndex: index + 1, tabCount: tabController.length);
|
final iconImgPath =
|
||||||
|
'${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
|
||||||
|
String tabLabel = localizations.tabLabel(
|
||||||
|
tabIndex: index + 1, tabCount: tabController.length);
|
||||||
tabLabel = '$label: $tabLabel';
|
tabLabel = '$label: $tabLabel';
|
||||||
final double btnWidth = (context.widthPx / 6).clamp(50, 120);
|
final double btnWidth = (context.widthPx / 6).clamp(_minWidth, _maxWidth);
|
||||||
return MergeSemantics(
|
return MergeSemantics(
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
selected: selected,
|
selected: selected,
|
||||||
label: tabLabel,
|
label: tabLabel,
|
||||||
child: ExcludeSemantics(
|
child: ExcludeSemantics(
|
||||||
child: AppBtn.basic(
|
child: AppBtn.basic(
|
||||||
padding: EdgeInsets.only(top: $styles.insets.md + $styles.insets.xs, bottom: $styles.insets.sm),
|
padding: EdgeInsets.only(
|
||||||
|
top: $styles.insets.md + $styles.insets.xs,
|
||||||
|
bottom: $styles.insets.sm),
|
||||||
onPressed: () => tabController.index = index,
|
onPressed: () => tabController.index = index,
|
||||||
semanticLabel: label,
|
semanticLabel: label,
|
||||||
minimumSize: Size(btnWidth, 0),
|
minimumSize: Size(btnWidth, 0),
|
||||||
// Image icon
|
// Image icon
|
||||||
child: Image.asset(iconImgPath, height: 32, width: 32, color: selected ? null : color),
|
child: Image.asset(iconImgPath,
|
||||||
|
height: 32, width: 32, color: selected ? null : color),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -56,7 +56,6 @@ class _EventsListState extends State<_EventsList> {
|
|||||||
}
|
}
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
//TODO: Remove scrollbar on portrait
|
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
controller: _scroller,
|
controller: _scroller,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -101,10 +100,11 @@ class _EventsListState extends State<_EventsList> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps the list in a scroll listener
|
/// Wraps the list in a scroll listener that fades in an underlay as the content
|
||||||
|
/// is scrolled
|
||||||
Widget _buildScrollingListWithBlur() {
|
Widget _buildScrollingListWithBlur() {
|
||||||
return ListenableBuilder(
|
return AnimatedBuilder(
|
||||||
listenable: _scroller,
|
animation: _scroller,
|
||||||
child: _buildScrollingList(),
|
child: _buildScrollingList(),
|
||||||
builder: (_, child) {
|
builder: (_, child) {
|
||||||
bool showBackdrop = true;
|
bool showBackdrop = true;
|
||||||
|
@ -4,6 +4,7 @@ import 'package:wonders/logic/data/wonder_data.dart';
|
|||||||
import 'package:wonders/ui/common/app_backdrop.dart';
|
import 'package:wonders/ui/common/app_backdrop.dart';
|
||||||
import 'package:wonders/ui/common/app_icons.dart';
|
import 'package:wonders/ui/common/app_icons.dart';
|
||||||
import 'package:wonders/ui/common/centered_box.dart';
|
import 'package:wonders/ui/common/centered_box.dart';
|
||||||
|
import 'package:wonders/ui/common/controls/app_header.dart';
|
||||||
import 'package:wonders/ui/common/curved_clippers.dart';
|
import 'package:wonders/ui/common/curved_clippers.dart';
|
||||||
import 'package:wonders/ui/common/hidden_collectible.dart';
|
import 'package:wonders/ui/common/hidden_collectible.dart';
|
||||||
import 'package:wonders/ui/common/list_gradient.dart';
|
import 'package:wonders/ui/common/list_gradient.dart';
|
||||||
@ -35,17 +36,20 @@ class WonderEvents extends StatelessWidget {
|
|||||||
/// Main view switches between portrait and landscape views
|
/// Main view switches between portrait and landscape views
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
top: $styles.insets.lg,
|
top: $styles.insets.lg,
|
||||||
child: context.isLandscape ? _buildLandscape() : _buildPortrait(),
|
child: context.isLandscape ? _buildLandscape(context) : _buildPortrait(),
|
||||||
),
|
),
|
||||||
|
|
||||||
/// Floating TimelineBtn
|
/// Header w/ TimelineBtn
|
||||||
Positioned(
|
TopCenter(
|
||||||
right: $styles.insets.lg,
|
child: AppHeader(
|
||||||
top: $styles.insets.lg,
|
showBackBtn: false,
|
||||||
child: CircleIconBtn(
|
isTransparent: true,
|
||||||
|
trailing: (_) => CircleIconBtn(
|
||||||
icon: AppIcons.timeline,
|
icon: AppIcons.timeline,
|
||||||
onPressed: handleTimelineBtnPressed,
|
onPressed: handleTimelineBtnPressed,
|
||||||
semanticLabel: $strings.eventsListButtonOpenGlobal)),
|
semanticLabel: $strings.eventsListButtonOpenGlobal),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -54,7 +58,7 @@ class WonderEvents extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Landscape layout is a row, with the WonderImage on left and EventsList on the right
|
/// Landscape layout is a row, with the WonderImage on left and EventsList on the right
|
||||||
Widget _buildLandscape() {
|
Widget _buildLandscape(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
/// WonderImage w/ Timeline btn
|
/// WonderImage w/ Timeline btn
|
||||||
@ -66,10 +70,19 @@ class WonderEvents extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Gap($styles.insets.lg),
|
Gap($styles.insets.lg),
|
||||||
Expanded(child: Center(child: _WonderImageWithTimeline(data: _data, height: 500))),
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_WonderImageWithTimeline(data: _data, height: min(500, context.heightPx - 300)),
|
||||||
Gap($styles.insets.lg),
|
Gap($styles.insets.lg),
|
||||||
SizedBox(width: 300, child: _TimelineBtn(type: type)),
|
SizedBox(width: 300, child: _TimelineBtn(type: type)),
|
||||||
Gap($styles.insets.xl),
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Gap($styles.insets.lg),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -78,7 +91,7 @@ class WonderEvents extends StatelessWidget {
|
|||||||
/// EventsList
|
/// EventsList
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CenteredBox(
|
child: CenteredBox(
|
||||||
width: $styles.sizes.maxContentWidth1,
|
width: $styles.sizes.maxContentWidth2,
|
||||||
child: _EventsList(
|
child: _EventsList(
|
||||||
data: _data,
|
data: _data,
|
||||||
topHeight: 100,
|
topHeight: 100,
|
||||||
|
@ -3,16 +3,18 @@ import 'dart:async';
|
|||||||
import 'package:wonders/common_libs.dart';
|
import 'package:wonders/common_libs.dart';
|
||||||
import 'package:wonders/ui/common/utils/context_utils.dart';
|
import 'package:wonders/ui/common/utils/context_utils.dart';
|
||||||
|
|
||||||
//TODO: Make the clouds fade out and in
|
// TODO: Clouds should fade in and out
|
||||||
// Shows a set of clouds that animated onto stage.
|
// Shows a set of clouds that animated onto stage.
|
||||||
// When value-key is changed, a new set of clouds will animate into place and the old ones will animate out.
|
// When value-key is changed, a new set of clouds will animate into place and the old ones will animate out.
|
||||||
// Uses a random seed system, to make sure we get the same set of clouds for each wonder, without actually having to hand-position them.
|
// Uses a random seed system, to make sure we get the same set of clouds for each wonder, without actually having to hand-position them.
|
||||||
class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin {
|
class AnimatedClouds extends StatefulWidget with GetItStatefulWidgetMixin {
|
||||||
AnimatedClouds({Key? key, this.enableAnimations = true, required this.wonderType, required this.opacity})
|
AnimatedClouds(
|
||||||
|
{Key? key, this.enableAnimations = true, required this.wonderType, required this.opacity, this.cloudSize = 500})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
final WonderType wonderType;
|
final WonderType wonderType;
|
||||||
final bool enableAnimations;
|
final bool enableAnimations;
|
||||||
final double opacity;
|
final double opacity;
|
||||||
|
final double cloudSize;
|
||||||
@override
|
@override
|
||||||
State<AnimatedClouds> createState() => _AnimatedCloudsState();
|
State<AnimatedClouds> createState() => _AnimatedCloudsState();
|
||||||
}
|
}
|
||||||
@ -76,7 +78,7 @@ class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProvid
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Old clouds animate from 0 to startOffset, new clouds do the opposite.
|
// Old clouds animate from 0 to startOffset, new clouds do the opposite.
|
||||||
Widget buildCloud(c, {required bool isOld, required int startOffset}) {
|
Widget buildCloud(_Cloud c, {required bool isOld, required int startOffset}) {
|
||||||
// Use a positive, or negative start offset, based on index
|
// Use a positive, or negative start offset, based on index
|
||||||
final stOffset = _clouds.indexOf(c) % 2 == 0 ? -startOffset : startOffset;
|
final stOffset = _clouds.indexOf(c) % 2 == 0 ? -startOffset : startOffset;
|
||||||
// If old, we will end at the stOffset and start at 0, if new, start at stOffset, and end at 0
|
// If old, we will end at the stOffset and start at 0, if new, start at stOffset, and end at 0
|
||||||
@ -84,15 +86,15 @@ class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProvid
|
|||||||
return Positioned(
|
return Positioned(
|
||||||
top: c.pos.dy,
|
top: c.pos.dy,
|
||||||
left: isOld ? c.pos.dx - stOffset * curvedValue : c.pos.dx + stOffset * (1 - curvedValue),
|
left: isOld ? c.pos.dx - stOffset * curvedValue : c.pos.dx + stOffset * (1 - curvedValue),
|
||||||
child: c,
|
child: Opacity(opacity: isOld ? 1 - _anim.value : _anim.value, child: c),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: ClipRect(
|
child: ClipRect(
|
||||||
child: OverflowBox(
|
child: OverflowBox(
|
||||||
child: ListenableBuilder(
|
child: AnimatedBuilder(
|
||||||
listenable: _anim,
|
animation: _anim,
|
||||||
builder: (_, __) {
|
builder: (_, __) {
|
||||||
// A stack with 2 sets of clouds, one set is moving out of view while the other moves in.
|
// A stack with 2 sets of clouds, one set is moving out of view while the other moves in.
|
||||||
return Stack(
|
return Stack(
|
||||||
@ -123,19 +125,22 @@ class _AnimatedCloudsState extends State<AnimatedClouds> with SingleTickerProvid
|
|||||||
flipX: rnd.getBool(),
|
flipX: rnd.getBool(),
|
||||||
flipY: rnd.getBool(),
|
flipY: rnd.getBool(),
|
||||||
opacity: widget.opacity,
|
opacity: widget.opacity,
|
||||||
|
size: widget.cloudSize,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Cloud extends StatelessWidget {
|
class _Cloud extends StatelessWidget {
|
||||||
|
const _Cloud(this.pos,
|
||||||
|
{this.scale = 1, this.flipX = false, this.flipY = false, required this.opacity, required this.size});
|
||||||
|
|
||||||
final Offset pos;
|
final Offset pos;
|
||||||
final double scale;
|
final double scale;
|
||||||
final bool flipX;
|
final bool flipX;
|
||||||
final bool flipY;
|
final bool flipY;
|
||||||
final double opacity;
|
final double opacity;
|
||||||
|
final double size;
|
||||||
const _Cloud(this.pos, {this.scale = 1, this.flipX = false, this.flipY = false, required this.opacity});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Transform.scale(
|
Widget build(BuildContext context) => Transform.scale(
|
||||||
@ -144,7 +149,7 @@ class _Cloud extends StatelessWidget {
|
|||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
ImagePaths.cloud,
|
ImagePaths.cloud,
|
||||||
opacity: AlwaysStoppedAnimation(.4 * opacity),
|
opacity: AlwaysStoppedAnimation(.4 * opacity),
|
||||||
width: 400 * scale,
|
width: size * scale,
|
||||||
fit: BoxFit.fitWidth,
|
fit: BoxFit.fitWidth,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -69,6 +69,7 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
|
|||||||
final wonderBuilder = context.watch<WonderIllustrationBuilderState>();
|
final wonderBuilder = context.watch<WonderIllustrationBuilderState>();
|
||||||
final type = wonderBuilder.widget.wonderType;
|
final type = wonderBuilder.widget.wonderType;
|
||||||
final imgPath = '${type.assetPath}/${widget.fileName}';
|
final imgPath = '${type.assetPath}/${widget.fileName}';
|
||||||
|
// Dynamically determine the aspect ratio of the image, so we can more easily position it
|
||||||
if (aspectRatio == null) {
|
if (aspectRatio == null) {
|
||||||
aspectRatio == 0; // indicates load has started, so we don't run twice
|
aspectRatio == 0; // indicates load has started, so we don't run twice
|
||||||
rootBundle.load(imgPath).then((img) async {
|
rootBundle.load(imgPath).then((img) async {
|
||||||
@ -111,21 +112,26 @@ class _IllustrationPieceState extends State<IllustrationPiece> {
|
|||||||
height * widget.fractionalOffset!.dy,
|
height * widget.fractionalOffset!.dy,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Stack(
|
Widget? content;
|
||||||
children: [
|
if (uiImage != null) {
|
||||||
if (widget.bottom != null) Positioned.fill(child: widget.bottom!.call(context)),
|
content = Transform.translate(
|
||||||
if (uiImage != null) ...[
|
|
||||||
Transform.translate(
|
|
||||||
offset: finalTranslation,
|
offset: finalTranslation,
|
||||||
child: Transform.scale(
|
child: Transform.scale(
|
||||||
scale: 1 + (widget.zoomAmt * config.zoom) + introZoom,
|
scale: 1 + (widget.zoomAmt * config.zoom) + introZoom,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: height,
|
height: height,
|
||||||
width: height * aspectRatio!,
|
width: height * aspectRatio!,
|
||||||
child: !widget.enableHero ? img : Hero(tag: '$type-${widget.fileName}', child: img),
|
child: img,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
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!,
|
||||||
],
|
],
|
||||||
if (widget.top != null) Positioned.fill(child: widget.top!.call(context)),
|
if (widget.top != null) Positioned.fill(child: widget.top!.call(context)),
|
||||||
],
|
],
|
||||||
|
@ -12,13 +12,19 @@ class IllustrationTexture extends StatelessWidget {
|
|||||||
final Animation<double>? opacity;
|
final Animation<double>? opacity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ListenableBuilder(
|
Widget build(BuildContext context) => AnimatedBuilder(
|
||||||
listenable: opacity ?? AlwaysStoppedAnimation(1),
|
animation: opacity ?? AlwaysStoppedAnimation(1),
|
||||||
builder: (context, child) => ClipRect(
|
builder: (context, child) => ClipRect(
|
||||||
child: Transform.scale(
|
child: Transform.scale(
|
||||||
scaleX: scale * (flipX ? -1 : 1),
|
scaleX: scale * (flipX ? -1 : 1),
|
||||||
scaleY: scale * (flipY ? -1 : 1),
|
scaleY: scale * (flipY ? -1 : 1),
|
||||||
child: Image.asset(path, repeat: ImageRepeat.repeat, fit: BoxFit.contain, alignment: Alignment.topCenter, color: color, opacity: opacity, cacheWidth: 2048)),
|
child: Image.asset(path,
|
||||||
|
repeat: ImageRepeat.repeat,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
color: color,
|
||||||
|
opacity: opacity,
|
||||||
|
cacheWidth: 2048)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -571,10 +571,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_fade
|
name: image_fade
|
||||||
sha256: "6ec32003f451f3341f1b8ebd899b97e94f80ce0d66b13e59ade41ae03c34b464"
|
sha256: "7296c9c53cd5de98e675ef1e27bdaa4035d6c3a45cf5b86094b2e545689b4ea6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.6.2"
|
||||||
image_gallery_saver:
|
image_gallery_saver:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -34,7 +34,7 @@ dependencies:
|
|||||||
google_maps_flutter_web: ^0.4.0+1
|
google_maps_flutter_web: ^0.4.0+1
|
||||||
go_router: ^4.2.8
|
go_router: ^4.2.8
|
||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
image_fade: ^0.6.1
|
image_fade: ^0.6.2
|
||||||
image_gallery_saver: ^1.7.1
|
image_gallery_saver: ^1.7.1
|
||||||
internet_connection_checker: ^0.0.1+3
|
internet_connection_checker: ^0.0.1+3
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user