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

Popular posts from this blog

Educational File Encryptor GUI (Python AES Project) | FuzzuTech

Is This News Real or Fake? πŸ€– AI Exposes the Truth | FuzzuTech Python App Demo

🚨 Python Intrusion Detection System (IDS) – Real-Time ML + Tkinter GUI Project | FuzzuTech