Flutter + Python Local Backend System Monitor App | Real-Time Battery, RAM, Storage
Demo :
Click Video πππ
Features :
-
✔️ Flutter UI with Cyberpunk theme
-
✔️ Python Flask backend (localhost)
-
✔️ Real-time Battery, RAM, Storage
-
✔️ Offline capable
-
✔️ Final year project ready
-
✔️ Full source code + setup steps
Code :
main.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:google_fonts/google_fonts.dart';
import 'package:glassmorphism_ui/glassmorphism_ui.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:flutter_animate/flutter_animate.dart';
// ==========================================
// 1. THEME & STYLES
// ==========================================
class CyberTheme {
// Colors
static const Color background = Color(0xFF050510);
static const Color surface = Color(0xFF121225);
static const Color neonBlue = Color(0xFF00F0FF);
static const Color neonPink = Color(0xFFFF003C);
static const Color neonGreen = Color(0xFF00FF9D);
static const Color neonPurple = Color(0xFFBC13FE);
static const LinearGradient cyberGradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0A0A1E),
Color(0xFF050510),
],
);
// Text Styles
static TextStyle get titleStyle => GoogleFonts.orbitron(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1.5,
shadows: [
Shadow(color: neonBlue, blurRadius: 10),
],
);
static TextStyle get headerStyle => GoogleFonts.rajdhani(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
);
static TextStyle get labelStyle => GoogleFonts.exo2(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white70,
);
static TextStyle get valueStyle => GoogleFonts.orbitron(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
);
// Theme Data
static ThemeData get themeData => ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: background,
primaryColor: neonBlue,
textTheme: TextTheme(
bodyMedium: GoogleFonts.exo2(color: Colors.white),
),
colorScheme: ColorScheme.dark(
primary: neonBlue,
secondary: neonPink,
surface: surface,
background: background,
),
);
}
// ==========================================
// 2. DATA MODELS & SERVICES
// ==========================================
class SystemData {
final double batteryPercent;
final bool isPlugged;
final double ramPercent;
final double storagePercent;
SystemData({
required this.batteryPercent,
required this.isPlugged,
required this.ramPercent,
required this.storagePercent,
});
factory SystemData.fromJson(Map<String, dynamic> json) {
final data = json['data'];
return SystemData(
batteryPercent: (data['battery']['percent'] as num).toDouble(),
isPlugged: data['battery']['is_plugged'] as bool,
ramPercent: (data['ram']['percent'] as num).toDouble(),
storagePercent: (data['storage']['percent'] as num).toDouble(),
);
}
}
class SystemService {
// Automatically determine the correct host
static String get _baseUrl {
if (kIsWeb) return 'http://127.0.0.1:5000/system';
if (Platform.isAndroid) return 'http://10.0.2.2:5000/system';
return 'http://127.0.0.1:5000/system';
}
static Future<SystemData> fetchSystemStats() async {
try {
final response = await http.get(Uri.parse(_baseUrl)).timeout(
const Duration(seconds: 3),
);
if (response.statusCode == 200) {
return SystemData.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load system data: ${response.statusCode}');
}
} catch (e) {
throw Exception('Connection Refused. Is app.py running? Error: $e');
}
}
}
// ==========================================
// 3. WIDGETS
// ==========================================
class NeonCard extends StatelessWidget {
final String title;
final double percent; // 0.0 to 100.0
final Color neonColor;
final IconData icon;
final String subtitle;
const NeonCard({
super.key,
required this.title,
required this.percent,
required this.neonColor,
required this.icon,
required this.subtitle,
});
@override
Widget build(BuildContext context) {
return GlassContainer(
height: 220,
width: double.infinity,
blur: 10,
border: Border.fromBorderSide(BorderSide.none),
shadowStrength: 4,
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [
CyberTheme.surface.withOpacity(0.6),
CyberTheme.surface.withOpacity(0.3),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: neonColor.withOpacity(0.3),
width: 1.5,
),
boxShadow: [
BoxShadow(
color: neonColor.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 0,
)
]
),
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Header
Row(
children: [
Icon(icon, color: neonColor, size: 24),
const SizedBox(width: 10),
Text(
title,
style: CyberTheme.labelStyle.copyWith(
color: neonColor,
fontWeight: FontWeight.bold,
),
),
],
),
// Indicator
CircularPercentIndicator(
radius: 60.0,
lineWidth: 12.0,
animation: true,
animateFromLastPercent: true,
percent: percent / 100,
center: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${percent.toStringAsFixed(1)}%",
style: CyberTheme.valueStyle.copyWith(fontSize: 22),
),
],
),
circularStrokeCap: CircularStrokeCap.round,
backgroundColor: Colors.white10,
progressColor: neonColor,
maskFilter: MaskFilter.blur(BlurStyle.solid, 2),
).animate().scale(delay: 200.ms, duration: 600.ms, curve: Curves.easeOutBack),
// Subtitle
Text(
subtitle,
style: CyberTheme.labelStyle.copyWith(fontSize: 12, color: Colors.white54),
),
],
),
),
).animate().fadeIn(duration: 600.ms).slideY(begin: 0.1, end: 0, duration: 600.ms);
}
}
class GlitchText extends StatelessWidget {
final String text;
final TextStyle style;
const GlitchText(this.text, {super.key, required this.style});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Text(text, style: style.copyWith(color: CyberTheme.neonBlue.withOpacity(0.7)))
.animate(onPlay: (controller) => controller.repeat(reverse: true))
.moveX(begin: 2, end: -2, duration: 1200.ms, curve: Curves.easeInOut)
.fadeIn(),
Text(text, style: style.copyWith(color: CyberTheme.neonPink.withOpacity(0.7)))
.animate(onPlay: (controller) => controller.repeat(reverse: true))
.moveX(begin: -2, end: 2, duration: 800.ms, curve: Curves.easeInOut)
.fadeIn(),
Text(text, style: style),
],
);
}
}
class RefreshBtn extends StatelessWidget {
final VoidCallback onPressed;
const RefreshBtn({required this.onPressed, super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: CyberTheme.surface,
border: Border.all(color: CyberTheme.neonGreen.withOpacity(0.5)),
boxShadow: [
BoxShadow(
color: CyberTheme.neonGreen.withOpacity(0.2),
blurRadius: 10,
spreadRadius: 2,
)
]
),
child: const Icon(Icons.refresh, color: CyberTheme.neonGreen),
),
).animate(onPlay: (controller) => controller.repeat()).shimmer(duration: 1500.ms, delay: 3000.ms);
}
}
// ==========================================
// 4. SCREENS
// ==========================================
class MonitorScreen extends StatefulWidget {
const MonitorScreen({super.key});
@override
State<MonitorScreen> createState() => _MonitorScreenState();
}
class _MonitorScreenState extends State<MonitorScreen> {
SystemData? _systemData;
bool _isLoading = true;
String _errorMessage = '';
Timer? _timer;
@override
void initState() {
super.initState();
_fetchData();
// Auto-refresh every 2 seconds
_timer = Timer.periodic(const Duration(seconds: 2), (timer) {
if (mounted) _fetchData(silent: true);
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Future<void> _fetchData({bool silent = false}) async {
if (!silent) {
if(mounted) setState(() => _isLoading = true);
}
try {
final data = await SystemService.fetchSystemStats();
if (mounted) {
setState(() {
_systemData = data;
_isLoading = false;
_errorMessage = '';
});
}
} catch (e) {
if (mounted) {
setState(() {
_errorMessage = e.toString();
_isLoading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
// Responsive grid
int crossAxisCount = 1;
if (size.width > 600) crossAxisCount = 2; // Tablet
if (size.width > 900) crossAxisCount = 3; // Desktop
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: CyberTheme.cyberGradient,
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GlitchText("FUZZUTECH", style: CyberTheme.titleStyle),
Text("SYSTEM MONITOR V1.0", style: CyberTheme.labelStyle.copyWith(color: CyberTheme.neonBlue)),
],
),
RefreshBtn(onPressed: () => _fetchData(silent: false)),
],
),
const SizedBox(height: 30),
// Content
Expanded(
child: _isLoading && _systemData == null
? Center(
child: CircularProgressIndicator(
color: CyberTheme.neonBlue,
),
)
: _errorMessage.isNotEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 50, color: CyberTheme.neonPink),
const SizedBox(height: 10),
Text(
"CONNECTION LOST",
style: CyberTheme.headerStyle.copyWith(color: CyberTheme.neonPink),
),
const SizedBox(height: 10),
Text(
_errorMessage,
textAlign: TextAlign.center,
style: CyberTheme.labelStyle,
),
const SizedBox(height: 20),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: CyberTheme.neonBlue.withOpacity(0.2),
side: BorderSide(color: CyberTheme.neonBlue),
),
onPressed: () => _fetchData(),
child: Text("RETRY", style: TextStyle(color: CyberTheme.neonBlue)),
)
],
),
)
: GridView.count(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 15,
mainAxisSpacing: 15,
childAspectRatio: 1.1,
children: [
NeonCard(
title: "BATTERY DRAIN",
icon: Icons.battery_charging_full,
neonColor: CyberTheme.neonGreen,
percent: _systemData!.batteryPercent,
subtitle: _systemData!.isPlugged ? "CHARGING ACTIVE" : "ON BATTERY POWER",
),
NeonCard(
title: "RAM DISPATCH",
icon: Icons.memory,
neonColor: CyberTheme.neonPink,
percent: _systemData!.ramPercent,
subtitle: "REAL-TIME ALLOCATION",
),
NeonCard(
title: "STORAGE CORE",
icon: Icons.storage,
neonColor: CyberTheme.neonBlue,
percent: _systemData!.storagePercent,
subtitle: "DISK SEGMENT FRAGMENTATION",
),
// Extra stylized card if space permits or just generic info
if (crossAxisCount >= 2)
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white10),
borderRadius: BorderRadius.circular(20),
color: Colors.black26
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.settings_input_component, color: CyberTheme.neonPurple, size: 40),
const SizedBox(height: 10),
Text("SYSTEM ONLINE", style: CyberTheme.labelStyle.copyWith(color: CyberTheme.neonPurple)),
Text("ALL SYSTEMS NOMINAL", style: CyberTheme.labelStyle.copyWith(fontSize: 10)),
]
)
)
)
],
),
),
],
),
),
),
),
);
}
}
// ==========================================
// 5. APP ENTRY POINT
// ==========================================
void main() {
WidgetsFlutterBinding.ensureInitialized();
// Force portrait mode and transparent status bar for immersion
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: Color(0xFF050510),
));
runApp(const FuzzuTechApp());
}
class FuzzuTechApp extends StatelessWidget {
const FuzzuTechApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FuzzuTech System Monitor',
debugShowCheckedModeBanner: false,
theme: CyberTheme.themeData,
home: const MonitorScreen(),
);
}
}
app.py
import psutil
import os
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Enable CORS to allow requests from the Flutter app
CORS(app)
@app.route('/')
def home():
return jsonify({
'message': 'FuzzuTech System Monitor Backend is Running!',
'endpoints': {
'system_stats': '/system'
}
})
@app.route('/system', methods=['GET'])
def get_system_stats():
try:
# Battery Info
battery = psutil.sensors_battery()
battery_percent = battery.percent if battery else 0
is_plugged = battery.power_plugged if battery else False
# RAM Info
mem = psutil.virtual_memory()
ram_percent = mem.percent
# Disk Info
# Using root directory to get main drive stats
disk_path = os.path.abspath(os.sep)
disk = psutil.disk_usage(disk_path)
storage_percent = disk.percent
return jsonify({
'status': 'success',
'data': {
'battery': {
'percent': battery_percent,
'is_plugged': is_plugged
},
'ram': {
'percent': ram_percent,
'total': mem.total,
'available': mem.available
},
'storage': {
'percent': storage_percent,
'total': disk.total,
'free': disk.free
}
}
})
except Exception as e:
return jsonify({
'status': 'error',
'message': str(e)
}), 500
if __name__ == '__main__':
print("FuzzuTech System Monitor Backend Running on port 5000...")
app.run(host='0.0.0.0', port=5000, debug=True)
Comments
Post a Comment