Add endpoint discovery mechanism
This commit is contained in:
parent
2d3c821932
commit
d228f7278f
13 changed files with 572 additions and 36 deletions
49
lib/core/configuration.dart
Normal file
49
lib/core/configuration.dart
Normal file
|
@ -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
lib/core/event_bus.dart
Normal file
5
lib/core/event_bus.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:event_bus/event_bus.dart';
|
||||
|
||||
class Bus {
|
||||
static final EventBus instance = EventBus();
|
||||
}
|
5
lib/events/remote_sync_event.dart
Normal file
5
lib/events/remote_sync_event.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
class RemoteSyncEvent {
|
||||
final bool success;
|
||||
|
||||
RemoteSyncEvent(this.success);
|
||||
}
|
1
lib/events/user_authenticated_event.dart
Normal file
1
lib/events/user_authenticated_event.dart
Normal file
|
@ -0,0 +1 @@
|
|||
class UserAuthenticatedEvent {}
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:myapp/core/configuration.dart';
|
||||
import 'package:myapp/photo_loader.dart';
|
||||
import 'package:myapp/photo_provider.dart';
|
||||
import 'package:myapp/photo_sync_manager.dart';
|
||||
import 'package:myapp/ui/home_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -9,9 +9,8 @@ import 'package:provider/provider.dart';
|
|||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(MyApp());
|
||||
PhotoProvider.instance
|
||||
.refreshGalleryList()
|
||||
.then((_) => PhotoSyncManager.instance.load(PhotoProvider.instance.list));
|
||||
Configuration.instance.init();
|
||||
PhotoSyncManager.instance.sync();
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
||||
|
@ -33,9 +32,7 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
|||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
PhotoProvider.instance.refreshGalleryList().then((_) {
|
||||
return PhotoSyncManager.instance.load(PhotoProvider.instance.list);
|
||||
});
|
||||
PhotoSyncManager.instance.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:myapp/core/event_bus.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_provider.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.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/core/constants.dart' as Constants;
|
||||
|
||||
import 'events/remote_sync_event.dart';
|
||||
|
||||
class PhotoSyncManager {
|
||||
final _logger = Logger();
|
||||
final _dio = Dio();
|
||||
bool _isLoadInProgress = false;
|
||||
bool _isSyncInProgress = false;
|
||||
|
||||
static final _lastSyncTimestampKey = "last_sync_timestamp_0";
|
||||
static final _lastDBUpdateTimestampKey = "last_db_update_timestamp";
|
||||
|
||||
PhotoSyncManager._privateConstructor();
|
||||
PhotoSyncManager._privateConstructor() {
|
||||
Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
|
||||
sync();
|
||||
});
|
||||
}
|
||||
|
||||
static final PhotoSyncManager instance =
|
||||
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;
|
||||
}
|
||||
_isLoadInProgress = true;
|
||||
_logger.i("Loading...");
|
||||
_isSyncInProgress = true;
|
||||
_logger.i("Syncing...");
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
var lastDBUpdateTimestamp = prefs.getInt(_lastDBUpdateTimestampKey);
|
||||
if (lastDBUpdateTimestamp == null) {
|
||||
|
@ -38,6 +49,8 @@ class PhotoSyncManager {
|
|||
await _initializeDirectories();
|
||||
}
|
||||
|
||||
await PhotoProvider.instance.refreshGalleryList();
|
||||
final pathEntities = PhotoProvider.instance.list;
|
||||
final photos = List<Photo>();
|
||||
for (AssetPathEntity pathEntity in pathEntities) {
|
||||
if (Platform.isIOS || pathEntity.name != "Recent") {
|
||||
|
@ -56,16 +69,15 @@ class PhotoSyncManager {
|
|||
}
|
||||
}
|
||||
if (photos.isEmpty) {
|
||||
_isLoadInProgress = false;
|
||||
return;
|
||||
_isSyncInProgress = false;
|
||||
_syncPhotos().then((_) {
|
||||
_deletePhotos();
|
||||
});
|
||||
} else {
|
||||
photos.sort((first, second) =>
|
||||
first.createTimestamp.compareTo(second.createTimestamp));
|
||||
_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());
|
||||
|
||||
_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.
|
||||
|
@ -121,7 +135,7 @@ class PhotoSyncManager {
|
|||
var localPath = path + basename(photo.remotePath);
|
||||
await _dio
|
||||
.download(Constants.ENDPOINT + "/" + photo.remotePath, localPath)
|
||||
.catchError(_onError);
|
||||
.catchError((e) => _logger.e(e));
|
||||
// TODO: Save path
|
||||
photo.pathName = localPath;
|
||||
await DatabaseHelper.instance.insertPhoto(photo);
|
||||
|
@ -135,14 +149,15 @@ class PhotoSyncManager {
|
|||
queryParameters: {
|
||||
"user": Constants.USER,
|
||||
"lastSyncTimestamp": lastSyncTimestamp
|
||||
}).catchError(_onError);
|
||||
_logger.i(response.toString());
|
||||
}).catchError((e) => _logger.e(e));
|
||||
if (response != null) {
|
||||
Bus.instance.fire(RemoteSyncEvent(true));
|
||||
return (response.data["diff"] as List)
|
||||
.map((photo) => new Photo.fromJson(photo))
|
||||
.toList();
|
||||
} else {
|
||||
return List<Photo>();
|
||||
Bus.instance.fire(RemoteSyncEvent(false));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,7 +173,7 @@ class PhotoSyncManager {
|
|||
_logger.i(response.toString());
|
||||
var photo = Photo.fromJson(response.data);
|
||||
return photo;
|
||||
}).catchError(_onError);
|
||||
}).catchError((e) => _logger.e(e));
|
||||
}
|
||||
|
||||
Future<void> _deletePhotos() async {
|
||||
|
@ -174,11 +189,7 @@ class PhotoSyncManager {
|
|||
return _dio.post(Constants.ENDPOINT + "/delete", queryParameters: {
|
||||
"user": Constants.USER,
|
||||
"fileID": photo.uploadedFileId
|
||||
}).catchError((e) => _onError(e));
|
||||
}
|
||||
|
||||
void _onError(error) {
|
||||
_logger.e(error);
|
||||
}).catchError((e) => _logger.e(e));
|
||||
}
|
||||
|
||||
Future _initializeDirectories() async {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import 'package:flutter/cupertino.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/events/remote_sync_event.dart';
|
||||
import 'package:myapp/models/photo.dart';
|
||||
import 'package:myapp/photo_loader.dart';
|
||||
import 'package:myapp/ui/setup_page.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
import 'package:myapp/utils/share_util.dart';
|
||||
|
||||
|
@ -24,10 +28,25 @@ class GalleryAppBarWidget extends StatefulWidget
|
|||
}
|
||||
|
||||
class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
||||
bool _hasSyncErrors = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Bus.instance.on<RemoteSyncEvent>().listen((event) {
|
||||
setState(() {
|
||||
_hasSyncErrors = !event.success;
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.selectedPhotos.isEmpty) {
|
||||
return AppBar(title: Text(widget.title));
|
||||
return AppBar(
|
||||
title: Text(widget.title),
|
||||
actions: _getDefaultActions(context),
|
||||
);
|
||||
}
|
||||
|
||||
return AppBar(
|
||||
|
@ -38,11 +57,24 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
|||
},
|
||||
),
|
||||
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>();
|
||||
if (widget.selectedPhotos.isNotEmpty) {
|
||||
actions.add(IconButton(
|
||||
|
@ -119,4 +151,16 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
|||
widget.onSelectionClear();
|
||||
}
|
||||
}
|
||||
|
||||
void _openSyncConfiguration(BuildContext context) {
|
||||
final page = SetupPage();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
settings: RouteSettings(name: "/setup"),
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
190
lib/ui/setup_page.dart
Normal file
190
lib/ui/setup_page.dart
Normal file
|
@ -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
lib/ui/sign_in_widget.dart
Normal file
109
lib/ui/sign_in_widget.dart
Normal file
|
@ -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
lib/user_authenticator.dart
Normal file
38
lib/user_authenticator.dart
Normal file
|
@ -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
lib/utils/endpoint_finder.dart
Normal file
57
lib/utils/endpoint_finder.dart
Normal file
|
@ -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
pubspec.lock
28
pubspec.lock
|
@ -43,6 +43,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -85,6 +106,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
|
|
@ -39,6 +39,8 @@ dependencies:
|
|||
photo_view: ^0.9.2
|
||||
flutter_image_compress: ^0.6.5+1
|
||||
visibility_detector: ^0.1.4
|
||||
connectivity: ^0.4.8+2
|
||||
event_bus: ^1.1.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Add table
Reference in a new issue