Pennyfoxcode
Pennyfoxcode
dart';
// Entry point
void main() {
runApp(const PennyFoxApp());
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PennyFox',
theme: ThemeData(
primarySwatch: [Link],
fontFamily: 'Sans',
),
initialRoute: [Link],
routes: {
[Link]: (context) => const SplashScreen(),
[Link]: (context) => const OnboardingScreen(),
[Link]: (context) => const HomeScreen(),
[Link]: (context) => const ProductDetailsScreen(),
[Link]: (context) => const CategoryScreen(),
[Link]: (context) => const RemindersScreen(),
[Link]: (context) => const ExecuteTripScreen(),
[Link]: (context) => const TripCompleteScreen(),
[Link]: (context) => const TripReportScreen(),
[Link]: (context) => const
ShoppingHistoryScreen(),
},
);
}
}
// -----------------------------------------------------------------------------
// SPLASH SCREEN
// -----------------------------------------------------------------------------
class SplashScreen extends StatelessWidget {
static const routeName = '/splash';
const SplashScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Simulate a short delay before navigating to Onboarding
[Link](const Duration(seconds: 2), () {
[Link](context, [Link]);
});
return Scaffold(
backgroundColor: [Link],
body: Center(
child: Column(
mainAxisAlignment: [Link],
children: [
// Logo
[Link](
'assets/images/pennyfox_logo.png', // Replace with your actual logo
asset
height: 100,
),
const SizedBox(height: 16),
const Text(
'PENNYFOX',
style: TextStyle(
fontSize: 24,
fontWeight: [Link],
),
),
const Text(
'Save money and feel stunning',
style: TextStyle(fontSize: 16),
),
],
),
),
);
}
}
// -----------------------------------------------------------------------------
// ONBOARDING SCREEN
// -----------------------------------------------------------------------------
class OnboardingScreen extends StatefulWidget {
static const routeName = '/onboarding';
const OnboardingScreen({Key? key}) : super(key: key);
@override
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
void _onNext() {
if (_currentPage < [Link] - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: [Link],
);
} else {
[Link](context, [Link]);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: [Link](
controller: _pageController,
itemCount: [Link],
onPageChanged: (index) {
setState(() {
_currentPage = index;
});
},
itemBuilder: (context, index) {
return OnboardingPage(
content: onboardingPages[index],
currentPage: index,
totalPages: [Link],
onNext: _onNext,
);
},
),
);
}
}
const OnboardingPage({
Key? key,
required [Link],
required [Link],
required [Link],
required [Link],
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const [Link](24),
child: Column(
mainAxisAlignment: [Link],
children: [
[Link](
[Link],
height: 300,
),
const SizedBox(height: 32),
Text(
[Link],
style: const TextStyle(
fontSize: 24,
fontWeight: [Link],
),
textAlign: [Link],
),
const SizedBox(height: 16),
Text(
[Link],
style: const TextStyle(fontSize: 16),
textAlign: [Link],
),
const SizedBox(height: 32),
ElevatedButton(
onPressed: onNext,
child: Text(currentPage == totalPages - 1 ? 'Create Shopping List' :
'Next'),
),
],
),
);
}
}
class OnboardingContent {
final String imagePath;
final String title;
final String description;
OnboardingContent({
required [Link],
required [Link],
required [Link],
});
}
// -----------------------------------------------------------------------------
// HOME SCREEN (Browse, etc.)
// -----------------------------------------------------------------------------
class HomeScreen extends StatefulWidget {
static const routeName = '/home';
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
selectedItemColor: [Link],
onTap: _onItemTapped,
items: const [
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'Browse',
),
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'Reminders',
),
BottomNavigationBarItem(
icon: Icon([Link]),
label: 'History',
),
],
),
);
}
}
@override
Widget build(BuildContext context) {
// Mimic the UI: "Hi, Katerina", "Search Catalogues", "Categories", etc.
return SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const [Link](horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: [Link],
children: [
const Text(
'Hi, Kateřina',
style: TextStyle(fontSize: 20, fontWeight: [Link]),
),
const SizedBox(height: 4),
Text(
'Trip starts from: <saved address truncated...>',
style: TextStyle(color: [Link][600]),
),
const SizedBox(height: 16),
TextField(
decoration: InputDecoration(
hintText: 'Search Catalogues',
prefixIcon: const Icon([Link]),
border: OutlineInputBorder(
borderRadius: [Link](12),
),
),
),
const SizedBox(height: 16),
// Example of a promotional banner
Container(
width: [Link],
padding: const [Link](16),
decoration: BoxDecoration(
color: [Link].shade100,
borderRadius: [Link](12),
),
child: const Text(
'New Offer: Veggie Mania Sale',
style: TextStyle(fontWeight: [Link]),
),
),
const SizedBox(height: 16),
// Categories
const Text(
'Categories',
style: TextStyle(fontSize: 18, fontWeight: [Link]),
),
const SizedBox(height: 8),
SizedBox(
height: 100,
child: ListView(
scrollDirection: [Link],
children: [
_buildCategoryIcon('Seafood', Icons.set_meal),
_buildCategoryIcon('Vegetables', [Link]),
_buildCategoryIcon('Fruits', [Link]),
_buildCategoryIcon('Dessert', [Link]),
// Add more categories as needed
],
),
),
const SizedBox(height: 16),
const Text(
'In your previous list',
style: TextStyle(fontSize: 18, fontWeight: [Link]),
),
const SizedBox(height: 8),
// Sample horizontal product list
SizedBox(
height: 200,
child: ListView(
scrollDirection: [Link],
children: [
_buildProductCard(
context,
image: 'assets/images/[Link]',
title: 'Skyr by Danone',
price: 480,
discount: 630,
),
_buildProductCard(
context,
image: 'assets/images/[Link]',
title: 'Bjorg Wheat Flakes',
price: 315,
discount: 390,
),
],
),
),
],
),
),
),
);
}
// -----------------------------------------------------------------------------
// PRODUCT DETAILS SCREEN
// -----------------------------------------------------------------------------
class ProductDetailsScreen extends StatelessWidget {
static const routeName = '/productDetails';
const ProductDetailsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Sample UI for Nutella
return Scaffold(
appBar: AppBar(
title: const Text('Product Details'),
),
body: Padding(
padding: const [Link](16.0),
child: Column(
children: [
// Image carousel (placeholder)
Container(
height: 200,
color: [Link][200],
child: PageView(
children: [
[Link]('assets/images/[Link]'),
// Add more images if you like
],
),
),
const SizedBox(height: 16),
// Product Title and Price
Row(
mainAxisAlignment: [Link],
children: const [
Text(
'Nutella Hazelnut Spread 180g',
style: TextStyle(fontSize: 18, fontWeight: [Link]),
),
Text(
'480Kč',
style: TextStyle(fontSize: 18, color: [Link]),
),
],
),
const SizedBox(height: 4),
Row(
children: const [
Text(
'140Kč',
style: TextStyle(
decoration: [Link],
color: [Link],
),
),
SizedBox(width: 8),
Text(
'52% OFF',
style: TextStyle(color: [Link]),
),
],
),
const SizedBox(height: 8),
// Category
const Align(
alignment: [Link],
child: Text(
'Category: Spreads and Sauces',
style: TextStyle(color: [Link]),
),
),
const SizedBox(height: 16),
// Add to List
Row(
mainAxisAlignment: [Link],
children: [
ElevatedButton(
onPressed: () {
// Add item to list logic
},
child: const Text('Add to List'),
),
Row(
children: [
IconButton(
onPressed: () {},
icon: const Icon([Link]),
),
const Text('1'),
IconButton(
onPressed: () {},
icon: const Icon([Link]),
),
],
),
],
),
const SizedBox(height: 16),
// List Total
const Align(
alignment: [Link],
child: Text(
'List Total 800Kč',
style: TextStyle(fontSize: 16, fontWeight: [Link]),
),
),
],
),
),
);
}
}
// -----------------------------------------------------------------------------
// CATEGORY SCREEN (e.g. Snacks listing)
// -----------------------------------------------------------------------------
class CategoryScreen extends StatelessWidget {
static const routeName = '/category';
const CategoryScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Example for "Snacks" category
return Scaffold(
appBar: AppBar(
title: const Text('Snacks'),
),
body: Padding(
padding: const [Link](16.0),
child: [Link](
itemCount: 6,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.65,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
[Link](context, [Link]);
},
child: Container(
decoration: BoxDecoration(
border: [Link](color: [Link].shade200),
borderRadius: [Link](12),
),
child: Column(
children: [
Expanded(
child: [Link](
'assets/images/[Link]',
fit: [Link],
),
),
const SizedBox(height: 8),
const Text('Nutella 480Kč'),
const SizedBox(height: 4),
const Text(
'630Kč',
style: TextStyle(
decoration: [Link],
color: [Link],
fontSize: 12,
),
),
const SizedBox(height: 4),
],
),
),
);
},
),
),
);
}
}
// -----------------------------------------------------------------------------
// REMINDERS SCREEN
// -----------------------------------------------------------------------------
class RemindersScreen extends StatelessWidget {
static const routeName = '/reminders';
const RemindersScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Example layout from your mockup
return Scaffold(
appBar: AppBar(
title: const Text('Reminders'),
),
body: Padding(
padding: const [Link](16.0),
child: Column(
children: [
// Days row
SingleChildScrollView(
scrollDirection: [Link],
child: Row(
children: const [
_DayChip(label: 'Mon'),
_DayChip(label: 'Tue'),
_DayChip(label: 'Wed'),
_DayChip(label: 'Thu'),
_DayChip(label: 'Fri'),
_DayChip(label: 'Sat'),
_DayChip(label: 'Sun', isSelected: true),
],
),
),
const SizedBox(height: 16),
// Time
Row(
mainAxisAlignment: [Link],
children: [
const Text(
'10:30 AM',
style: TextStyle(fontSize: 18, fontWeight: [Link]),
),
TextButton(
onPressed: () {},
child: const Text('Change'),
),
],
),
const SizedBox(height: 8),
const Text(
'Reminder will repeat every Sunday at 10:30 AM',
style: TextStyle(color: [Link]),
),
const SizedBox(height: 16),
// Buttons
Row(
mainAxisAlignment: [Link],
children: [
ElevatedButton(
onPressed: () {},
child: const Text('Add More Items'),
),
OutlinedButton(
onPressed: () {},
child: const Text('Add to List'),
),
],
),
const SizedBox(height: 16),
// Reminder list
Expanded(
child: [Link](
itemCount: 5,
itemBuilder: (context, index) {
return Card(
child: ListTile(
leading: [Link]('assets/images/[Link]'),
title: const Text('Nutella Hazelnut Spread'),
subtitle: const Text('480Kč, 52% OFF'),
trailing: Row(
mainAxisSize: [Link],
children: [
IconButton(
onPressed: () {},
icon: const Icon([Link], color: [Link]),
),
IconButton(
onPressed: () {},
icon: const Icon([Link]),
),
],
),
),
);
},
),
)
],
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Container(
margin: const [Link](right: 8),
child: Chip(
label: Text(label),
backgroundColor: isSelected ? [Link] : [Link].shade200,
labelStyle: TextStyle(
color: isSelected ? [Link] : [Link],
),
),
);
}
}
// -----------------------------------------------------------------------------
// EXECUTE TRIP SCREEN (Map + Steps)
// -----------------------------------------------------------------------------
class ExecuteTripScreen extends StatelessWidget {
static const routeName = '/executeTrip';
const ExecuteTripScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// For simplicity, we’ll show a placeholder map area and list of stops
return Scaffold(
appBar: AppBar(
title: const Text('Execute Your Shopping Trip'),
),
body: Column(
children: [
// Map placeholder
Container(
height: 200,
color: [Link][300],
child: const Center(child: Text('Map Placeholder')),
),
Expanded(
child: ListView(
padding: const [Link](16),
children: [
const Text(
'Your place',
style: TextStyle(fontWeight: [Link]),
),
const Text('<saved address>'),
const Divider(),
ListTile(
leading: const Icon([Link]),
title: const Text('Insta Grocery Store'),
subtitle: const Text('10 Items • 220Kč'),
trailing: const Text('1st Stop'),
),
ListTile(
leading: const Icon([Link]),
title: const Text('Primo grocery de jour'),
subtitle: const Text('10 Items • 220Kč'),
trailing: const Text('2nd Stop'),
),
const Divider(),
ListTile(
leading: const Icon([Link]),
title: const Text('Return home!'),
trailing: const Text('Finish Trip'),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
// Navigate to trip complete or end trip flow
[Link](context, [Link]);
},
child: const Text('End Trip'),
)
],
),
)
],
),
);
}
}
// -----------------------------------------------------------------------------
// TRIP COMPLETE SCREEN
// -----------------------------------------------------------------------------
class TripCompleteScreen extends StatelessWidget {
static const routeName = '/tripComplete';
const TripCompleteScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Basic layout from your mockup
return Scaffold(
appBar: AppBar(
title: const Text('Trip Complete!'),
),
body: Padding(
padding: const [Link](16.0),
child: Column(
children: [
const Text(
'Shopping Trip Complete!',
style: TextStyle(fontSize: 20, fontWeight: [Link]),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: [Link],
children: const [
_TripStat(label: '300Kč', sublabel: 'Savings'),
_TripStat(label: '890Kč', sublabel: 'Expenses'),
_TripStat(label: '35', sublabel: 'Items Purchased'),
],
),
const SizedBox(height: 16),
// Stores visited
const Text(
'Stores Visited:',
style: TextStyle(fontWeight: [Link]),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: const [
_StoreChip(label: 'LIDL\n14 items'),
_StoreChip(label: 'GLOBUS\n14 items'),
_StoreChip(label: 'ALBERT\n14 items'),
_StoreChip(label: 'TESCO\n14 items'),
_StoreChip(label: 'METRO\n14 items'),
],
),
const SizedBox(height: 16),
// Rate your experience
const Text('Rate your experience'),
const SizedBox(height: 8),
Row(
mainAxisAlignment: [Link],
children: [
IconButton(onPressed: () {}, icon: const
Icon(Icons.sentiment_very_dissatisfied)),
IconButton(onPressed: () {}, icon: const
Icon(Icons.sentiment_dissatisfied)),
IconButton(onPressed: () {}, icon: const
Icon(Icons.sentiment_neutral)),
IconButton(onPressed: () {}, icon: const
Icon(Icons.sentiment_satisfied)),
IconButton(onPressed: () {}, icon: const
Icon(Icons.sentiment_very_satisfied)),
],
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// Possibly navigate to create another list
[Link](context, [Link]);
},
child: const Text('Create Another List'),
)
],
),
),
);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
label,
style: const TextStyle(fontSize: 20, fontWeight: [Link]),
),
Text(sublabel),
],
);
}
}
@override
Widget build(BuildContext context) {
return Chip(
label: Text(label, textAlign: [Link]),
backgroundColor: [Link].shade50,
);
}
}
// -----------------------------------------------------------------------------
// TRIP REPORT SCREEN
// -----------------------------------------------------------------------------
class TripReportScreen extends StatelessWidget {
static const routeName = '/tripReport';
const TripReportScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Layout from your mockup
return Scaffold(
appBar: AppBar(
title: const Text('Trip Report'),
),
body: Padding(
padding: const [Link](16.0),
child: Column(
children: [
// Header
Container(
padding: const [Link](16),
decoration: BoxDecoration(
color: [Link].shade50,
borderRadius: [Link](12),
),
child: Column(
children: const [
Text(
'PENNYFOX',
style: TextStyle(fontSize: 24, fontWeight: [Link]),
),
Text('Save Money and Feel Stunning'),
SizedBox(height: 8),
Text(
'Monthly Report for March 2025',
style: TextStyle(fontSize: 16, fontWeight: [Link]),
),
],
),
),
const SizedBox(height: 16),
// Summary
const Text(
'Summary',
style: TextStyle(fontSize: 18, fontWeight: [Link]),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: [Link],
children: const [
_TripStat(label: '300Kč', sublabel: 'Total Savings'),
_TripStat(label: '890Kč', sublabel: 'Total Expenses'),
_TripStat(label: '35', sublabel: 'Total Items'),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: [Link],
children: const [
_TripStat(label: '5', sublabel: 'Trips'),
_TripStat(label: '600Kč', sublabel: 'Avg. Trip Cost'),
_TripStat(label: '12', sublabel: 'Avg. items/trip'),
],
),
const SizedBox(height: 16),
// Most purchased items
const Align(
alignment: [Link],
child: Text(
'Most Purchased Items:',
style: TextStyle(fontWeight: [Link]),
),
),
const SizedBox(height: 8),
// Example list
ListTile(
leading: [Link]('assets/images/[Link]'),
title: const Text('Danone yogurt unflavoured 4p...'),
),
ListTile(
leading: [Link]('assets/images/[Link]'),
title: const Text('Nutella Hazelnut Spread With C...'),
),
ListTile(
leading: [Link]('assets/images/[Link]'),
title: const Text('Skyr by Danone unflavoured yog...'),
),
const SizedBox(height: 16),
// Average Trip Rating
const Align(
alignment: [Link],
child: Text(
'Average Trip Rating:',
style: TextStyle(fontWeight: [Link]),
),
),
Row(
mainAxisAlignment: [Link],
children: [
const Icon([Link], color: [Link]),
const Icon([Link], color: [Link]),
const Icon([Link], color: [Link]),
const Icon(Icons.star_half, color: [Link]),
const Icon(Icons.star_border, color: [Link]),
],
),
],
),
),
);
}
}
// -----------------------------------------------------------------------------
// SHOPPING HISTORY SCREEN (Past Trips + Reports)
// -----------------------------------------------------------------------------
class ShoppingHistoryScreen extends StatefulWidget {
static const routeName = '/shoppingHistory';
const ShoppingHistoryScreen({Key? key}) : super(key: key);
@override
State<ShoppingHistoryScreen> createState() => _ShoppingHistoryScreenState();
}
class _ShoppingHistoryScreenState extends State<ShoppingHistoryScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 2, vsync: this);
[Link]();
}
@override
void dispose() {
_tabController.dispose();
[Link]();
}
@override
Widget build(BuildContext context) {
// Based on your mockup, there's a Past Trips tab and a Reports tab
return Scaffold(
appBar: AppBar(
title: const Text('Shopping History'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: 'Past Trips'),
Tab(text: 'Reports'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
// Past Trips
_PastTripsTab(),
// Reports
_ReportsTab(),
],
),
);
}
}
// Reports Tab
class _ReportsTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Mock data for annual/monthly reports
return ListView(
children: [
const ListTile(
title: Text('Annual Report for March 2025'),
subtitle: Text('unlock in 8 months!'),
trailing: Icon([Link]),
),
ListTile(
title: const Text('Annual Report for March 2025'),
onTap: () {
[Link](context, [Link]);
},
),
ListTile(
title: const Text('Monthly Report for March 2025'),
onTap: () {
[Link](context, [Link]);
},
),
ListTile(
title: const Text('Monthly Report for February 2025'),
onTap: () {
[Link](context, [Link]);
},
),
ListTile(
title: const Text('Monthly Report for January 2025'),
onTap: () {
[Link](context, [Link]);
},
),
],
);
}
}