Browse Source

Add endpoint discovery mechanism

Vishnu Mohandas 5 năm trước cách đây
mục cha
commit
d228f7278f

+ 49 - 0
lib/core/configuration.dart

@@ -0,0 +1,49 @@
+import 'package:shared_preferences/shared_preferences.dart';
+
+class Configuration {
+  Configuration._privateConstructor();
+  static final Configuration instance = Configuration._privateConstructor();
+
+  static const endpointKey = "endpoint_7";
+  static const tokenKey = "token";
+  static const usernameKey = "username";
+  static const passwordKey = "password";
+
+  SharedPreferences preferences;
+
+  Future<void> init() async {
+    preferences = await SharedPreferences.getInstance();
+  }
+
+  String getEndpoint() {
+    return preferences.getString(endpointKey);
+  }
+
+  void setEndpoint(String endpoint) async {
+    await preferences.setString(endpointKey, endpoint);
+  }
+
+  String getToken() {
+    return preferences.getString(tokenKey);
+  }
+
+  void setToken(String token) async {
+    await preferences.setString(tokenKey, token);
+  }
+
+  String getUsername() {
+    return preferences.getString(usernameKey);
+  }
+
+  void setUsername(String username) async {
+    await preferences.setString(usernameKey, username);
+  }
+
+  String getPassword() {
+    return preferences.getString(passwordKey);
+  }
+
+  void setPassword(String password) async {
+    await preferences.setString(passwordKey, password);
+  }
+}

+ 5 - 0
lib/core/event_bus.dart

@@ -0,0 +1,5 @@
+import 'package:event_bus/event_bus.dart';
+
+class Bus {
+  static final EventBus instance = EventBus();
+}

+ 5 - 0
lib/events/remote_sync_event.dart

@@ -0,0 +1,5 @@
+class RemoteSyncEvent {
+  final bool success;
+
+  RemoteSyncEvent(this.success);
+}

+ 1 - 0
lib/events/user_authenticated_event.dart

@@ -0,0 +1 @@
+class UserAuthenticatedEvent {}

+ 4 - 7
lib/main.dart

@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:logger/logger.dart';
 import 'package:logger/logger.dart';
+import 'package:myapp/core/configuration.dart';
 import 'package:myapp/photo_loader.dart';
 import 'package:myapp/photo_loader.dart';
-import 'package:myapp/photo_provider.dart';
 import 'package:myapp/photo_sync_manager.dart';
 import 'package:myapp/photo_sync_manager.dart';
 import 'package:myapp/ui/home_widget.dart';
 import 'package:myapp/ui/home_widget.dart';
 import 'package:provider/provider.dart';
 import 'package:provider/provider.dart';
