singgih_counter_widget 1.0.6
singgih_counter_widget: ^1.0.6 copied to clipboard
A Flutter package providing a customizable counter widget and a live chat widget powered by WebView.
import 'package:flutter/material.dart';
import 'package:singgih_counter_widget/singgih_counter_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Kouventa Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MainShell(),
);
}
}
class MainShell extends StatefulWidget {
const MainShell({super.key});
@override
State<MainShell> createState() => _MainShellState();
}
class _MainShellState extends State<MainShell> {
int _currentIndex = 0;
final _pages = const [
_HomePage(),
_ShopPage(),
_CounterDemoPage(),
_ProfilePage(),
];
@override
Widget build(BuildContext context) {
return Stack(
children: [
Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (i) => setState(() => _currentIndex = i),
destinations: const [
NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: 'Home'),
NavigationDestination(icon: Icon(Icons.store_outlined), selectedIcon: Icon(Icons.store), label: 'Shop'),
NavigationDestination(icon: Icon(Icons.add_circle_outline), selectedIcon: Icon(Icons.add_circle), label: 'Counter'),
NavigationDestination(icon: Icon(Icons.person_outline), selectedIcon: Icon(Icons.person), label: 'Profile'),
],
),
),
LiveChatOverlayButton(config: LivechatModel(id: '63db685f-9ae6-49f1-9a4a-b9ffb3215256')),
],
);
}
}
// ──────────────────────────────────────────────
// Home Page
// ──────────────────────────────────────────────
class _HomePage extends StatelessWidget {
const _HomePage();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Kouventa'),
actions: [
IconButton(icon: const Icon(Icons.notifications_outlined), onPressed: () {}),
IconButton(icon: const Icon(Icons.search), onPressed: () {}),
],
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Banner
Container(
height: 160,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [theme.colorScheme.primary, theme.colorScheme.secondary],
),
),
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Welcome to Kouventa', style: TextStyle(color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text('Your all-in-one solution', style: TextStyle(color: Colors.white70)),
],
),
),
),
const SizedBox(height: 24),
Text('Quick Actions', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_QuickAction(icon: Icons.flash_on, label: 'Flash Sale'),
_QuickAction(icon: Icons.category, label: 'Categories'),
_QuickAction(icon: Icons.local_offer, label: 'Vouchers'),
_QuickAction(icon: Icons.history, label: 'History'),
],
),
const SizedBox(height: 24),
Text('Featured Products', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 4,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.75,
),
itemBuilder: (_, i) => _ProductCard(index: i),
),
const SizedBox(height: 24),
Text('Latest Articles', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 12),
...List.generate(3, (i) => _ArticleCard(index: i)),
],
),
);
}
}
class _QuickAction extends StatelessWidget {
final IconData icon;
final String label;
const _QuickAction({required this.icon, required this.label});
@override
Widget build(BuildContext context) {
return Column(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
child: Icon(icon, color: Theme.of(context).colorScheme.primary),
),
const SizedBox(height: 6),
Text(label, style: const TextStyle(fontSize: 12)),
],
);
}
}
class _ProductCard extends StatelessWidget {
final int index;
const _ProductCard({required this.index});
static const _names = ['Wireless Earbuds', 'Smart Watch', 'Mechanical Keyboard', 'USB-C Hub'];
static const _prices = ['Rp 299.000', 'Rp 899.000', 'Rp 1.250.000', 'Rp 450.000'];
static const _ratings = ['4.8', '4.6', '4.9', '4.7'];
static const _colors = [Colors.blue, Colors.green, Colors.orange, Colors.purple];
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: _colors[index % _colors.length].withAlpha(40),
child: Center(child: Icon(Icons.shopping_bag_outlined, size: 48, color: _colors[index % _colors.length])),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_names[index % _names.length], style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13), maxLines: 1, overflow: TextOverflow.ellipsis),
const SizedBox(height: 2),
Text(_prices[index % _prices.length], style: TextStyle(color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.bold, fontSize: 13)),
Row(
children: [
const Icon(Icons.star, size: 12, color: Colors.amber),
const SizedBox(width: 2),
Text(_ratings[index % _ratings.length], style: const TextStyle(fontSize: 11)),
],
),
],
),
),
],
),
);
}
}
class _ArticleCard extends StatelessWidget {
final int index;
const _ArticleCard({required this.index});
static const _titles = [
'Top 10 Gadgets You Need in 2025',
'How to Choose the Right Smartwatch',
'Wireless Audio: A Buyer\'s Guide',
];
static const _subtitles = [
'Technology is evolving fast — here are the must-haves.',
'Battery life, health tracking, and more explained.',
'Bluetooth codecs, latency, and what actually matters.',
];
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
child: const Icon(Icons.article_outlined),
),
title: Text(_titles[index % _titles.length], style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 13)),
subtitle: Text(_subtitles[index % _subtitles.length], maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12)),
trailing: const Icon(Icons.chevron_right),
),
);
}
}
// ──────────────────────────────────────────────
// Shop Page
// ──────────────────────────────────────────────
class _ShopPage extends StatelessWidget {
const _ShopPage();
static const _categories = ['All', 'Electronics', 'Fashion', 'Home', 'Sports', 'Beauty'];
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: _categories.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Shop'),
bottom: TabBar(
isScrollable: true,
tabs: _categories.map((c) => Tab(text: c)).toList(),
),
),
body: TabBarView(
children: _categories.map((_) => _ShopGrid()).toList(),
),
),
);
}
}
class _ShopGrid extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView.builder(
padding: const EdgeInsets.all(12),
itemCount: 8,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 0.7,
),
itemBuilder: (_, i) => _ProductCard(index: i),
);
}
}
// ──────────────────────────────────────────────
// Counter Demo Page
// ──────────────────────────────────────────────
class _CounterDemoPage extends StatelessWidget {
const _CounterDemoPage();
@override
Widget build(BuildContext context) {
return const CounterPage(title: 'Counter Demo');
}
}
// ──────────────────────────────────────────────
// Profile Page
// ──────────────────────────────────────────────
class _ProfilePage extends StatelessWidget {
const _ProfilePage();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Profile')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Center(
child: Column(
children: [
CircleAvatar(radius: 48, backgroundColor: theme.colorScheme.primaryContainer, child: const Icon(Icons.person, size: 48)),
const SizedBox(height: 12),
const Text('Singgih Rama', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const Text('singgih@example.com', style: TextStyle(color: Colors.grey)),
],
),
),
const SizedBox(height: 24),
Card(
child: Column(
children: [
_ProfileTile(icon: Icons.shopping_bag_outlined, label: 'My Orders'),
const Divider(height: 0),
_ProfileTile(icon: Icons.favorite_outline, label: 'Wishlist'),
const Divider(height: 0),
_ProfileTile(icon: Icons.location_on_outlined, label: 'Addresses'),
const Divider(height: 0),
_ProfileTile(icon: Icons.payment_outlined, label: 'Payment Methods'),
],
),
),
const SizedBox(height: 12),
Card(
child: Column(
children: [
_ProfileTile(icon: Icons.notifications_outlined, label: 'Notifications'),
const Divider(height: 0),
_ProfileTile(icon: Icons.help_outline, label: 'Help & Support'),
const Divider(height: 0),
_ProfileTile(icon: Icons.info_outline, label: 'About'),
],
),
),
const SizedBox(height: 12),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.logout),
label: const Text('Sign Out'),
style: OutlinedButton.styleFrom(foregroundColor: Colors.red),
),
],
),
);
}
}
class _ProfileTile extends StatelessWidget {
final IconData icon;
final String label;
const _ProfileTile({required this.icon, required this.label});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Icon(icon, color: Theme.of(context).colorScheme.primary),
title: Text(label),
trailing: const Icon(Icons.chevron_right, size: 18),
onTap: () {},
);
}
}