real_ist_time 1.0.0
real_ist_time: ^1.0.0 copied to clipboard
Get accurate Indian Standard Time (IST) from multiple NTP servers and HTTP APIs with smart caching and automatic fallback. Independent of device clock settings.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:real_ist_time/real_ist_time.dart';
import 'dart:async';
void main() {
runApp(const RealIstTimeExampleApp());
}
class RealIstTimeExampleApp extends StatelessWidget {
const RealIstTimeExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Real IST Time Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
DateTime? _currentTime;
String? _source;
Duration? _latency;
bool _fromCache = false;
bool _isLoading = false;
String? _errorMessage;
Timer? _timer;
Map<String, dynamic>? _cacheStats;
@override
void initState() {
super.initState();
_fetchTime();
// Auto-refresh every second
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
if (_currentTime != null && !_isLoading) {
setState(() {
_currentTime = _currentTime!.add(const Duration(seconds: 1));
});
}
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Future<void> _fetchTime() async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final result = await RealIstTime.getIstTimeWithSource();
setState(() {
_currentTime = result.dateTime;
_source = result.source;
_latency = result.latency;
_fromCache = result.fromCache;
_cacheStats = RealIstTime.getCacheStatus();
_isLoading = false;
});
} catch (e) {
setState(() {
_errorMessage = e.toString();
_isLoading = false;
});
}
}
Future<void> _testAllSources() async {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
try {
final results = await RealIstTime.testAllSources();
if (!mounted) return;
Navigator.pop(context);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('All Sources Test Results'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: results.length,
itemBuilder: (context, index) {
final result = results[index];
return ListTile(
leading: const Icon(Icons.check_circle, color: Colors.green),
title: Text(result.source),
subtitle: Text(
'Latency: ${result.latency.inMilliseconds}ms\n'
'Time: ${result.dateTime}',
),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
} catch (e) {
if (!mounted) return;
Navigator.pop(context);
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error: $e')));
}
}
void _showConfigDialog() {
showDialog(context: context, builder: (context) => const ConfigDialog());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Real IST Time'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: _showConfigDialog,
tooltip: 'Configuration',
),
],
),
body: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Main Time Display
if (_isLoading)
const CircularProgressIndicator()
else if (_errorMessage != null)
Column(
children: [
const Icon(Icons.error, color: Colors.red, size: 64),
const SizedBox(height: 16),
Text(
'Error',
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
_errorMessage!,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red),
),
],
)
else if (_currentTime != null)
Column(
children: [
// Time Display
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.deepPurple.shade700,
Colors.deepPurple.shade900,
],
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.deepPurple.withValues(alpha: 0.3),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: Column(
children: [
Text(
_formatTime(_currentTime!),
style: const TextStyle(
fontSize: 56,
fontWeight: FontWeight.bold,
letterSpacing: 2,
),
),
const SizedBox(height: 8),
Text(
_formatDate(_currentTime!),
style: TextStyle(
fontSize: 20,
color: Colors.white.withValues(alpha: 0.8),
),
),
],
),
),
const SizedBox(height: 32),
// Source Info
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_InfoRow(
icon: Icons.source,
label: 'Source',
value: _source ?? 'Unknown',
),
const Divider(),
_InfoRow(
icon: Icons.speed,
label: 'Latency',
value: '${_latency?.inMilliseconds ?? 0}ms',
),
const Divider(),
_InfoRow(
icon: Icons.cached,
label: 'From Cache',
value: _fromCache ? 'Yes' : 'No',
),
],
),
),
),
// Cache Stats
if (_cacheStats != null) ...[
const SizedBox(height: 16),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Cache Status',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text('Valid: ${_cacheStats!['isValid']}'),
Text(
'Age: ${_cacheStats!['ageSeconds']} seconds',
),
],
),
),
),
],
],
),
const SizedBox(height: 32),
// Action Buttons
Wrap(
spacing: 12,
runSpacing: 12,
alignment: WrapAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _isLoading ? null : _fetchTime,
icon: const Icon(Icons.refresh),
label: const Text('Refresh'),
),
ElevatedButton.icon(
onPressed: _isLoading
? null
: () {
RealIstTime.clearCache();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cache cleared')),
);
_fetchTime();
},
icon: const Icon(Icons.clear),
label: const Text('Clear Cache'),
),
ElevatedButton.icon(
onPressed: _isLoading ? null : _testAllSources,
icon: const Icon(Icons.science),
label: const Text('Test All Sources'),
),
],
),
],
),
),
),
);
}
String _formatTime(DateTime time) {
return '${time.hour.toString().padLeft(2, '0')}:'
'${time.minute.toString().padLeft(2, '0')}:'
'${time.second.toString().padLeft(2, '0')}';
}
String _formatDate(DateTime time) {
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
return '${time.day} ${months[time.month - 1]} ${time.year}';
}
}
class _InfoRow extends StatelessWidget {
final IconData icon;
final String label;
final String value;
const _InfoRow({
required this.icon,
required this.label,
required this.value,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(icon, size: 20),
const SizedBox(width: 12),
Text(label, style: const TextStyle(fontWeight: FontWeight.w500)),
const Spacer(),
Text(
value,
style: TextStyle(color: Colors.white.withValues(alpha: 0.7)),
),
],
);
}
}
class ConfigDialog extends StatefulWidget {
const ConfigDialog({super.key});
@override
State<ConfigDialog> createState() => _ConfigDialogState();
}
class _ConfigDialogState extends State<ConfigDialog> {
late bool _useNtp;
late bool _useHttp;
late bool _useCache;
late int _cacheDuration;
@override
void initState() {
super.initState();
final config = RealIstTime.getConfig();
_useNtp = config.useNtp;
_useHttp = config.useHttp;
_useCache = config.useCache;
_cacheDuration = config.cacheDurationSeconds;
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Configuration'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
SwitchListTile(
title: const Text('Use NTP'),
subtitle: const Text('Google, Cloudflare, etc.'),
value: _useNtp,
onChanged: (value) => setState(() => _useNtp = value),
),
SwitchListTile(
title: const Text('Use HTTP APIs'),
subtitle: const Text('timeapi.io, worldtimeapi.org'),
value: _useHttp,
onChanged: (value) => setState(() => _useHttp = value),
),
SwitchListTile(
title: const Text('Use Cache'),
value: _useCache,
onChanged: (value) => setState(() => _useCache = value),
),
ListTile(
title: const Text('Cache Duration'),
subtitle: Slider(
value: _cacheDuration.toDouble(),
min: 10,
max: 300,
divisions: 29,
label: '$_cacheDuration seconds',
onChanged: (value) =>
setState(() => _cacheDuration = value.toInt()),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
RealIstTime.configure(
IstTimeConfig(
useNtp: _useNtp,
useHttp: _useHttp,
useCache: _useCache,
cacheDurationSeconds: _cacheDuration,
),
);
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Configuration updated')),
);
},
child: const Text('Apply'),
),
],
);
}
}