@@ -9,9 +9,8 @@ import 'package:provider/provider.dart';
 void main() async {
 void main() async {
   WidgetsFlutterBinding.ensureInitialized();
   WidgetsFlutterBinding.ensureInitialized();
   runApp(MyApp());
   runApp(MyApp());
-  PhotoProvider.instance
-      .refreshGalleryList()
-      .then((_) => PhotoSyncManager.instance.load(PhotoProvider.instance.list));
+  Configuration.instance.init();
+  PhotoSyncManager.instance.sync();
 }
 }
 
 
 class MyApp extends StatelessWidget with WidgetsBindingObserver {
 class MyApp extends StatelessWidget with WidgetsBindingObserver {
@@ -33,9 +32,7 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver {
   @override
   @override
   void didChangeAppLifecycleState(AppLifecycleState state) {
   void didChangeAppLifecycleState(AppLifecycleState state) {
     if (state == AppLifecycleState.resumed) {
     if (state == AppLifecycleState.resumed) {
-      PhotoProvider.instance.refreshGalleryList().then((_) {
-        return PhotoSyncManager.instance.load(PhotoProvider.instance.list);
-      });
+      PhotoSyncManager.instance.sync();
     }
     }
   }
   }
 }
 }

+ 37 - 26
lib/photo_sync_manager.dart

@@ -2,8 +2,11 @@ import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
 import 'package:logger/logger.dart';
 import 'package:logger/logger.dart';
+import 'package:myapp/core/event_bus.dart';
 import 'package:myapp/db/db_helper.dart';
 import 'package:myapp/db/db_helper.dart';
+import 'package:myapp/events/user_authenticated_event.dart';
 import 'package:myapp/photo_loader.dart';
 import 'package:myapp/photo_loader.dart';
+import 'package:myapp/photo_provider.dart';
 import 'package:path/path.dart';
 import 'package:path/path.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photo_manager/photo_manager.dart';
@@ -12,25 +15,33 @@ import 'package:dio/dio.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/core/constants.dart' as Constants;
 import 'package:myapp/core/constants.dart' as Constants;
 
 
+import 'events/remote_sync_event.dart';
+
 class PhotoSyncManager {
 class PhotoSyncManager {
   final _logger = Logger();
   final _logger = Logger();
   final _dio = Dio();
   final _dio = Dio();
-  bool _isLoadInProgress = false;
+  bool _isSyncInProgress = false;
 
 
   static final _lastSyncTimestampKey = "last_sync_timestamp_0";
   static final _lastSyncTimestampKey = "last_sync_timestamp_0";
   static final _lastDBUpdateTimestampKey = "last_db_update_timestamp";
   static final _lastDBUpdateTimestampKey = "last_db_update_timestamp";
 
 
-  PhotoSyncManager._privateConstructor();
+  PhotoSyncManager._privateConstructor() {
+    Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
+      sync();
+    });
+  }
+
   static final PhotoSyncManager instance =
   static final PhotoSyncManager instance =
       PhotoSyncManager._privateConstructor();
       PhotoSyncManager._privateConstructor();
 
 
-  Future<void> load(List<AssetPathEntity> pathEntities) async {
-    if (_isLoadInProgress) {
-      _logger.w("Load already in progress, skipping.");
+  Future<void> sync() async {
+    if (_isSyncInProgress) {
+      _logger.w("Sync already in progress, skipping.");
       return;
       return;
     }
     }
-    _isLoadInProgress = true;
-    _logger.i("Loading...");
+    _isSyncInProgress = true;
+    _logger.i("Syncing...");
+
     final prefs = await SharedPreferences.getInstance();
     final prefs = await SharedPreferences.getInstance();
     var lastDBUpdateTimestamp = prefs.getInt(_lastDBUpdateTimestampKey);
     var lastDBUpdateTimestamp = prefs.getInt(_lastDBUpdateTimestampKey);
     if (lastDBUpdateTimestamp == null) {
     if (lastDBUpdateTimestamp == null) {
@@ -38,6 +49,8 @@ class PhotoSyncManager {
       await _initializeDirectories();
       await _initializeDirectories();
     }
     }
 
 
+    await PhotoProvider.instance.refreshGalleryList();
+    final pathEntities = PhotoProvider.instance.list;
     final photos = List<Photo>();
     final photos = List<Photo>();
     for (AssetPathEntity pathEntity in pathEntities) {
     for (AssetPathEntity pathEntity in pathEntities) {
       if (Platform.isIOS || pathEntity.name != "Recent") {
       if (Platform.isIOS || pathEntity.name != "Recent") {
@@ -56,16 +69,15 @@ class PhotoSyncManager {
       }
       }
     }
     }
     if (photos.isEmpty) {
     if (photos.isEmpty) {
-      _isLoadInProgress = false;
-      return;
+      _isSyncInProgress = false;
+      _syncPhotos().then((_) {
+        _deletePhotos();
+      });
     } else {
     } else {
       photos.sort((first, second) =>
       photos.sort((first, second) =>
           first.createTimestamp.compareTo(second.createTimestamp));
           first.createTimestamp.compareTo(second.createTimestamp));
       _updateDatabase(photos, prefs, lastDBUpdateTimestamp).then((_) {
       _updateDatabase(photos, prefs, lastDBUpdateTimestamp).then((_) {
-        _isLoadInProgress = false;
-        _syncPhotos().then((_) {
-          _deletePhotos();
-        });
+        _isSyncInProgress = false;
       });
       });
     }
     }
   }
   }
@@ -91,9 +103,11 @@ class PhotoSyncManager {
     _logger.i("Last sync timestamp: " + lastSyncTimestamp.toString());
     _logger.i("Last sync timestamp: " + lastSyncTimestamp.toString());
 
 
     _getDiff(lastSyncTimestamp).then((diff) {
     _getDiff(lastSyncTimestamp).then((diff) {
-      _downloadDiff(diff, prefs).then((_) {
-        _uploadDiff(prefs);
-      });
+      if (diff != null) {
+        _downloadDiff(diff, prefs).then((_) {
+          _uploadDiff(prefs);
+        });
+      }
     });
     });
 
 
     // TODO:  Fix race conditions triggered due to concurrent syncs.
     // TODO:  Fix race conditions triggered due to concurrent syncs.
@@ -121,7 +135,7 @@ class PhotoSyncManager {
       var localPath = path + basename(photo.remotePath);
       var localPath = path + basename(photo.remotePath);
       await _dio
       await _dio
           .download(Constants.ENDPOINT + "/" + photo.remotePath, localPath)
           .download(Constants.ENDPOINT + "/" + photo.remotePath, localPath)
-          .catchError(_onError);
+          .catchError((e) => _logger.e(e));
       // TODO: Save path
       // TODO: Save path
       photo.pathName = localPath;
       photo.pathName = localPath;
       await DatabaseHelper.instance.insertPhoto(photo);
       await DatabaseHelper.instance.insertPhoto(photo);
@@ -135,14 +149,15 @@ class PhotoSyncManager {
         queryParameters: {
         queryParameters: {
           "user": Constants.USER,
           "user": Constants.USER,
           "lastSyncTimestamp": lastSyncTimestamp
           "lastSyncTimestamp": lastSyncTimestamp
-        }).catchError(_onError);
-    _logger.i(response.toString());
+        }).catchError((e) => _logger.e(e));
     if (response != null) {
     if (response != null) {
+      Bus.instance.fire(RemoteSyncEvent(true));
       return (response.data["diff"] as List)
       return (response.data["diff"] as List)
           .map((photo) => new Photo.fromJson(photo))
           .map((photo) => new Photo.fromJson(photo))
           .toList();
           .toList();
     } else {
     } else {
-      return List<Photo>();
+      Bus.instance.fire(RemoteSyncEvent(false));
+      return null;
     }
     }
   }
   }
 
 
@@ -158,7 +173,7 @@ class PhotoSyncManager {
       _logger.i(response.toString());
       _logger.i(response.toString());
       var photo = Photo.fromJson(response.data);
       var photo = Photo.fromJson(response.data);
       return photo;
       return photo;
-    }).catchError(_onError);
+    }).catchError((e) => _logger.e(e));
   }
   }
 
 
   Future<void> _deletePhotos() async {
   Future<void> _deletePhotos() async {
@@ -174,11 +189,7 @@ class PhotoSyncManager {
     return _dio.post(Constants.ENDPOINT + "/delete", queryParameters: {
     return _dio.post(Constants.ENDPOINT + "/delete", queryParameters: {
       "user": Constants.USER,
       "user": Constants.USER,
       "fileID": photo.uploadedFileId
       "fileID": photo.uploadedFileId
-    }).catchError((e) => _onError(e));
-  }
-
-  void _onError(error) {
-    _logger.e(error);
+    }).catchError((e) => _logger.e(e));
   }
   }
 
 
   Future _initializeDirectories() async {
   Future _initializeDirectories() async {

+ 47 - 3
lib/ui/gallery_app_bar_widget.dart

@@ -1,8 +1,12 @@
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:logger/logger.dart';
+import 'package:myapp/core/event_bus.dart';
 import 'package:myapp/db/db_helper.dart';
 import 'package:myapp/db/db_helper.dart';
+import 'package:myapp/events/remote_sync_event.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/models/photo.dart';
 import 'package:myapp/photo_loader.dart';
 import 'package:myapp/photo_loader.dart';
+import 'package:myapp/ui/setup_page.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:myapp/utils/share_util.dart';
 import 'package:myapp/utils/share_util.dart';
 
 
@@ -24,10 +28,25 @@ class GalleryAppBarWidget extends StatefulWidget
 }
 }
 
 
 class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
 class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
+  bool _hasSyncErrors = false;
+
+  @override
+  void initState() {
+    Bus.instance.on<RemoteSyncEvent>().listen((event) {
+      setState(() {
+        _hasSyncErrors = !event.success;
+      });
+    });
+    super.initState();
+  }
+
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     if (widget.selectedPhotos.isEmpty) {
     if (widget.selectedPhotos.isEmpty) {
-      return AppBar(title: Text(widget.title));
+      return AppBar(
+        title: Text(widget.title),
+        actions: _getDefaultActions(context),
+      );
     }
     }
 
 
     return AppBar(
     return AppBar(
@@ -38,11 +57,24 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
         },
         },
       ),
       ),
       title: Text(widget.selectedPhotos.length.toString()),
       title: Text(widget.selectedPhotos.length.toString()),
