feat(app): responsive asset detail layout, full width chart, reactive hero header, shimmer loaders and enriched fundamentals
This commit is contained in:
@@ -168,10 +168,28 @@ class _CandlestickChartState extends State<CandlestickChart> {
|
||||
return Listener(
|
||||
onPointerSignal: (pointerSignal) {
|
||||
if (pointerSignal is PointerScrollEvent) {
|
||||
setState(() {
|
||||
final double zoomFactor = pointerSignal.scrollDelta.dy > 0 ? 0.9 : 1.1;
|
||||
_scale = (_scale * zoomFactor).clamp(0.2, 5.0);
|
||||
});
|
||||
GestureBinding.instance.pointerSignalResolver.register(
|
||||
pointerSignal,
|
||||
(event) {
|
||||
if (event is PointerScrollEvent) {
|
||||
setState(() {
|
||||
final double localX = event.localPosition.dx;
|
||||
final double zoomFactor = event.scrollDelta.dy > 0 ? 0.9 : 1.1;
|
||||
final double newScale = (_scale * zoomFactor).clamp(0.2, 5.0);
|
||||
final double scaleRatio = newScale / _scale;
|
||||
|
||||
// Zoom centered on cursor
|
||||
_panOffset = localX - (localX - _panOffset) * scaleRatio;
|
||||
_scale = newScale;
|
||||
|
||||
final double updatedCandleSpace = (baseWidth + spacing) * _scale;
|
||||
final double updatedContentWidth = (widget.candles.length + 15) * updatedCandleSpace;
|
||||
final double newMinOffset = constraints.maxWidth - updatedContentWidth - 60.0;
|
||||
_panOffset = _panOffset.clamp(newMinOffset < maxOffset ? newMinOffset : maxOffset, maxOffset);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: GestureDetector(
|
||||
|
||||
@@ -5,6 +5,8 @@ import '../../../../core/widgets/asset_logo_widget.dart';
|
||||
import '../../../../shared/widgets/favorite_star_button.dart';
|
||||
import '../../bloc/header/asset_header_bloc.dart';
|
||||
import '../../bloc/header/asset_header_state.dart';
|
||||
import '../../bloc/technical/asset_technical_bloc.dart';
|
||||
import '../../bloc/technical/asset_technical_state.dart';
|
||||
import '../../models/asset_model.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@@ -146,45 +148,66 @@ class AssetHeroHeader extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'AKTUELLER PREIS',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.primaryColor,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
BlocBuilder<AssetTechnicalBloc, AssetTechnicalState>(
|
||||
builder: (context, taState) {
|
||||
double? livePrice = price;
|
||||
String liveCurrency = selectedOption.tradingCurrency.isNotEmpty
|
||||
? selectedOption.tradingCurrency
|
||||
: currency;
|
||||
|
||||
if (taState is AssetTechnicalLoaded && taState.data != null) {
|
||||
if (taState.data!.candles.isNotEmpty) {
|
||||
final lastClose = taState.data!.candles.last.close;
|
||||
if (lastClose > 0) {
|
||||
livePrice = lastClose;
|
||||
}
|
||||
}
|
||||
if (taState.data!.currency.isNotEmpty) {
|
||||
liveCurrency = taState.data!.currency;
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText(
|
||||
price != null && price > 0 ? price.toStringAsFixed(2) : '---',
|
||||
Text(
|
||||
'AKTUELLER PREIS',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textPrimary,
|
||||
color: theme.primaryColor,
|
||||
letterSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
selectedOption.tradingCurrency.isNotEmpty ? selectedOption.tradingCurrency : currency,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.primaryColor,
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
SelectableText(
|
||||
livePrice != null && livePrice > 0 ? livePrice.toStringAsFixed(2) : '---',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.textPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
child: Text(
|
||||
liveCurrency,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: theme.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
// Interactive Ticker & Exchange Selector Dropdown
|
||||
PopupMenuButton<String>(
|
||||
|
||||
Reference in New Issue
Block a user