mirror of
https://github.com/gskinnerTeam/flutter-wonderous-app.git
synced 2025-05-17 21:05:59 +08:00
163 lines
5.1 KiB
Dart
163 lines
5.1 KiB
Dart
import 'dart:math' as math;
|
|
import 'dart:ui';
|
|
|
|
import 'package:wonders/common_libs.dart';
|
|
import 'package:wonders/logic/data/highlight_data.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/simple_header.dart';
|
|
import 'package:wonders/ui/common/static_text_scale.dart';
|
|
|
|
part 'widgets/_blurred_image_bg.dart';
|
|
part 'widgets/_collapsing_carousel_item.dart';
|
|
part 'widgets/_bottom_text_content.dart';
|
|
|
|
class ArtifactCarouselScreen extends StatefulWidget {
|
|
final WonderType type;
|
|
const ArtifactCarouselScreen({Key? key, required this.type}) : super(key: key);
|
|
|
|
@override
|
|
State<ArtifactCarouselScreen> createState() => _ArtifactScreenState();
|
|
}
|
|
|
|
class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
|
|
PageController? _pageController;
|
|
final _currentPage = ValueNotifier<double>(9999);
|
|
|
|
late final List<HighlightData> _artifacts = HighlightData.forWonder(widget.type);
|
|
late final _currentArtifactIndex = ValueNotifier<int>(_wrappedPageIndex);
|
|
|
|
int get _wrappedPageIndex => _currentPage.value.round() % _artifacts.length;
|
|
|
|
void _handlePageChanged() {
|
|
_currentPage.value = _pageController?.page ?? 0;
|
|
_currentArtifactIndex.value = _wrappedPageIndex;
|
|
}
|
|
|
|
void _handleSearchTap() => context.push(ScreenPaths.search(widget.type));
|
|
|
|
void _handleArtifactTap(int index) {
|
|
int delta = index - _currentPage.value.round();
|
|
if (delta == 0) {
|
|
HighlightData data = _artifacts[index % _artifacts.length];
|
|
context.push(ScreenPaths.artifact(data.artifactId));
|
|
} else {
|
|
_pageController?.animateToPage(
|
|
_currentPage.value.round() + delta,
|
|
duration: $styles.times.fast,
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
bool shortMode = context.heightPx < 800;
|
|
final double bottomHeight = shortMode ? 240 : 340;
|
|
// Allow objects to become wider as the screen becomes tall, this allows
|
|
// them to grow taller as well, filling the available space better.
|
|
double itemHeight = (context.heightPx - 200 - bottomHeight).clamp(250, 400);
|
|
double itemWidth = itemHeight * .666;
|
|
// TODO: This could be optimized to only run if the size has changed...is it worth it?
|
|
_pageController?.dispose();
|
|
_pageController = PageController(
|
|
viewportFraction: itemWidth / context.widthPx,
|
|
initialPage: _currentPage.value.round(),
|
|
);
|
|
_pageController?.addListener(_handlePageChanged);
|
|
final pages = _artifacts.map((e) {
|
|
return Padding(
|
|
padding: EdgeInsets.all(10),
|
|
child: _DoubleBorderImage(e),
|
|
);
|
|
}).toList();
|
|
|
|
return Stack(
|
|
children: [
|
|
/// Blurred Bg
|
|
Positioned.fill(
|
|
child: ValueListenableBuilder<int>(
|
|
valueListenable: _currentArtifactIndex,
|
|
builder: (_, value, __) {
|
|
return _BlurredImageBg(url: _artifacts[value].imageUrl);
|
|
}),
|
|
),
|
|
|
|
/// BgCircle
|
|
_buildBgCircle(bottomHeight),
|
|
|
|
/// Carousel Items
|
|
PageView.builder(
|
|
controller: _pageController,
|
|
itemBuilder: (_, index) {
|
|
final wrappedIndex = index % pages.length;
|
|
final child = pages[wrappedIndex];
|
|
return ValueListenableBuilder<double>(
|
|
valueListenable: _currentPage,
|
|
builder: (_, value, __) {
|
|
final int offset = (value.round() - index).abs();
|
|
return _CollapsingCarouselItem(
|
|
width: itemWidth,
|
|
bottom: bottomHeight - 20,
|
|
indexOffset: min(3, offset),
|
|
onPressed: () => _handleArtifactTap(index),
|
|
title: _artifacts[wrappedIndex].title,
|
|
child: child,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
|
|
/// Bottom Text
|
|
BottomCenter(
|
|
child: ValueListenableBuilder<int>(
|
|
valueListenable: _currentArtifactIndex,
|
|
builder: (_, value, __) => _BottomTextContent(
|
|
artifact: _artifacts[value],
|
|
height: bottomHeight,
|
|
shortMode: shortMode,
|
|
state: this,
|
|
),
|
|
),
|
|
),
|
|
|
|
/// Header
|
|
SimpleHeader(
|
|
$strings.artifactsTitleArtifacts,
|
|
showBackBtn: false,
|
|
isTransparent: true,
|
|
trailing: (context) => CircleBtn(
|
|
semanticLabel: $strings.artifactsButtonBrowse,
|
|
onPressed: _handleSearchTap,
|
|
child: AppIcon(AppIcons.search),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
OverflowBox _buildBgCircle(double height) {
|
|
const double size = 1500;
|
|
return OverflowBox(
|
|
maxWidth: size,
|
|
maxHeight: size,
|
|
child: Transform.translate(
|
|
offset: Offset(0, size / 2),
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: $styles.colors.offWhite.withOpacity(0.8),
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(size * .45)),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_pageController?.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|