-      actions: _getActions(context),
+      actions: _getPhotoActions(context),
     );
     );
   }
   }
 
 
-  List<Widget> _getActions(BuildContext context) {
+  List<Widget> _getDefaultActions(BuildContext context) {
+    List<Widget> actions = List<Widget>();
+    if (_hasSyncErrors) {
+      actions.add(IconButton(
+        icon: Icon(Icons.sync_problem),
+        onPressed: () {
+          _openSyncConfiguration(context);
+        },
+      ));
+    }
+    return actions;
+  }
+
+  List<Widget> _getPhotoActions(BuildContext context) {
     List<Widget> actions = List<Widget>();
     List<Widget> actions = List<Widget>();
     if (widget.selectedPhotos.isNotEmpty) {
     if (widget.selectedPhotos.isNotEmpty) {
       actions.add(IconButton(
       actions.add(IconButton(
@@ -119,4 +151,16 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
       widget.onSelectionClear();
       widget.onSelectionClear();
     }
     }
   }
   }
+
+  void _openSyncConfiguration(BuildContext context) {
+    final page = SetupPage();
+    Navigator.of(context).push(
+      MaterialPageRoute(
+        settings: RouteSettings(name: "/setup"),
+        builder: (BuildContext context) {
+          return page;
+        },
+      ),
+    );
+  }
 }
 }

+ 190 - 0
lib/ui/setup_page.dart

@@ -0,0 +1,190 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:logger/logger.dart';
+import 'package:myapp/core/configuration.dart';
+import 'package:myapp/ui/sign_in_widget.dart';
+import 'package:myapp/utils/endpoint_finder.dart';
+
+class SetupPage extends StatefulWidget {
+  SetupPage({key}) : super(key: key);
+
+  @override
+  _SetupPageState createState() => _SetupPageState();
+}
+
+class _SetupPageState extends State<SetupPage> {
+  bool _hasFoundEndpoint = Configuration.instance.getEndpoint() != null;
+  bool _errorFindingEndpoint = false;
+  String _enteredEndpoint = "";
+
+  @override
+  Widget build(BuildContext context) {
+    _hasFoundEndpoint = Configuration.instance.getEndpoint() != null;
+    if (!_hasFoundEndpoint && !_errorFindingEndpoint) {
+      EndpointFinder.instance.findEndpoint().then((endpoint) {
+        setState(() {
+          Configuration.instance.setEndpoint(endpoint);
+        });
+      }).catchError((e) {
+        setState(() {
+          _errorFindingEndpoint = true;
+        });
+      });
+    }
+
+    return Scaffold(
+      appBar: AppBar(
+        title: Text("Setup"),
+      ),
+      body: _getBody(),
+    );
+  }
+
+  Widget _getBody() {
+    if (!_hasFoundEndpoint && !_errorFindingEndpoint) {
+      return _getSearchScreen();
+    } else if (!_hasFoundEndpoint && _errorFindingEndpoint) {
+      return _getManualEndpointEntryScreen();
+    } else {
+      return SignInWidget(() {
+        setState(() {});
+      });
+    }
+  }
+
+  Widget _getManualEndpointEntryScreen() {
+    return Container(
+      margin: EdgeInsets.all(12),
+      child: Column(
+        children: <Widget>[
+          Text("Please enter the IP address of the ente server manually."),
+          TextField(
+            decoration: InputDecoration(
+              hintText: '192.168.1.1',
+              contentPadding: EdgeInsets.all(20),
+            ),
+            autofocus: true,
+            autocorrect: false,
+            onChanged: (value) {
+              setState(() {
+                _enteredEndpoint = value;
+              });
+            },
+          ),
+          CupertinoButton(
+            child: Text("Connect"),
+            onPressed: () async {
+              try {
+                bool success =
+                    await EndpointFinder.instance.ping(_enteredEndpoint);
+                if (success) {
+                  setState(() {
+                    _errorFindingEndpoint = false;
+                    Configuration.instance.setEndpoint(_enteredEndpoint);
+                  });
+                } else {
+                  _showPingErrorDialog();
+                }
+              } catch (e) {
+                _showPingErrorDialog();
+              }
+            },
+          ),
+        ],
+      ),
+    );
+  }
+
+  Center _getSearchScreen() {
+    return Center(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: <Widget>[
+          AnimatedSearchIconWidget(),
+          Text("Searching for ente server..."),
+        ],
+      ),
+    );
+  }
+
+  void _showPingErrorDialog() {
+    showDialog<void>(
+      context: context,
+      barrierDismissible: false, // user must tap button!
+      builder: (BuildContext context) {
+        return CupertinoAlertDialog(
+          title: Text('Connection failed'),
+          content: Padding(
+            padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
+            child: Text(
+                'Please make sure that the server is running and reachable.'),
+          ),
+          actions: <Widget>[
+            CupertinoDialogAction(
+              child: Text('OK'),
+              onPressed: () {
+                Navigator.of(context).pop();
+              },
+            ),
+          ],
+        );
+      },
+    );
+  }
+}
+
+class AnimatedSearchIconWidget extends StatefulWidget {
+  AnimatedSearchIconWidget({
+    Key key,
+  }) : super(key: key);
+
+  @override
+  _AnimatedSearchIconWidgetState createState() =>
+      _AnimatedSearchIconWidgetState();
+}
+
+class _AnimatedSearchIconWidgetState extends State<AnimatedSearchIconWidget>
+    with SingleTickerProviderStateMixin {
+  Animation<double> _animation;
+  AnimationController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller =
+        AnimationController(duration: const Duration(seconds: 1), vsync: this);
+    _animation = Tween<double>(begin: 100, end: 200).animate(_controller)
+      ..addListener(() {
+        setState(() {
+          // The state that has changed here is the animation object’s value.
+        });
+      })
+      ..addStatusListener((status) {
+        if (status == AnimationStatus.completed) {
+          _controller.reverse();
+        } else if (status == AnimationStatus.dismissed) {
+          _controller.forward();
+        }
+      });
+    _controller.forward();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: Icon(
+        Icons.search,
+        size: _animation.value,
+      ),
+      width: 200,
+      height: 200,
+    );
+  }
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    super.dispose();
+  }
+}

+ 109 - 0
lib/ui/sign_in_widget.dart

@@ -0,0 +1,109 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:myapp/core/configuration.dart';
+import 'package:myapp/user_authenticator.dart';
+
+class SignInWidget extends StatefulWidget {
+  final Function() onReconfigurationRequested;
+
+  const SignInWidget(
+    this.onReconfigurationRequested, {
+    Key key,
+  }) : super(key: key);
+
+  @override
+  _SignInWidgetState createState() => _SignInWidgetState();
+}
+
+class _SignInWidgetState extends State<SignInWidget> {
+  String _username, _password;
+  @override
+  void initState() {
+    _username = Configuration.instance.getUsername();
+    _password = Configuration.instance.getPassword();
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+        child: Column(
+      children: <Widget>[
+        TextFormField(
+          initialValue: _username,
+          decoration: InputDecoration(
+            hintText: 'username',
+            contentPadding: EdgeInsets.all(20),
+          ),
+          autofocus: true,
+          autocorrect: false,
+          onChanged: (value) {
+            setState(() {
+              _username = value;
+            });
+          },
+        ),
+        TextFormField(
+          initialValue: _password,
+          decoration: InputDecoration(
+            hintText: 'password',
+            contentPadding: EdgeInsets.all(20),
+          ),
+          autocorrect: false,
+          obscureText: true,
+          onChanged: (value) {
+            setState(() {
+              _password = value;
+            });
+          },
+        ),
+        CupertinoButton(
+          child: Text("Sign In"),
+          onPressed: () async {
+            final loggedIn =
+                await UserAuthenticator.instance.login(_username, _password);
+            if (loggedIn) {
+              Navigator.of(context).pop();
+            } else {
+              _showErrorDialog();
+            }
+          },
+        ),
+      ],
+    ));
+  }
+
+  void _showErrorDialog() {
+    showDialog<void>(
+      context: context,
+      barrierDismissible: false, // user must tap button!
+      builder: (BuildContext context) {
+        return CupertinoAlertDialog(
+          title: Text('Login failed'),
+          content: Padding(
+            padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
+            child: Text(
+                'Please make sure that the credentials entered are correct.'),
+          ),
+          actions: <Widget>[
+            CupertinoDialogAction(
+              child: Text('Reconfigure'),
+              onPressed: () {
+                Navigator.of(context).pop();
+                Configuration.instance.setEndpoint(null);
+                widget.onReconfigurationRequested();
+              },
+            ),
+            CupertinoDialogAction(
+              child: Text('OK'),
+              onPressed: () {
+                Navigator.of(context).pop();
+              },
+            ),
+          ],
+        );
+      },
+    );
+  }
+}

