mirror of
https://github.com/timokz/flutter-vienna-hackathon-25.git
synced 2025-11-08 19:04:20 +01:00
Compare commits
2 commits
2b84f749ae
...
1d066e1e11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d066e1e11 | ||
|
|
da5fe81b1b |
9 changed files with 383 additions and 135 deletions
|
|
@ -26,8 +26,9 @@ class FunmapMgr {
|
|||
// E.g. `flutter run --dart-define=SERVER_URL=https://api.example.com/`
|
||||
|
||||
const serverUrlFromEnv = String.fromEnvironment('SERVER_URL');
|
||||
serverUrl =
|
||||
serverUrlFromEnv.isEmpty ? 'http://$localhost:8080/' : serverUrlFromEnv;
|
||||
serverUrl = serverUrlFromEnv.isEmpty
|
||||
? 'https://wien-talks-api.aequito.sh/'
|
||||
: serverUrlFromEnv;
|
||||
|
||||
client = Client(serverUrl, connectionTimeout: const Duration(seconds: 2))
|
||||
..connectivityMonitor = FlutterConnectivityMonitor();
|
||||
|
|
|
|||
|
|
@ -1,15 +1,51 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:wien_talks_client/wien_talks_client.dart';
|
||||
import 'package:wien_talks_flutter/screens/create_event_screen.dart';
|
||||
import 'package:wien_talks_flutter/screens/login_page.dart';
|
||||
import 'package:wien_talks_flutter/screens/news_screen.dart';
|
||||
|
||||
double? _qpDouble(GoRouterState s, String key) {
|
||||
final v = s.uri.queryParameters[key];
|
||||
return v == null || v.isEmpty ? null : double.tryParse(v);
|
||||
}
|
||||
|
||||
final router = GoRouter(
|
||||
initialLocation: '/',
|
||||
routes: [
|
||||
GoRoute(path: '/login', builder: (c, s) => const LoginScreen()),
|
||||
GoRoute(path: '/', builder: (c, s) => NewsScreen()),
|
||||
GoRoute(
|
||||
path: '/create_event',
|
||||
name: 'create_event',
|
||||
builder: (c, s) => CreateEventScreen()),
|
||||
path: '/',
|
||||
name: 'news',
|
||||
builder: (c, s) => NewsScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/create_event',
|
||||
name: 'create_event',
|
||||
pageBuilder: (c, s) {
|
||||
final quote = s.extra as Quote?;
|
||||
final lat = _qpDouble(s, 'lat');
|
||||
final lon = _qpDouble(s, 'lon');
|
||||
|
||||
return CustomTransitionPage(
|
||||
key: s.pageKey,
|
||||
child: CreateEventScreen(
|
||||
contextQuote: quote,
|
||||
initialLat: lat,
|
||||
initialLon: lon,
|
||||
),
|
||||
transitionsBuilder: (context, anim, secAnim, child) {
|
||||
final curve =
|
||||
CurvedAnimation(parent: anim, curve: Curves.easeOutCubic);
|
||||
return FadeTransition(
|
||||
opacity: curve,
|
||||
child: SlideTransition(
|
||||
position: Tween(begin: const Offset(0, 0.08), end: Offset.zero)
|
||||
.animate(curve),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,40 +1,195 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:location/location.dart';
|
||||
import 'package:wien_talks_client/wien_talks_client.dart';
|
||||
import 'package:wien_talks_flutter/widgets/get_location_widget.dart';
|
||||
import 'package:wien_talks_flutter/helper/funmap_mgr.dart';
|
||||
import 'package:wien_talks_flutter/widgets/carousel_widget.dart';
|
||||
import 'package:wien_talks_flutter/widgets/get_location_widget.dart';
|
||||
import 'package:wien_talks_flutter/widgets/mapfile_widget.dart';
|
||||
import 'package:wien_talks_flutter/widgets/news_input_form.dart';
|
||||
import 'package:wien_talks_flutter/widgets/screen_widget.dart';
|
||||
|
||||
import '../helper/location_mgr.dart';
|
||||
class CreateEventScreen extends StatefulWidget {
|
||||
const CreateEventScreen({
|
||||
super.key,
|
||||
this.contextQuote,
|
||||
this.initialLat,
|
||||
this.initialLon,
|
||||
});
|
||||
|
||||
class CreateEventScreen extends StatelessWidget {
|
||||
const CreateEventScreen({super.key});
|
||||
final Quote? contextQuote;
|
||||
final double? initialLat;
|
||||
final double? initialLon;
|
||||
|
||||
@override
|
||||
State<CreateEventScreen> createState() => _CreateEventScreenState();
|
||||
}
|
||||
|
||||
class _CreateEventScreenState extends State<CreateEventScreen> {
|
||||
double? _lat;
|
||||
double? _lon;
|
||||
bool _submitting = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_lat = widget.contextQuote?.lat ?? widget.initialLat;
|
||||
_lon = widget.contextQuote?.long ?? widget.initialLon;
|
||||
}
|
||||
|
||||
Future<void> _useMyLocation() async {
|
||||
try {
|
||||
final loc = await Location().getLocation();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_lat = loc.latitude;
|
||||
_lon = loc.longitude;
|
||||
});
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Couldn`’t get your location.')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _submit(CreateQuoteRequest req) async {
|
||||
final lat = _lat ?? widget.contextQuote?.lat;
|
||||
final lon = _lon ?? widget.contextQuote?.long;
|
||||
|
||||
if (lat == null || lon == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text(' location first.')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final toSend = CreateQuoteRequest(
|
||||
text: req.text,
|
||||
authorName: req.authorName,
|
||||
lat: lat,
|
||||
lng: lon,
|
||||
);
|
||||
|
||||
setState(() => _submitting = true);
|
||||
try {
|
||||
await FunmapMgr().client.quote.createQuote(toSend);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Posting failed: $e')),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) setState(() => _submitting = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasContextQuote = widget.contextQuote != null;
|
||||
final quote = widget.contextQuote;
|
||||
|
||||
return ScreenWidget(
|
||||
child: Column(
|
||||
children: [
|
||||
NewsInputForm(
|
||||
onSubmit: (CreateQuoteRequest request) async {
|
||||
await FunmapMgr().client.quote.createQuote(request);
|
||||
},
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: LocationMgr().stream,
|
||||
builder:
|
||||
(BuildContext context, AsyncSnapshot<LocationData> snapshot) =>
|
||||
snapshot.data != null
|
||||
? Text(snapshot.data.toString())
|
||||
: SizedBox()),
|
||||
Expanded(
|
||||
child: GetLocationWidget(
|
||||
child: MapfileWidget(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (hasContextQuote) ...[
|
||||
Card(
|
||||
margin: const EdgeInsets.fromLTRB(12, 12, 12, 6),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(quote!.text,
|
||||
style: Theme.of(context).textTheme.bodyLarge),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
[
|
||||
if ((quote.authorName ?? '').trim().isNotEmpty)
|
||||
quote.authorName!.trim()
|
||||
].join(' · '),
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_lat != null && _lon != null
|
||||
? 'Location: ${_lat!.toStringAsFixed(5)}, ${_lon!.toStringAsFixed(5)}'
|
||||
: 'Pick an alt location',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton.icon(
|
||||
onPressed: _useMyLocation,
|
||||
icon: const Icon(Icons.my_location, size: 18),
|
||||
label: const Text('My location'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 4, 12, 8),
|
||||
child: NewsInputForm(
|
||||
// enabled: !_submitting,
|
||||
onSubmit: _submit,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
GetLocationWidget(
|
||||
child: MapfileWidget(),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: LocationCarousel(
|
||||
suggestions: [
|
||||
if (widget.contextQuote?.lat != null &&
|
||||
widget.contextQuote?.long != null)
|
||||
LocationSuggestion(
|
||||
label: 'Quote location',
|
||||
lat: widget.contextQuote!.lat,
|
||||
lon: widget.contextQuote!.long,
|
||||
assetImage: 'assets/funny_images/tram.jpg',
|
||||
),
|
||||
const LocationSuggestion(
|
||||
label: 'Stephansplatz',
|
||||
lat: 48.2085,
|
||||
lon: 16.3731,
|
||||
assetImage: 'assets/funny_images/sightseeing.jpg'),
|
||||
const LocationSuggestion(
|
||||
label: 'Naschmarkt',
|
||||
lat: 48.1970,
|
||||
lon: 16.3615,
|
||||
assetImage: 'assets/funny_images/houses.jpg'),
|
||||
],
|
||||
onPick: (lat, lon) => setState(() {
|
||||
_lat = lat;
|
||||
_lon = lon;
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import 'package:wien_talks_flutter/screens/latest_quotes_screen.dart';
|
|||
import 'package:wien_talks_flutter/widgets/intro_text_widget.dart';
|
||||
import 'package:wien_talks_flutter/widgets/screen_widget.dart';
|
||||
|
||||
import '../widgets/carousel_widget.dart';
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({
|
||||
super.key,
|
||||
|
|
@ -43,7 +41,6 @@ class HomeScreen extends StatelessWidget {
|
|||
SizedBox(
|
||||
height: 30,
|
||||
),
|
||||
CarouselWidget(),
|
||||
Row(
|
||||
children: [
|
||||
Spacer(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:wien_talks_client/wien_talks_client.dart';
|
||||
import 'package:wien_talks_flutter/helper/funmap_mgr.dart';
|
||||
import 'package:wien_talks_flutter/helper/location_filter.dart';
|
||||
|
|
@ -212,6 +213,14 @@ class _LatestQuotesScreenState extends State<LatestQuotesScreen> {
|
|||
_applyFilters();
|
||||
},
|
||||
staticMapUrlBuilder: gStaticMap,
|
||||
onTap: () => context.pushNamed(
|
||||
'create_event',
|
||||
extra: q,
|
||||
queryParameters: {
|
||||
'lat': q.lat.toString(),
|
||||
'lon': q.long.toString(),
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:wien_talks_flutter/helper/auth_service.dart';
|
||||
|
||||
class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xff2193b0), Color(0xff6dd5ed)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Wien Talks',
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 42,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white)),
|
||||
const SizedBox(height: 60),
|
||||
FilledButton.icon(
|
||||
onPressed: () async => await AuthService.signIn(),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black87,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30)),
|
||||
elevation: 6,
|
||||
),
|
||||
icon: Icon(
|
||||
Icons.lock,
|
||||
),
|
||||
label: const Text('Sign in with Google'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,110 @@
|
|||
import 'package:carousel_slider/carousel_slider.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CarouselWidget extends StatelessWidget {
|
||||
const CarouselWidget({super.key});
|
||||
class LocationSuggestion {
|
||||
final String label;
|
||||
final double lat;
|
||||
final double lon;
|
||||
final String? subtitle;
|
||||
final String? assetImage;
|
||||
const LocationSuggestion({
|
||||
required this.label,
|
||||
required this.lat,
|
||||
required this.lon,
|
||||
this.subtitle,
|
||||
this.assetImage,
|
||||
});
|
||||
}
|
||||
|
||||
class LocationCarousel extends StatelessWidget {
|
||||
const LocationCarousel({
|
||||
super.key,
|
||||
required this.suggestions,
|
||||
required this.onPick,
|
||||
this.height = 96,
|
||||
});
|
||||
|
||||
final List<LocationSuggestion> suggestions;
|
||||
final void Function(double lat, double lon) onPick;
|
||||
final double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: CarouselSlider(
|
||||
options: CarouselOptions(height: 300.0, autoPlay: true),
|
||||
items: ["houses.jpg", "kangaroos.jpg", "sightseeing.jpg", "tram.jpg", "fiaker.jpg", "falco.jpg", "wastebin.jpg"].map((i) {
|
||||
return Builder(
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
//decoration: BoxDecoration(color: Colors.amber),
|
||||
child: Image(image: AssetImage("assets/funny_images/$i")));
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
if (suggestions.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Card(
|
||||
margin: const EdgeInsets.fromLTRB(12, 0, 12, 12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: CarouselSlider(
|
||||
options: CarouselOptions(
|
||||
height: height,
|
||||
viewportFraction: 0.72,
|
||||
enlargeCenterPage: true,
|
||||
enableInfiniteScroll: suggestions.length > 1,
|
||||
),
|
||||
items: suggestions.map((s) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: () => onPick(s.lat, s.lon),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (s.assetImage != null)
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.horizontal(
|
||||
left: Radius.circular(12)),
|
||||
child: Image.asset(
|
||||
s.assetImage!,
|
||||
width: 90,
|
||||
height: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
else
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.label,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
if (s.subtitle != null)
|
||||
Text(s.subtitle!,
|
||||
style:
|
||||
Theme.of(context).textTheme.labelSmall,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
Text(
|
||||
'${s.lat.toStringAsFixed(4)}, ${s.lon.toStringAsFixed(4)}',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class FilterChipsBar extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(width: 8),
|
||||
ChoiceChip(
|
||||
label: const Text('Loveed'),
|
||||
label: const Text('Loved'),
|
||||
selected: sort == 'top',
|
||||
onSelected: (_) => onSortChanged('top'),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class FlamboyantQuoteCard extends StatelessWidget {
|
|||
required this.onVoteUp,
|
||||
required this.onVoteDown,
|
||||
this.staticMapUrlBuilder,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final Quote quote;
|
||||
|
|
@ -28,21 +29,20 @@ class FlamboyantQuoteCard extends StatelessWidget {
|
|||
final VoidCallback onVoteUp;
|
||||
final VoidCallback onVoteDown;
|
||||
final StaticMapUrlBuilder? staticMapUrlBuilder;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final seed = (quote.id ?? quote.text.hashCode) & 0x7fffffff;
|
||||
final rng = math.Random(seed);
|
||||
|
||||
final variant = (rng.nextInt(3));
|
||||
|
||||
// Subtle tilt and accent
|
||||
final variant = rng.nextInt(3);
|
||||
final tiltDeg = [-2.2, -1.4, -0.6, 0, 0.6, 1.2, 2.0][rng.nextInt(7)];
|
||||
final tiltRad = tiltDeg * math.pi / 180.0;
|
||||
final accents = [
|
||||
const Color(0xFFE53935), // red
|
||||
const Color(0xFF3949AB), // indigo
|
||||
const Color(0xFF00897B), // teal
|
||||
const Color(0xFFE53935),
|
||||
const Color(0xFF3949AB),
|
||||
const Color(0xFF00897B),
|
||||
];
|
||||
final accent = accents[seed % accents.length];
|
||||
|
||||
|
|
@ -52,29 +52,41 @@ class FlamboyantQuoteCard extends StatelessWidget {
|
|||
.withValues(alpha: 0.70),
|
||||
);
|
||||
|
||||
final card = Container(
|
||||
decoration: BoxDecoration(
|
||||
color: t.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x14000000),
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 6),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: accent.withValues(alpha: 0.25), width: 1),
|
||||
final borderRadius = BorderRadius.circular(14);
|
||||
|
||||
final cardContent = CardContenty(
|
||||
quote: quote,
|
||||
staticMapUrlBuilder: staticMapUrlBuilder,
|
||||
meta: meta,
|
||||
onVoteUp: onVoteUp,
|
||||
onVoteDown: onVoteDown,
|
||||
context: context,
|
||||
variant: variant,
|
||||
accent: accent,
|
||||
metaStyle: metaStyle,
|
||||
);
|
||||
|
||||
final tappableCard = Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Ink(
|
||||
decoration: BoxDecoration(
|
||||
color: t.colorScheme.surface,
|
||||
borderRadius: borderRadius,
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x14000000),
|
||||
blurRadius: 12,
|
||||
offset: Offset(0, 6),
|
||||
),
|
||||
],
|
||||
border: Border.all(color: accent.withValues(alpha: 0.25), width: 1),
|
||||
),
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius,
|
||||
onTap: onTap,
|
||||
child: cardContent,
|
||||
),
|
||||
),
|
||||
child: CardContenty(
|
||||
quote: quote,
|
||||
staticMapUrlBuilder: staticMapUrlBuilder,
|
||||
meta: meta,
|
||||
onVoteUp: onVoteUp,
|
||||
onVoteDown: onVoteDown,
|
||||
context: context,
|
||||
variant: variant,
|
||||
accent: accent,
|
||||
metaStyle: metaStyle),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
|
|
@ -84,14 +96,17 @@ class FlamboyantQuoteCard extends StatelessWidget {
|
|||
children: [
|
||||
Transform.rotate(
|
||||
angle: tiltRad,
|
||||
child: card,
|
||||
transformHitTests: false,
|
||||
child: tappableCard,
|
||||
),
|
||||
Positioned(
|
||||
top: -8,
|
||||
right: 16,
|
||||
child: UbahnTape(
|
||||
lat: quote.lat,
|
||||
lon: quote.long,
|
||||
child: IgnorePointer(
|
||||
child: UbahnTape(
|
||||
lat: quote.lat,
|
||||
lon: quote.long,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue