| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- import 'dart:io';
- import 'package:flutter/material.dart';
- import 'package:shared_preferences/shared_preferences.dart';
- import 'slide_controller.dart';
- void main() => runApp(const PptRemoteApp());
- class PptRemoteApp extends StatelessWidget {
- const PptRemoteApp({super.key});
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- title: 'PPT Remote',
- theme: ThemeData.dark(useMaterial3: true).copyWith(
- colorScheme: ColorScheme.fromSeed(
- seedColor: Colors.deepOrange,
- brightness: Brightness.dark,
- ),
- ),
- home: const ConnectScreen(),
- );
- }
- }
- // ─── Connect Screen ───────────────────────────────────────────────────────────
- class ConnectScreen extends StatefulWidget {
- const ConnectScreen({super.key});
- @override
- State<ConnectScreen> createState() => _ConnectScreenState();
- }
- class _ConnectScreenState extends State<ConnectScreen> {
- final _hostController = TextEditingController(text: '192.168.1.100');
- final _portController = TextEditingController(text: '8765');
- @override
- void initState() {
- super.initState();
- _loadPrefs();
- }
- Future<void> _loadPrefs() async {
- final prefs = await SharedPreferences.getInstance();
- setState(() {
- _hostController.text = prefs.getString('host') ?? '192.168.1.100';
- _portController.text = prefs.getString('port') ?? '8765';
- });
- }
- bool _checking = false;
- String? _checkError;
- Future<void> _connect() async {
- final host = _hostController.text.trim();
- final port = _portController.text.trim();
- final portNum = int.tryParse(port) ?? 8765;
- setState(() {
- _checking = true;
- _checkError = null;
- });
- // Probe the HTTP endpoint before opening a WebSocket
- final reachable = await _checkServer(host, portNum);
- if (!mounted) return;
- if (!reachable) {
- setState(() {
- _checking = false;
- _checkError = 'Cannot reach $host:$portNum\n'
- 'Make sure the server is running and both devices are on the same Wi-Fi.';
- });
- return;
- }
- final prefs = await SharedPreferences.getInstance();
- await prefs.setString('host', host);
- await prefs.setString('port', port);
- setState(() => _checking = false);
- if (!mounted) return;
- Navigator.of(context).push(MaterialPageRoute(
- builder: (_) => RemoteScreen(host: host, port: portNum),
- ));
- }
- /// Tries a plain TCP connection to host:port with a 3-second timeout.
- Future<bool> _checkServer(String host, int port) async {
- try {
- final socket = await Socket.connect(host, port,
- timeout: const Duration(seconds: 3));
- socket.destroy();
- return true;
- } catch (_) {
- return false;
- }
- }
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: const Text('PPT Remote')),
- body: Padding(
- padding: const EdgeInsets.all(24),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Icon(Icons.slideshow, size: 72, color: Colors.deepOrange),
- const SizedBox(height: 32),
- TextField(
- controller: _hostController,
- decoration: const InputDecoration(
- labelText: 'Server IP',
- border: OutlineInputBorder(),
- prefixIcon: Icon(Icons.computer),
- ),
- keyboardType: TextInputType.url,
- ),
- const SizedBox(height: 16),
- TextField(
- controller: _portController,
- decoration: const InputDecoration(
- labelText: 'Port',
- border: OutlineInputBorder(),
- prefixIcon: Icon(Icons.settings_ethernet),
- ),
- keyboardType: TextInputType.number,
- ),
- const SizedBox(height: 32),
- if (_checkError != null)
- Padding(
- padding: const EdgeInsets.only(bottom: 16),
- child: Row(
- children: [
- const Icon(Icons.error_outline, color: Colors.redAccent),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- _checkError!,
- style: const TextStyle(color: Colors.redAccent),
- ),
- ),
- ],
- ),
- ),
- FilledButton.icon(
- onPressed: _checking ? null : _connect,
- icon: _checking
- ? const SizedBox(
- width: 18,
- height: 18,
- child: CircularProgressIndicator(strokeWidth: 2),
- )
- : const Icon(Icons.wifi),
- label: Text(_checking ? 'Checking...' : 'Connect'),
- style: FilledButton.styleFrom(
- minimumSize: const Size.fromHeight(52),
- ),
- ),
- ],
- ),
- ),
- );
- }
- }
|