+ 38 - 0
lib/user_authenticator.dart

@@ -0,0 +1,38 @@
+import 'package:dio/dio.dart';
+import 'package:logger/logger.dart';
+import 'package:myapp/core/configuration.dart';
+import 'package:myapp/core/event_bus.dart';
+
+import 'events/user_authenticated_event.dart';
+
+class UserAuthenticator {
+  final _dio = Dio();
+  final _logger = Logger();
+
+  UserAuthenticator._privateConstructor();
+
+  static final UserAuthenticator instance =
+      UserAuthenticator._privateConstructor();
+
+  Future<bool> login(String username, String password) {
+    return _dio.post(
+        "http://" + Configuration.instance.getEndpoint() + ":8080/users/login",
+        queryParameters: {
+          "username": username,
+          "password": password
+        }).then((response) {
+      if (response.statusCode == 200 && response.data != null) {
+        Configuration.instance.setUsername(username);
+        Configuration.instance.setPassword(password);
+        Configuration.instance.setToken(response.data["token"]);
+        Bus.instance.fire(UserAuthenticatedEvent());
+        return true;
+      } else {
+        return false;
+      }
+    }).catchError((e) {
+      _logger.e(e.toString());
+      return false;
+    });
+  }
+}

+ 57 - 0
lib/utils/endpoint_finder.dart

