main.dart 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import 'dart:io';
  2. import 'package:flutter/material.dart';
  3. import 'package:shared_preferences/shared_preferences.dart';
  4. import 'slide_controller.dart';
  5. void main() => runApp(const PptRemoteApp());
  6. class PptRemoteApp extends StatelessWidget {
  7. const PptRemoteApp({super.key});
  8. @override
  9. Widget build(BuildContext context) {
  10. return MaterialApp(
  11. title: 'PPT Remote',
  12. theme: ThemeData.dark(useMaterial3: true).copyWith(
  13. colorScheme: ColorScheme.fromSeed(
  14. seedColor: Colors.deepOrange,
  15. brightness: Brightness.dark,
  16. ),
  17. ),
  18. home: const ConnectScreen(),
  19. );
  20. }
  21. }
  22. // ─── Connect Screen ───────────────────────────────────────────────────────────
  23. class ConnectScreen extends StatefulWidget {
  24. const ConnectScreen({super.key});
  25. @override
  26. State<ConnectScreen> createState() => _ConnectScreenState();
  27. }
  28. class _ConnectScreenState extends State<ConnectScreen> {
  29. final _hostController = TextEditingController(text: '192.168.1.100');
  30. final _portController = TextEditingController(text: '8765');
  31. @override
  32. void initState() {
  33. super.initState();
  34. _loadPrefs();
  35. }
  36. Future<void> _loadPrefs() async {
  37. final prefs = await SharedPreferences.getInstance();
  38. setState(() {
  39. _hostController.text = prefs.getString('host') ?? '192.168.1.100';
  40. _portController.text = prefs.getString('port') ?? '8765';
  41. });
  42. }
  43. bool _checking = false;
  44. String? _checkError;
  45. Future<void> _connect() async {
  46. final host = _hostController.text.trim();
  47. final port = _portController.text.trim();
  48. final portNum = int.tryParse(port) ?? 8765;
  49. setState(() {
  50. _checking = true;
  51. _checkError = null;
  52. });
  53. // Probe the HTTP endpoint before opening a WebSocket
  54. final reachable = await _checkServer(host, portNum);
  55. if (!mounted) return;
  56. if (!reachable) {
  57. setState(() {
  58. _checking = false;
  59. _checkError = 'Cannot reach $host:$portNum\n'
  60. 'Make sure the server is running and both devices are on the same Wi-Fi.';
  61. });
  62. return;
  63. }
  64. final prefs = await SharedPreferences.getInstance();
  65. await prefs.setString('host', host);
  66. await prefs.setString('port', port);
  67. setState(() => _checking = false);
  68. if (!mounted) return;
  69. Navigator.of(context).push(MaterialPageRoute(
  70. builder: (_) => RemoteScreen(host: host, port: portNum),
  71. ));
  72. }
  73. /// Tries a plain TCP connection to host:port with a 3-second timeout.
  74. Future<bool> _checkServer(String host, int port) async {
  75. try {
  76. final socket = await Socket.connect(host, port,
  77. timeout: const Duration(seconds: 3));
  78. socket.destroy();
  79. return true;
  80. } catch (_) {
  81. return false;
  82. }
  83. }
  84. @override
  85. Widget build(BuildContext context) {
  86. return Scaffold(
  87. appBar: AppBar(title: const Text('PPT Remote')),
  88. body: Padding(
  89. padding: const EdgeInsets.all(24),
  90. child: Column(
  91. mainAxisAlignment: MainAxisAlignment.center,
  92. children: [
  93. const Icon(Icons.slideshow, size: 72, color: Colors.deepOrange),
  94. const SizedBox(height: 32),
  95. TextField(
  96. controller: _hostController,
  97. decoration: const InputDecoration(
  98. labelText: 'Server IP',
  99. border: OutlineInputBorder(),
  100. prefixIcon: Icon(Icons.computer),
  101. ),
  102. keyboardType: TextInputType.url,
  103. ),
  104. const SizedBox(height: 16),
  105. TextField(
  106. controller: _portController,
  107. decoration: const InputDecoration(
  108. labelText: 'Port',
  109. border: OutlineInputBorder(),
  110. prefixIcon: Icon(Icons.settings_ethernet),
  111. ),
  112. keyboardType: TextInputType.number,
  113. ),
  114. const SizedBox(height: 32),
  115. if (_checkError != null)
  116. Padding(
  117. padding: const EdgeInsets.only(bottom: 16),
  118. child: Row(
  119. children: [
  120. const Icon(Icons.error_outline, color: Colors.redAccent),
  121. const SizedBox(width: 8),
  122. Expanded(
  123. child: Text(
  124. _checkError!,
  125. style: const TextStyle(color: Colors.redAccent),
  126. ),
  127. ),
  128. ],
  129. ),
  130. ),
  131. FilledButton.icon(
  132. onPressed: _checking ? null : _connect,
  133. icon: _checking
  134. ? const SizedBox(
  135. width: 18,
  136. height: 18,
  137. child: CircularProgressIndicator(strokeWidth: 2),
  138. )
  139. : const Icon(Icons.wifi),
  140. label: Text(_checking ? 'Checking...' : 'Connect'),
  141. style: FilledButton.styleFrom(
  142. minimumSize: const Size.fromHeight(52),
  143. ),
  144. ),
  145. ],
  146. ),
  147. ),
  148. );
  149. }
  150. }