@@ -0,0 +1,57 @@
+import 'dart:async';
+
+import 'package:connectivity/connectivity.dart';
+import 'package:dio/dio.dart';
+import 'package:logger/logger.dart';
+
+class EndpointFinder {
+  final _dio = Dio();
+
+  EndpointFinder._privateConstructor() {
+    _dio.options = BaseOptions(connectTimeout: 200);
+  }
+
+  static final EndpointFinder instance = EndpointFinder._privateConstructor();
+
+  Future<String> findEndpoint() {
+    return (Connectivity().getWifiIP()).then((ip) async {
+      Logger().i(ip);
+      final ipSplit = ip.split(".");
+      var prefix = "";
+      for (int index = 0; index < ipSplit.length; index++) {
+        if (index != ipSplit.length - 1) {
+          prefix += ipSplit[index] + ".";
+        }
+      }
+      Logger().i(prefix);
+
+      for (int i = 1; i <= 10; i++) {
+        var endpoint = prefix + i.toString();
+        if (i == 300) {
+          endpoint = "192.168.0.101";
+        }
+        Logger().i("Trying " + endpoint);
+        try {
+          final success = await ping(endpoint);
+          if (success) {
+            return endpoint;
+          }
+        } catch (e) {
+          // Do nothing
+        }
+      }
+      throw TimeoutException("Could not find a valid endpoint");
+    });
+  }
+
+  Future<bool> ping(String endpoint) async {
+    return _dio.get("http://" + endpoint + ":8080/ping").then((response) {
+      if (response.data["message"] == "pong") {
+        Logger().i("Found " + endpoint);
+        return true;
+      } else {
+        return false;
+      }
+    });
+  }
+}

+ 28 - 0
pubspec.lock

@@ -43,6 +43,27 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.14.11"
     version: "1.14.11"
+  connectivity:
+    dependency: "direct main"
+    description:
+      name: connectivity
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.8+2"
+  connectivity_macos:
+    dependency: transitive
+    description:
+      name: connectivity_macos
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.0+2"
+  connectivity_platform_interface:
+    dependency: transitive
+    description:
+      name: connectivity_platform_interface
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.5"
   convert:
   convert:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -85,6 +106,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.0.2"
     version: "1.0.2"
+  event_bus:
+    dependency: "direct main"
+    description:
+      name: event_bus
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
   flutter:
   flutter:
     dependency: "direct main"
     dependency: "direct main"
     description: flutter
     description: flutter

+ 2 - 0
pubspec.yaml

@@ -39,6 +39,8 @@ dependencies:
   photo_view: ^0.9.2
   photo_view: ^0.9.2
   flutter_image_compress: ^0.6.5+1
   flutter_image_compress: ^0.6.5+1
   visibility_detector: ^0.1.4
   visibility_detector: ^0.1.4
+  connectivity: ^0.4.8+2
+  event_bus: ^1.1.1
 
 
 dev_dependencies:
 dev_dependencies:
   flutter_test:
   flutter_test: