Documentation, Basic Login Workflow

This commit is contained in:
Evan Reichard 2021-02-20 14:10:25 -05:00
parent fec590b16e
commit 5212d7bf70
27 changed files with 1327 additions and 341 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
imagini.db
media/
notes notes
web/node_modules/ web/node_modules/
web/dist/ web/dist/

View File

@ -1,22 +1,32 @@
#uImagini # Imagini
A self hosted photo library with user management & authentication. Cross platform Client supporting Android, iOS, and Web.
## Running Server ## Server
# cd ./cmd/ ### Running
CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run main.go serve
## Generate GraphQL Models CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run cmd/main.go serve
# cd ./cmd/
## Building
# Generate GraphQL Models
go run github.com/99designs/gqlgen generate go run github.com/99designs/gqlgen generate
go run main.go generate go run cmd/main.go generate
## Generate GraphQL Documentation # Generate GraphQL Documentation
# From app root
graphdoc -e http://localhost:8484/query -o ./docs/schema graphdoc -e http://localhost:8484/query -o ./docs/schema
## Server Build ## Client
See `web_native` subfolder.
### Running
## Flutter Build # Chrome
flutter run -d chrome
# Simulator
open -a Simulator
flutter run flutter run
## Generate GraphQL Flutter Models ### Building
# Generate GraphQL Flutter Models
flutter pub run build_runner build flutter pub run build_runner build

View File

@ -41,35 +41,46 @@ func (api *API) refreshTokens(refreshToken jwt.Token) (string, string, error) {
} }
// Update Access Token // Update Access Token
accessTokenCookie, err := api.Auth.CreateJWTAccessToken(user, device) accessToken, err := api.Auth.CreateJWTAccessToken(user, device)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return accessTokenCookie, "", err return accessToken, "", err
} }
func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Token, error) { func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Token, error) {
// TODO: Check from X-Imagini-AccessToken accessTokenHeader := r.Header.Get("X-Imagini-AccessToken")
// TODO: Check from X-Imagini-RefreshToken if accessTokenHeader != "" {
accessToken, err := api.Auth.ValidateJWTAccessToken(accessTokenHeader)
// Validate Access Token
accessCookie, _ := r.Cookie("AccessToken")
if accessCookie != nil {
accessToken, err := api.Auth.ValidateJWTAccessToken(accessCookie.Value)
if err == nil { if err == nil {
return accessToken, nil return accessToken, nil
} }
} }
// Validate Refresh Cookie Exists refreshTokenHeader := r.Header.Get("X-Imagini-RefreshToken")
refreshCookie, _ := r.Cookie("RefreshToken") if refreshTokenHeader == "" {
if refreshCookie == nil {
return nil, errors.New("Tokens Invalid") return nil, errors.New("Tokens Invalid")
} }
// Validate Access Token
// accessCookie, _ := r.Cookie("AccessToken")
// if accessCookie != nil {
// accessToken, err := api.Auth.ValidateJWTAccessToken(accessCookie.Value)
// if err == nil {
// return accessToken, nil
// }
// }
// Validate Refresh Cookie Exists
// refreshCookie, _ := r.Cookie("RefreshToken")
// if refreshCookie == nil {
// return nil, errors.New("Tokens Invalid")
// }
// Validate Refresh Token // Validate Refresh Token
refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value) // refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value)
refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshTokenHeader)
if err != nil { if err != nil {
return nil, errors.New("Tokens Invalid") return nil, errors.New("Tokens Invalid")
} }
@ -81,21 +92,21 @@ func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Tok
} }
// TODO: Actually Refresh Refresh Token // TODO: Actually Refresh Refresh Token
newRefreshToken = refreshCookie.Value // newRefreshToken = refreshCookie.Value
newRefreshToken = refreshTokenHeader
// Set appropriate cookies (TODO: Only for web!) // Set appropriate cookies (TODO: Only for web!)
// Update Access & Refresh Cookies // Update Access & Refresh Cookies
http.SetCookie(*w, &http.Cookie{ // http.SetCookie(*w, &http.Cookie{
Name: "AccessToken", // Name: "AccessToken",
Value: newAccessToken, // Value: newAccessToken,
}) // })
http.SetCookie(*w, &http.Cookie{ // http.SetCookie(*w, &http.Cookie{
Name: "RefreshToken", // Name: "RefreshToken",
Value: newRefreshToken, // Value: newRefreshToken,
}) // })
// Only for iOS & Android (TODO: Remove for web! Only cause affected by CORS during development)
(*w).Header().Set("X-Imagini-AccessToken", newAccessToken) (*w).Header().Set("X-Imagini-AccessToken", newAccessToken)
(*w).Header().Set("X-Imagini-RefreshToken", newRefreshToken) (*w).Header().Set("X-Imagini-RefreshToken", newRefreshToken)

View File

@ -1,16 +1,16 @@
# imagini # Imagini Client
A cross platform (iOS, Android, & Web) client used with the Imagini server.
A new Flutter project. ## Running
## Getting Started # Chrome
flutter run -d chrome
This project is a starting point for a Flutter application. # Simulator
open -a Simulator
flutter run
A few resources to get you started if this is your first Flutter project: ## Building
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) # Generate GraphQL Flutter Models
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) flutter pub run build_runner build
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,18 @@
query mediaItems($order: Order, $page: Page, $filter: MediaItemFilter) {
mediaItems(filter: $filter, page: $page, order: $order) {
data {
id
fileName
latitude
longitude
isVideo
origName
createdAt
}
page {
size
page
total
}
}
}

View File

@ -1,4 +1,7 @@
PODS: PODS:
- connectivity (0.0.1):
- Flutter
- Reachability
- DKImagePickerController/Core (4.3.2): - DKImagePickerController/Core (4.3.2):
- DKImagePickerController/ImageDataManager - DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource - DKImagePickerController/Resource
@ -38,6 +41,9 @@ PODS:
- Flutter - Flutter
- integration_test (0.0.1): - integration_test (0.0.1):
- Flutter - Flutter
- path_provider (0.0.1):
- Flutter
- Reachability (3.2)
- SDWebImage (5.10.2): - SDWebImage (5.10.2):
- SDWebImage/Core (= 5.10.2) - SDWebImage/Core (= 5.10.2)
- SDWebImage/Core (5.10.2) - SDWebImage/Core (5.10.2)
@ -48,10 +54,12 @@ PODS:
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- connectivity (from `.symlinks/plugins/connectivity/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
@ -59,10 +67,13 @@ SPEC REPOS:
trunk: trunk:
- DKImagePickerController - DKImagePickerController
- DKPhotoGallery - DKPhotoGallery
- Reachability
- SDWebImage - SDWebImage
- SwiftyGif - SwiftyGif
EXTERNAL SOURCES: EXTERNAL SOURCES:
connectivity:
:path: ".symlinks/plugins/connectivity/ios"
file_picker: file_picker:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
@ -71,18 +82,23 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios" :path: ".symlinks/plugins/flutter_secure_storage/ios"
integration_test: integration_test:
:path: ".symlinks/plugins/integration_test/ios" :path: ".symlinks/plugins/integration_test/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
shared_preferences: shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios" :path: ".symlinks/plugins/shared_preferences/ios"
url_launcher: url_launcher:
:path: ".symlinks/plugins/url_launcher/ios" :path: ".symlinks/plugins/url_launcher/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
integration_test: 5ed24a436eb7ec17b6a13046e9bf7ca4a404e59e integration_test: 6eb66a19f7104200dcfdd62bc0077e1b09686e4f
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: b969dcfc02c40a5da71eac0b03b8f1a0c794a86f SDWebImage: b969dcfc02c40a5da71eac0b03b8f1a0c794a86f
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40 SwiftyGif: e466e86c660d343357ab944a819a101c4127cb40

View File

@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>

View File

@ -1,61 +1,51 @@
import 'dart:async'; import 'dart:async';
// ignore: uri_does_not_exist import 'package:imagini/api/cookie_client/cookie_client.dart'
import 'cookie_client_stub.dart' if (dart.library.html) 'package:imagini/api/cookie_client/browser_cookie_client.dart'
// ignore: uri_does_not_exist if (dart.library.io) 'package:imagini/api/cookie_client/io_cookie_client.dart';
if (dart.library.html) 'browser_cookie_client.dart'
// ignore: uri_does_not_exist
if (dart.library.io) 'io_cookie_client.dart';
import 'package:meta/meta.dart'; import 'package:imagini/core/storage_client/base_storage_client.dart';
import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:imagini/graphql/imagini_graphql.dart'; import 'package:imagini/graphql/imagini_graphql.dart';
class APIProvider{ class APIProvider{
String _server, _accessToken, _refreshToken;
GraphQLClient _client;
HttpLink httpLink;
// CookieLink cookieLink;
static const String _GRAPHQL_ENDPOINT = "/query"; static const String _GRAPHQL_ENDPOINT = "/query";
APIProvider({ BaseStorageClient _storage;
@required String server, GraphQLClient _client;
String accessToken, HttpLink httpLink;
String refreshToken
}) { APIProvider(BaseStorageClient storage) {
_server = server; _storage = storage;
_accessToken = accessToken; init();
_refreshToken = refreshToken; }
Future<void> init() async {
String _server = await _storage.get("server");
httpLink = HttpLink(_server + _GRAPHQL_ENDPOINT, httpLink = HttpLink(_server + _GRAPHQL_ENDPOINT,
httpClient: getCookieClient(), httpClient: getCookieClient(_storage),
); );
// cookieLink = CookieLink(_updateAccessToken, _updateRefreshToken);
_client = GraphQLClient( _client = GraphQLClient(
cache: GraphQLCache(), cache: GraphQLCache(),
link: httpLink, link: httpLink,
); );
} }
// void _updateAccessToken(_accessToken) { Future<QueryResult> login(
// print("Updating Access Token: $_accessToken");
// this._accessToken = _accessToken;
// }
// void _updateRefreshToken(_refreshToken) {
// print("Updating Refresh Token: $_accessToken");
// this._refreshToken = _refreshToken;
// }
Future<Login$Query$AuthResponse> login([
String username, String username,
String password, String password,
]) async { String server,
) async {
assert( assert(
(username != null && password != null) (username != null && password != null && server != null)
); );
// Initialize Connection
await _storage.set("server", server);
await init();
QueryResult response = await _client.query( QueryResult response = await _client.query(
QueryOptions( QueryOptions(
document: LoginQuery().document, document: LoginQuery().document,
@ -65,79 +55,27 @@ class APIProvider{
}, },
) )
); );
return response;
final loginResponse = Login$Query.fromJson(response.data);
return loginResponse.login;
} }
Future<Me$Query$User> me() async { Future<QueryResult> me() async {
QueryResult response = await _client.query( QueryResult response = await _client.query(
QueryOptions( QueryOptions(
document: MeQuery().document, document: MeQuery().document,
) )
); );
return response;
final meResponse = Me$Query.fromJson(response.data);
return meResponse.me;
} }
Future<String> mediaItems([ Future<QueryResult> mediaItems() async {
String startDate, QueryResult response = await _client.query(
String endDate, QueryOptions(
String albumID, document: MediaItemsQuery().document,
List<String> tagID, )
String type, // TODO: Make enum );
int page,
]) async {
// Query:
// /api/v1/MediaItems
// Derive Params:
// startDate:
// &createdAt=>2020-10-10T10:10:10
// endDate:
// &createdAt=<2020-10-10T10:10:10
// albumID:
// &albumID=9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
// tagID:
// &tagID=9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d,9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
// type:
// &type=Photos
// &type=Videos
// page:
// &page=4
// Returns: print(response);
// { return response;
//
// }
return null;
}
Future<String> tags([
int page
]) async {
// Query:
// /api/v1/Tags
// Derive Params:
// page:
// &page=4
return null;
}
Future<String> albums([
int page
]) async {
// Query:
// /api/v1/Albums
// Derive Params:
// page:
// &page=4
return null;
}
Future<String> me() async {
return null;
} }
void dispose() {} void dispose() {}

View File

@ -1,30 +0,0 @@
import 'package:http/browser_client.dart';
import "package:http/http.dart";
BaseClient getCookieClient() => ClientWithCookies();
class ClientWithCookies extends BrowserClient {
String _accessToken = "asdasdasd";
String _refreshToken;
@override
Future<StreamedResponse> send(BaseRequest request) async {
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) {
if (response.headers.containsKey("x-imagini-accesstoken")) {
this._accessToken = response.headers["x-imagini-accesstoken"];
}
if (response.headers.containsKey("x-imagini-refreshtoken")) {
this._refreshToken = response.headers["x-imagini-refreshtoken"];
}
print("Access Token: $_accessToken");
print("Refresh Token: $_refreshToken");
return response;
});
}
}

View File

@ -0,0 +1,36 @@
import 'package:http/browser_client.dart';
import "package:http/http.dart";
import "package:imagini/core/storage_client/base_storage_client.dart";
BaseClient getCookieClient(storage) => ClientWithCookies(storage);
class ClientWithCookies extends BrowserClient {
BaseStorageClient _storage;
ClientWithCookies(BaseStorageClient storage) {
_storage = storage;
}
@override
Future<StreamedResponse> send(BaseRequest request) async {
String _accessToken = await _storage.get("accessToken");
String _refreshToken = await _storage.get("refreshToken");
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) async {
// We've been told to update our access token
if (response.headers.containsKey("x-imagini-accesstoken")) {
await _storage.set("accessToken", response.headers["x-imagini-accesstoken"]);
}
// We've been told to update our refresh token
if (response.headers.containsKey("x-imagini-refreshtoken")) {
await _storage.set("refreshToken", response.headers["x-imagini-refreshtoken"]);
}
return response;
});
}
}

View File

@ -1,4 +1,4 @@
import 'package:http/http.dart'; import 'package:http/http.dart';
BaseClient getCookieClient() => throw UnsupportedError( BaseClient getCookieClient(storage) => throw UnsupportedError(
'Cannot create a client without dart:html or dart:io.'); 'Cannot create a client without dart:html or dart:io.');

View File

@ -0,0 +1,43 @@
import 'package:http/io_client.dart';
import "package:http/http.dart";
import "package:imagini/core/storage_client/base_storage_client.dart";
BaseClient getCookieClient(storage) => IOClientWithCookies(storage);
class IOClientWithCookies extends IOClient {
BaseStorageClient _storage;
IOClientWithCookies(BaseStorageClient storage) {
_storage = storage;
}
@override
Future<IOStreamedResponse> send(BaseRequest request) async {
String _accessToken = await _storage.get("accessToken");
String _refreshToken = await _storage.get("refreshToken");
request.headers.addAll({
'X-Imagini-AccessToken': _accessToken,
'X-Imagini-RefreshToken': _refreshToken,
});
return super.send(request).then((response) async {
// We've been told to update our access token
if (response.headers.containsKey("x-imagini-accesstoken")) {
await _storage.set("accessToken", response.headers["x-imagini-accesstoken"]);
}
// We've been told to update our refresh token
if (response.headers.containsKey("x-imagini-refreshtoken")) {
await _storage.set("refreshToken", response.headers["x-imagini-refreshtoken"]);
}
_accessToken = await _storage.get("accessToken");
_refreshToken = await _storage.get("refreshToken");
print("Access Token: $_accessToken");
print("Refresh Token: $_refreshToken");
return response;
});
}
}

View File

@ -1,12 +1,35 @@
import 'dart:async';
import 'package:imagini/api/api_provider.dart'; import 'package:imagini/api/api_provider.dart';
import 'package:imagini/graphql/imagini_graphql.dart'; import 'package:imagini/graphql/imagini_graphql.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
class ImaginiAPIRepository { class ImaginiAPIRepository {
APIProvider _apiProvider; APIProvider _apiProvider;
ImaginiAPIRepository(this._apiProvider); ImaginiAPIRepository(this._apiProvider);
Stream<Login$Query$AuthResponse> login(String user, password) { Stream<bool> login(String user, password, server) {
return Stream.fromFuture(_apiProvider.login(user, password)); return Stream.fromFuture(_apiProvider.login(user, password, server).then((QueryResult resp) {
if (resp.exception != null)
return false;
final loginResponse = Login$Query.fromJson(resp.data);
if (loginResponse.login.result == AuthResult.failure)
return false;
return true;
}));
}
Stream<QueryResult> me() {
return Stream.fromFuture(_apiProvider.me());
}
Stream<bool> isAuthenticated() {
return Stream.fromFuture(_apiProvider.me().then((QueryResult resp) {
if (resp.exception != null)
return false;
return true;
}));
} }
} }

View File

@ -1,28 +0,0 @@
import 'package:http/io_client.dart';
import "package:http/http.dart";
BaseClient getCookieClient() => IOClientWithCookies();
class IOClientWithCookies extends IOClient {
String _accessToken;
String _refreshToken;
@override
Future<IOStreamedResponse> send(BaseRequest request) async {
// String cookie = await getCookie();
// String getCookieString(String _) => cookie;
// request.headers.update('cookie', getCookieString);
return super.send(request).then((response) {
if (response.headers.containsKey("x-imagini-accesstoken")) {
this._accessToken = response.headers["x-imagini-accesstoken"];
}
if (response.headers.containsKey("x-imagini-refreshtoken")) {
this._refreshToken = response.headers["x-imagini-refreshtoken"];
}
print("Access Token: $_accessToken");
print("Refresh Token: $_refreshToken");
return response;
});
}
}

View File

@ -0,0 +1,40 @@
import 'dart:async';
import 'package:imagini/core/imagini_application.dart';
import 'package:imagini/api/imagini_api_repository.dart';
class LoginBloc{
final ImaginiApplication _application;
ImaginiAPIRepository _imaginiAPI;
final _loginController = StreamController<bool>.broadcast();
Stream<bool> get loginResult => _loginController.stream;
final _authenticatedController = StreamController<bool>.broadcast();
Stream<bool> get authenticatedResult => _authenticatedController.stream;
LoginBloc(this._application){
_init();
}
void _init(){
_imaginiAPI = _application.imaginiAPI;
checkAuthentication();
// attemptLogin("admin", "admin", "http://localhost:8484");
}
void dispose(){
_loginController.close();
_authenticatedController.close();
}
checkAuthentication(){
_authenticatedController.addStream(_imaginiAPI.isAuthenticated());
}
attemptLogin(String username, password, server){
_loginController.addStream(_imaginiAPI.login(username, password, server));
}
}

View File

@ -1,36 +0,0 @@
import 'dart:async';
import 'package:imagini/core/imagini_application.dart';
import 'package:imagini/api/imagini_api_repository.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class SplashBloc{
final ImaginiApplication _application;
final _loginController = StreamController<Login$Query$AuthResponse>();
Stream<Login$Query$AuthResponse> get loginResult => _loginController.stream;
SplashBloc(this._application){
_init();
}
void _init(){
// Do Initial Load
initializeLogin();
}
void dispose(){
_loginController.close();
}
initializeLogin(){
ImaginiAPIRepository imaginiAPI = _application.imaginiAPI;
// TODO: This should actually attempt to load the existing Tokens, not login
_loginController.addStream(imaginiAPI.login("admin", "admin"));
// imaginiAPI.login("admin", "admin1").listen((LoginResponse lr) {
// });
}
}

View File

@ -24,7 +24,6 @@ class AppComponentState extends State<AppComponent> {
@override @override
void dispose() async { void dispose() async {
// Log.info('dispose');
super.dispose(); super.dispose();
await _application.onTerminate(); await _application.onTerminate();
} }

View File

@ -3,13 +3,13 @@ import 'package:flutter/material.dart';
import 'package:imagini/screens/home_screen.dart'; import 'package:imagini/screens/home_screen.dart';
import 'package:imagini/screens/login_screen.dart'; import 'package:imagini/screens/login_screen.dart';
import 'package:imagini/screens/splash_screen.dart'; // import 'package:imagini/screens/splash_screen.dart';
var splashHandler = new Handler( // var splashHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) { // handlerFunc: (BuildContext context, Map<String, List<String>> params) {
return SplashScreen(); // return SplashScreen();
} // }
); // );
var loginHandler = new Handler( var loginHandler = new Handler(
handlerFunc: (BuildContext context, Map<String, List<String>> params) { handlerFunc: (BuildContext context, Map<String, List<String>> params) {
@ -42,7 +42,7 @@ class AppRoutes {
return; return;
} }
); );
router.define(SplashScreen.PATH, handler: splashHandler); // router.define(SplashScreen.PATH, handler: splashHandler);
router.define(LoginScreen.PATH, handler: loginHandler); router.define(LoginScreen.PATH, handler: loginHandler);
router.define(HomeScreen.PATH, handler: homeHandler); router.define(HomeScreen.PATH, handler: homeHandler);
// router.define(AppDetailPage.PATH, handler: appDetailRouteHandler); // router.define(AppDetailPage.PATH, handler: appDetailRouteHandler);

View File

@ -3,13 +3,20 @@ import 'package:fluro/fluro.dart';
import 'package:imagini/core/app_routes.dart'; import 'package:imagini/core/app_routes.dart';
import 'package:imagini/api/api_provider.dart'; import 'package:imagini/api/api_provider.dart';
import 'package:imagini/api/imagini_api_repository.dart'; import 'package:imagini/api/imagini_api_repository.dart';
import 'package:imagini/core/storage_client/base_storage_client.dart';
import 'package:imagini/core/storage_client/storage_client.dart'
if (dart.library.html) 'package:imagini/core/storage_client/browser_storage_client.dart'
if (dart.library.io) 'package:imagini/core/storage_client/mobile_storage_client.dart';
class ImaginiApplication { class ImaginiApplication {
FluroRouter router; FluroRouter router;
ImaginiAPIRepository imaginiAPI; ImaginiAPIRepository imaginiAPI;
BaseStorageClient storageClient;
Future<void> onCreate() async { Future<void> onCreate() async {
_initRouter(); _initRouter();
_initStorageClient();
_initAPIRepository(); _initAPIRepository();
} }
@ -20,8 +27,13 @@ class ImaginiApplication {
AppRoutes.configureRoutes(router); AppRoutes.configureRoutes(router);
} }
_initStorageClient() {
storageClient = getStorageClient();
}
_initAPIRepository() { _initAPIRepository() {
APIProvider apiProvider = new APIProvider(server: "http://localhost:8484"); // TODO: Get from config
APIProvider apiProvider = new APIProvider(storageClient);
imaginiAPI = ImaginiAPIRepository(apiProvider); imaginiAPI = ImaginiAPIRepository(apiProvider);
} }
} }

View File

@ -0,0 +1,4 @@
abstract class BaseStorageClient {
Future<String> get(String key);
Future<void> set(String key, String value);
}

View File

@ -0,0 +1,19 @@
import 'dart:html';
import './base_storage_client.dart';
BaseStorageClient getStorageClient() => BrowserStorageClient();
class BrowserStorageClient extends BaseStorageClient {
@override
Future<String> get(String key) async {
var requestedValue = window.localStorage.containsKey(key) ? window.localStorage[key] : "";
return requestedValue;
}
@override
Future<void> set(String key, String value) async {
window.localStorage[key] = value;
}
}

View File

@ -0,0 +1,19 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import './base_storage_client.dart';
BaseStorageClient getStorageClient() => MobileStorageClient();
class MobileStorageClient extends BaseStorageClient {
final storage = new FlutterSecureStorage();
@override
Future<String> get(String key) async {
return storage.read(key: key);
}
@override
Future<void> set(String key, String value) async {
return storage.write(key: key, value: value);
}
}

View File

@ -0,0 +1,4 @@
import './base_storage_client.dart';
BaseStorageClient getStorageClient() => throw UnsupportedError(
'Cannot create a storage client.');

View File

@ -170,6 +170,362 @@ class Me$Query with EquatableMixin {
Map<String, dynamic> toJson() => _$Me$QueryToJson(this); Map<String, dynamic> toJson() => _$Me$QueryToJson(this);
} }
@JsonSerializable(explicitToJson: true)
class MediaItems$Query$MediaItemResponse$MediaItem with EquatableMixin {
MediaItems$Query$MediaItemResponse$MediaItem();
factory MediaItems$Query$MediaItemResponse$MediaItem.fromJson(
Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponse$MediaItemFromJson(json);
String id;
String fileName;
double latitude;
double longitude;
bool isVideo;
String origName;
DateTime createdAt;
@override
List<Object> get props =>
[id, fileName, latitude, longitude, isVideo, origName, createdAt];
Map<String, dynamic> toJson() =>
_$MediaItems$Query$MediaItemResponse$MediaItemToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItems$Query$MediaItemResponse$PageResponse with EquatableMixin {
MediaItems$Query$MediaItemResponse$PageResponse();
factory MediaItems$Query$MediaItemResponse$PageResponse.fromJson(
Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponse$PageResponseFromJson(json);
int size;
int page;
int total;
@override
List<Object> get props => [size, page, total];
Map<String, dynamic> toJson() =>
_$MediaItems$Query$MediaItemResponse$PageResponseToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItems$Query$MediaItemResponse with EquatableMixin {
MediaItems$Query$MediaItemResponse();
factory MediaItems$Query$MediaItemResponse.fromJson(
Map<String, dynamic> json) =>
_$MediaItems$Query$MediaItemResponseFromJson(json);
List<MediaItems$Query$MediaItemResponse$MediaItem> data;
MediaItems$Query$MediaItemResponse$PageResponse page;
@override
List<Object> get props => [data, page];
Map<String, dynamic> toJson() =>
_$MediaItems$Query$MediaItemResponseToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItems$Query with EquatableMixin {
MediaItems$Query();
factory MediaItems$Query.fromJson(Map<String, dynamic> json) =>
_$MediaItems$QueryFromJson(json);
MediaItems$Query$MediaItemResponse mediaItems;
@override
List<Object> get props => [mediaItems];
Map<String, dynamic> toJson() => _$MediaItems$QueryToJson(this);
}
@JsonSerializable(explicitToJson: true)
class TimeFilter with EquatableMixin {
TimeFilter(
{this.equalTo,
this.notEqualTo,
this.lessThan,
this.lessThanOrEqualTo,
this.greaterThan,
this.greaterThanOrEqualTo});
factory TimeFilter.fromJson(Map<String, dynamic> json) =>
_$TimeFilterFromJson(json);
DateTime equalTo;
DateTime notEqualTo;
DateTime lessThan;
DateTime lessThanOrEqualTo;
DateTime greaterThan;
DateTime greaterThanOrEqualTo;
@override
List<Object> get props => [
equalTo,
notEqualTo,
lessThan,
lessThanOrEqualTo,
greaterThan,
greaterThanOrEqualTo
];
Map<String, dynamic> toJson() => _$TimeFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class FloatFilter with EquatableMixin {
FloatFilter(
{this.equalTo,
this.notEqualTo,
this.lessThan,
this.lessThanOrEqualTo,
this.greaterThan,
this.greaterThanOrEqualTo});
factory FloatFilter.fromJson(Map<String, dynamic> json) =>
_$FloatFilterFromJson(json);
double equalTo;
double notEqualTo;
double lessThan;
double lessThanOrEqualTo;
double greaterThan;
double greaterThanOrEqualTo;
@override
List<Object> get props => [
equalTo,
notEqualTo,
lessThan,
lessThanOrEqualTo,
greaterThan,
greaterThanOrEqualTo
];
Map<String, dynamic> toJson() => _$FloatFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class BooleanFilter with EquatableMixin {
BooleanFilter({this.equalTo, this.notEqualTo});
factory BooleanFilter.fromJson(Map<String, dynamic> json) =>
_$BooleanFilterFromJson(json);
bool equalTo;
bool notEqualTo;
@override
List<Object> get props => [equalTo, notEqualTo];
Map<String, dynamic> toJson() => _$BooleanFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class IDFilter with EquatableMixin {
IDFilter({this.equalTo, this.notEqualTo});
factory IDFilter.fromJson(Map<String, dynamic> json) =>
_$IDFilterFromJson(json);
String equalTo;
String notEqualTo;
@override
List<Object> get props => [equalTo, notEqualTo];
Map<String, dynamic> toJson() => _$IDFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class StringFilter with EquatableMixin {
StringFilter(
{this.equalTo,
this.notEqualTo,
this.startsWith,
this.notStartsWith,
this.endsWith,
this.notEndsWith,
this.contains,
this.notContains});
factory StringFilter.fromJson(Map<String, dynamic> json) =>
_$StringFilterFromJson(json);
String equalTo;
String notEqualTo;
String startsWith;
String notStartsWith;
String endsWith;
String notEndsWith;
String contains;
String notContains;
@override
List<Object> get props => [
equalTo,
notEqualTo,
startsWith,
notStartsWith,
endsWith,
notEndsWith,
contains,
notContains
];
Map<String, dynamic> toJson() => _$StringFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class MediaItemFilter with EquatableMixin {
MediaItemFilter(
{this.id,
this.createdAt,
this.updatedAt,
this.exifDate,
this.latitude,
this.longitude,
this.isVideo,
this.origName,
this.tags,
this.albums});
factory MediaItemFilter.fromJson(Map<String, dynamic> json) =>
_$MediaItemFilterFromJson(json);
IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
TimeFilter exifDate;
FloatFilter latitude;
FloatFilter longitude;
BooleanFilter isVideo;
StringFilter origName;
TagFilter tags;
AlbumFilter albums;
@override
List<Object> get props => [
id,
createdAt,
updatedAt,
exifDate,
latitude,
longitude,
isVideo,
origName,
tags,
albums
];
Map<String, dynamic> toJson() => _$MediaItemFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class TagFilter with EquatableMixin {
TagFilter({this.id, this.createdAt, this.updatedAt, this.name});
factory TagFilter.fromJson(Map<String, dynamic> json) =>
_$TagFilterFromJson(json);
IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
StringFilter name;
@override
List<Object> get props => [id, createdAt, updatedAt, name];
Map<String, dynamic> toJson() => _$TagFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class AlbumFilter with EquatableMixin {
AlbumFilter({this.id, this.createdAt, this.updatedAt, this.name});
factory AlbumFilter.fromJson(Map<String, dynamic> json) =>
_$AlbumFilterFromJson(json);
IDFilter id;
TimeFilter createdAt;
TimeFilter updatedAt;
StringFilter name;
@override
List<Object> get props => [id, createdAt, updatedAt, name];
Map<String, dynamic> toJson() => _$AlbumFilterToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Page with EquatableMixin {
Page({this.size, this.page});
factory Page.fromJson(Map<String, dynamic> json) => _$PageFromJson(json);
int size;
int page;
@override
List<Object> get props => [size, page];
Map<String, dynamic> toJson() => _$PageToJson(this);
}
@JsonSerializable(explicitToJson: true)
class Order with EquatableMixin {
Order({this.by, this.direction});
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
String by;
@JsonKey(unknownEnumValue: OrderDirection.artemisUnknown)
OrderDirection direction;
@override
List<Object> get props => [by, direction];
Map<String, dynamic> toJson() => _$OrderToJson(this);
}
enum AuthResult { enum AuthResult {
@JsonValue('Success') @JsonValue('Success')
success, success,
@ -194,6 +550,14 @@ enum AuthType {
@JsonValue('ARTEMIS_UNKNOWN') @JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown, artemisUnknown,
} }
enum OrderDirection {
@JsonValue('ASC')
asc,
@JsonValue('DESC')
desc,
@JsonValue('ARTEMIS_UNKNOWN')
artemisUnknown,
}
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class LoginArguments extends JsonSerializable with EquatableMixin { class LoginArguments extends JsonSerializable with EquatableMixin {
@ -508,3 +872,161 @@ class MeQuery extends GraphQLQuery<Me$Query, JsonSerializable> {
@override @override
Me$Query parse(Map<String, dynamic> json) => Me$Query.fromJson(json); Me$Query parse(Map<String, dynamic> json) => Me$Query.fromJson(json);
} }
@JsonSerializable(explicitToJson: true)
class MediaItemsArguments extends JsonSerializable with EquatableMixin {
MediaItemsArguments({this.order, this.page, this.filter});
@override
factory MediaItemsArguments.fromJson(Map<String, dynamic> json) =>
_$MediaItemsArgumentsFromJson(json);
final Order order;
final Page page;
final MediaItemFilter filter;
@override
List<Object> get props => [order, page, filter];
@override
Map<String, dynamic> toJson() => _$MediaItemsArgumentsToJson(this);
}
class MediaItemsQuery
extends GraphQLQuery<MediaItems$Query, MediaItemsArguments> {
MediaItemsQuery({this.variables});
@override
final DocumentNode document = DocumentNode(definitions: [
OperationDefinitionNode(
type: OperationType.query,
name: NameNode(value: 'mediaItems'),
variableDefinitions: [
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'order')),
type: NamedTypeNode(
name: NameNode(value: 'Order'), isNonNull: false),
defaultValue: DefaultValueNode(value: null),
directives: []),
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'page')),
type: NamedTypeNode(
name: NameNode(value: 'Page'), isNonNull: false),
defaultValue: DefaultValueNode(value: null),
directives: []),
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'filter')),
type: NamedTypeNode(
name: NameNode(value: 'MediaItemFilter'), isNonNull: false),
defaultValue: DefaultValueNode(value: null),
directives: [])
],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'mediaItems'),
alias: null,
arguments: [
ArgumentNode(
name: NameNode(value: 'filter'),
value: VariableNode(name: NameNode(value: 'filter'))),
ArgumentNode(
name: NameNode(value: 'page'),
value: VariableNode(name: NameNode(value: 'page'))),
ArgumentNode(
name: NameNode(value: 'order'),
value: VariableNode(name: NameNode(value: 'order')))
],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'data'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'id'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'fileName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'latitude'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'longitude'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'isVideo'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'origName'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'createdAt'),
alias: null,
arguments: [],
directives: [],
selectionSet: null)
])),
FieldNode(
name: NameNode(value: 'page'),
alias: null,
arguments: [],
directives: [],
selectionSet: SelectionSetNode(selections: [
FieldNode(
name: NameNode(value: 'size'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'page'),
alias: null,
arguments: [],
directives: [],
selectionSet: null),
FieldNode(
name: NameNode(value: 'total'),
alias: null,
arguments: [],
directives: [],
selectionSet: null)
]))
]))
]))
]);
@override
final String operationName = 'mediaItems';
@override
final MediaItemsArguments variables;
@override
List<Object> get props => [document, operationName, variables];
@override
MediaItems$Query parse(Map<String, dynamic> json) =>
MediaItems$Query.fromJson(json);
}

View File

@ -192,6 +192,318 @@ Map<String, dynamic> _$Me$QueryToJson(Me$Query instance) => <String, dynamic>{
'me': instance.me?.toJson(), 'me': instance.me?.toJson(),
}; };
MediaItems$Query$MediaItemResponse$MediaItem
_$MediaItems$Query$MediaItemResponse$MediaItemFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse$MediaItem()
..id = json['id'] as String
..fileName = json['fileName'] as String
..latitude = (json['latitude'] as num)?.toDouble()
..longitude = (json['longitude'] as num)?.toDouble()
..isVideo = json['isVideo'] as bool
..origName = json['origName'] as String
..createdAt = json['createdAt'] == null
? null
: DateTime.parse(json['createdAt'] as String);
}
Map<String, dynamic> _$MediaItems$Query$MediaItemResponse$MediaItemToJson(
MediaItems$Query$MediaItemResponse$MediaItem instance) =>
<String, dynamic>{
'id': instance.id,
'fileName': instance.fileName,
'latitude': instance.latitude,
'longitude': instance.longitude,
'isVideo': instance.isVideo,
'origName': instance.origName,
'createdAt': instance.createdAt?.toIso8601String(),
};
MediaItems$Query$MediaItemResponse$PageResponse
_$MediaItems$Query$MediaItemResponse$PageResponseFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse$PageResponse()
..size = json['size'] as int
..page = json['page'] as int
..total = json['total'] as int;
}
Map<String, dynamic> _$MediaItems$Query$MediaItemResponse$PageResponseToJson(
MediaItems$Query$MediaItemResponse$PageResponse instance) =>
<String, dynamic>{
'size': instance.size,
'page': instance.page,
'total': instance.total,
};
MediaItems$Query$MediaItemResponse _$MediaItems$Query$MediaItemResponseFromJson(
Map<String, dynamic> json) {
return MediaItems$Query$MediaItemResponse()
..data = (json['data'] as List)
?.map((e) => e == null
? null
: MediaItems$Query$MediaItemResponse$MediaItem.fromJson(
e as Map<String, dynamic>))
?.toList()
..page = json['page'] == null
? null
: MediaItems$Query$MediaItemResponse$PageResponse.fromJson(
json['page'] as Map<String, dynamic>);
}
Map<String, dynamic> _$MediaItems$Query$MediaItemResponseToJson(
MediaItems$Query$MediaItemResponse instance) =>
<String, dynamic>{
'data': instance.data?.map((e) => e?.toJson())?.toList(),
'page': instance.page?.toJson(),
};
MediaItems$Query _$MediaItems$QueryFromJson(Map<String, dynamic> json) {
return MediaItems$Query()
..mediaItems = json['mediaItems'] == null
? null
: MediaItems$Query$MediaItemResponse.fromJson(
json['mediaItems'] as Map<String, dynamic>);
}
Map<String, dynamic> _$MediaItems$QueryToJson(MediaItems$Query instance) =>
<String, dynamic>{
'mediaItems': instance.mediaItems?.toJson(),
};
TimeFilter _$TimeFilterFromJson(Map<String, dynamic> json) {
return TimeFilter(
equalTo: json['equalTo'] == null
? null
: DateTime.parse(json['equalTo'] as String),
notEqualTo: json['notEqualTo'] == null
? null
: DateTime.parse(json['notEqualTo'] as String),
lessThan: json['lessThan'] == null
? null
: DateTime.parse(json['lessThan'] as String),
lessThanOrEqualTo: json['lessThanOrEqualTo'] == null
? null
: DateTime.parse(json['lessThanOrEqualTo'] as String),
greaterThan: json['greaterThan'] == null
? null
: DateTime.parse(json['greaterThan'] as String),
greaterThanOrEqualTo: json['greaterThanOrEqualTo'] == null
? null
: DateTime.parse(json['greaterThanOrEqualTo'] as String),
);
}
Map<String, dynamic> _$TimeFilterToJson(TimeFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo?.toIso8601String(),
'notEqualTo': instance.notEqualTo?.toIso8601String(),
'lessThan': instance.lessThan?.toIso8601String(),
'lessThanOrEqualTo': instance.lessThanOrEqualTo?.toIso8601String(),
'greaterThan': instance.greaterThan?.toIso8601String(),
'greaterThanOrEqualTo': instance.greaterThanOrEqualTo?.toIso8601String(),
};
FloatFilter _$FloatFilterFromJson(Map<String, dynamic> json) {
return FloatFilter(
equalTo: (json['equalTo'] as num)?.toDouble(),
notEqualTo: (json['notEqualTo'] as num)?.toDouble(),
lessThan: (json['lessThan'] as num)?.toDouble(),
lessThanOrEqualTo: (json['lessThanOrEqualTo'] as num)?.toDouble(),
greaterThan: (json['greaterThan'] as num)?.toDouble(),
greaterThanOrEqualTo: (json['greaterThanOrEqualTo'] as num)?.toDouble(),
);
}
Map<String, dynamic> _$FloatFilterToJson(FloatFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
'lessThan': instance.lessThan,
'lessThanOrEqualTo': instance.lessThanOrEqualTo,
'greaterThan': instance.greaterThan,
'greaterThanOrEqualTo': instance.greaterThanOrEqualTo,
};
BooleanFilter _$BooleanFilterFromJson(Map<String, dynamic> json) {
return BooleanFilter(
equalTo: json['equalTo'] as bool,
notEqualTo: json['notEqualTo'] as bool,
);
}
Map<String, dynamic> _$BooleanFilterToJson(BooleanFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
};
IDFilter _$IDFilterFromJson(Map<String, dynamic> json) {
return IDFilter(
equalTo: json['equalTo'] as String,
notEqualTo: json['notEqualTo'] as String,
);
}
Map<String, dynamic> _$IDFilterToJson(IDFilter instance) => <String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
};
StringFilter _$StringFilterFromJson(Map<String, dynamic> json) {
return StringFilter(
equalTo: json['equalTo'] as String,
notEqualTo: json['notEqualTo'] as String,
startsWith: json['startsWith'] as String,
notStartsWith: json['notStartsWith'] as String,
endsWith: json['endsWith'] as String,
notEndsWith: json['notEndsWith'] as String,
contains: json['contains'] as String,
notContains: json['notContains'] as String,
);
}
Map<String, dynamic> _$StringFilterToJson(StringFilter instance) =>
<String, dynamic>{
'equalTo': instance.equalTo,
'notEqualTo': instance.notEqualTo,
'startsWith': instance.startsWith,
'notStartsWith': instance.notStartsWith,
'endsWith': instance.endsWith,
'notEndsWith': instance.notEndsWith,
'contains': instance.contains,
'notContains': instance.notContains,
};
MediaItemFilter _$MediaItemFilterFromJson(Map<String, dynamic> json) {
return MediaItemFilter(
id: json['id'] == null
? null
: IDFilter.fromJson(json['id'] as Map<String, dynamic>),
createdAt: json['createdAt'] == null
? null
: TimeFilter.fromJson(json['createdAt'] as Map<String, dynamic>),
updatedAt: json['updatedAt'] == null
? null
: TimeFilter.fromJson(json['updatedAt'] as Map<String, dynamic>),
exifDate: json['exifDate'] == null
? null
: TimeFilter.fromJson(json['exifDate'] as Map<String, dynamic>),
latitude: json['latitude'] == null
? null
: FloatFilter.fromJson(json['latitude'] as Map<String, dynamic>),
longitude: json['longitude'] == null
? null
: FloatFilter.fromJson(json['longitude'] as Map<String, dynamic>),
isVideo: json['isVideo'] == null
? null
: BooleanFilter.fromJson(json['isVideo'] as Map<String, dynamic>),
origName: json['origName'] == null
? null
: StringFilter.fromJson(json['origName'] as Map<String, dynamic>),
tags: json['tags'] == null
? null
: TagFilter.fromJson(json['tags'] as Map<String, dynamic>),
albums: json['albums'] == null
? null
: AlbumFilter.fromJson(json['albums'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$MediaItemFilterToJson(MediaItemFilter instance) =>
<String, dynamic>{
'id': instance.id?.toJson(),
'createdAt': instance.createdAt?.toJson(),
'updatedAt': instance.updatedAt?.toJson(),
'exifDate': instance.exifDate?.toJson(),
'latitude': instance.latitude?.toJson(),
'longitude': instance.longitude?.toJson(),
'isVideo': instance.isVideo?.toJson(),
'origName': instance.origName?.toJson(),
'tags': instance.tags?.toJson(),
'albums': instance.albums?.toJson(),
};
TagFilter _$TagFilterFromJson(Map<String, dynamic> json) {
return TagFilter(
id: json['id'] == null
? null
: IDFilter.fromJson(json['id'] as Map<String, dynamic>),
createdAt: json['createdAt'] == null
? null
: TimeFilter.fromJson(json['createdAt'] as Map<String, dynamic>),
updatedAt: json['updatedAt'] == null
? null
: TimeFilter.fromJson(json['updatedAt'] as Map<String, dynamic>),
name: json['name'] == null
? null
: StringFilter.fromJson(json['name'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$TagFilterToJson(TagFilter instance) => <String, dynamic>{
'id': instance.id?.toJson(),
'createdAt': instance.createdAt?.toJson(),
'updatedAt': instance.updatedAt?.toJson(),
'name': instance.name?.toJson(),
};
AlbumFilter _$AlbumFilterFromJson(Map<String, dynamic> json) {
return AlbumFilter(
id: json['id'] == null
? null
: IDFilter.fromJson(json['id'] as Map<String, dynamic>),
createdAt: json['createdAt'] == null
? null
: TimeFilter.fromJson(json['createdAt'] as Map<String, dynamic>),
updatedAt: json['updatedAt'] == null
? null
: TimeFilter.fromJson(json['updatedAt'] as Map<String, dynamic>),
name: json['name'] == null
? null
: StringFilter.fromJson(json['name'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$AlbumFilterToJson(AlbumFilter instance) =>
<String, dynamic>{
'id': instance.id?.toJson(),
'createdAt': instance.createdAt?.toJson(),
'updatedAt': instance.updatedAt?.toJson(),
'name': instance.name?.toJson(),
};
Page _$PageFromJson(Map<String, dynamic> json) {
return Page(
size: json['size'] as int,
page: json['page'] as int,
);
}
Map<String, dynamic> _$PageToJson(Page instance) => <String, dynamic>{
'size': instance.size,
'page': instance.page,
};
Order _$OrderFromJson(Map<String, dynamic> json) {
return Order(
by: json['by'] as String,
direction: _$enumDecodeNullable(_$OrderDirectionEnumMap, json['direction'],
unknownValue: OrderDirection.artemisUnknown),
);
}
Map<String, dynamic> _$OrderToJson(Order instance) => <String, dynamic>{
'by': instance.by,
'direction': _$OrderDirectionEnumMap[instance.direction],
};
const _$OrderDirectionEnumMap = {
OrderDirection.asc: 'ASC',
OrderDirection.desc: 'DESC',
OrderDirection.artemisUnknown: 'ARTEMIS_UNKNOWN',
};
LoginArguments _$LoginArgumentsFromJson(Map<String, dynamic> json) { LoginArguments _$LoginArgumentsFromJson(Map<String, dynamic> json) {
return LoginArguments( return LoginArguments(
user: json['user'] as String, user: json['user'] as String,
@ -219,3 +531,25 @@ Map<String, dynamic> _$CreateMediaItemArgumentsToJson(
<String, dynamic>{ <String, dynamic>{
'file': fromDartMultipartFileToGraphQLUpload(instance.file), 'file': fromDartMultipartFileToGraphQLUpload(instance.file),
}; };
MediaItemsArguments _$MediaItemsArgumentsFromJson(Map<String, dynamic> json) {
return MediaItemsArguments(
order: json['order'] == null
? null
: Order.fromJson(json['order'] as Map<String, dynamic>),
page: json['page'] == null
? null
: Page.fromJson(json['page'] as Map<String, dynamic>),
filter: json['filter'] == null
? null
: MediaItemFilter.fromJson(json['filter'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$MediaItemsArgumentsToJson(
MediaItemsArguments instance) =>
<String, dynamic>{
'order': instance.order?.toJson(),
'page': instance.page?.toJson(),
'filter': instance.filter?.toJson(),
};

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart'; import 'package:fluro/fluro.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:imagini/blocs/login_bloc.dart';
import 'package:imagini/core/app_provider.dart'; import 'package:imagini/core/app_provider.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
static const String PATH = '/Login'; static const String PATH = '/';
LoginScreen({Key key}) : super(key: key); LoginScreen({Key key}) : super(key: key);
@ -16,7 +17,7 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> { class _LoginScreenState extends State<LoginScreen> {
// LoginBloc bloc; LoginBloc bloc;
@override @override
void dispose() { void dispose() {
@ -29,12 +30,76 @@ class _LoginScreenState extends State<LoginScreen> {
_init(); _init();
return Scaffold( return Scaffold(
body: Center( body: StreamBuilder<bool>(
stream: bloc.authenticatedResult,
builder: (context, snapshot) {
if (snapshot.data == null || snapshot.data == true)
return _appLoading();
return _appLogin();
}
)
);
}
void _init(){
if(bloc != null){
return;
}
bloc = LoginBloc(AppProvider.getApplication(context));
bloc.authenticatedResult.listen((bool status) {
if (status)
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
});
bloc.loginResult.listen((bool status) {
if (status == null || status == false)
return;
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
});
}
Widget _appLoading(){
return Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 270,
child: Column(
children: <Widget>[
Container(
child: FittedBox(
fit: BoxFit.contain,
child: const FlutterLogo(),
),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
PlatformCircularProgressIndicator()
],
),
),
),
);
}
Widget _appLogin(){
TextEditingController serverController = new TextEditingController();
TextEditingController userController = new TextEditingController();
TextEditingController passwordController = new TextEditingController();
final _formKey = GlobalKey<FormState>();
return Center(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500), constraints: BoxConstraints(maxWidth: 500),
child: Container( child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0), margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 500, height: 500,
child: Form(
key: _formKey,
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Container( Container(
@ -46,24 +111,45 @@ class _LoginScreenState extends State<LoginScreen> {
margin: EdgeInsets.fromLTRB(0, 0, 0, 50), margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
), ),
Expanded( Expanded(
child: TextField( child: TextFormField(
controller: serverController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter server address';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Server Address' labelText: 'Server Address'
), ),
), ),
), ),
Expanded( Expanded(
child: TextField( child: TextFormField(
controller: userController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter username or email';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Username / Email' labelText: 'Username / Email'
), ),
), ),
), ),
Expanded( Expanded(
child: TextField( child: TextFormField(
obscureText: true, obscureText: true,
enableSuggestions: false, enableSuggestions: false,
autocorrect: false, autocorrect: false,
controller: passwordController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter password';
}
return null;
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password' labelText: 'Password'
), ),
@ -73,11 +159,31 @@ class _LoginScreenState extends State<LoginScreen> {
width: double.infinity, width: double.infinity,
child: PlatformButton( child: PlatformButton(
onPressed: () { onPressed: () {
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn); if (!_formKey.currentState.validate())
return;
bloc.attemptLogin(userController.text, passwordController.text, serverController.text);
}, },
child: Text('Login') child: Text('Login')
), ),
), ),
StreamBuilder<bool>(
stream: bloc.loginResult,
builder: (context, snapshot) {
if (snapshot.data == null || snapshot.data == true)
return Container();
return Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(20, 20, 20, 20),
child: Text(
"Login Failed",
style: TextStyle(color: Colors.red),
)
),
);
}
)
], ],
), ),
), ),
@ -85,10 +191,4 @@ class _LoginScreenState extends State<LoginScreen> {
), ),
); );
} }
void _init(){
// if(null == bloc){
// bloc = LoginBloc(AppProvider.getApplication(context));
// }
}
} }

View File

@ -1,70 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:fluro/fluro.dart';
import 'package:imagini/core/app_provider.dart';
import 'package:imagini/blocs/splash_bloc.dart';
import 'package:imagini/graphql/imagini_graphql.dart';
class SplashScreen extends StatefulWidget {
static const String PATH = '/';
SplashScreen({Key key}) : super(key: key);
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc bloc;
@override
void dispose() {
super.dispose();
bloc.dispose();
}
@override
Widget build(BuildContext context) {
_init();
return Scaffold(
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500),
child: Container(
margin: EdgeInsets.fromLTRB(50, 0, 50, 0),
height: 270,
child: Column(
children: <Widget>[
Container(
child: FittedBox(
fit: BoxFit.contain,
child: const FlutterLogo(),
),
width: 175,
margin: EdgeInsets.fromLTRB(0, 0, 0, 50),
),
PlatformCircularProgressIndicator()
],
),
),
),
),
);
}
void _init(){
if(null == bloc){
bloc = SplashBloc(AppProvider.getApplication(context));
bloc.loginResult.listen((Login$Query$AuthResponse lr) {
if (lr.result == AuthResult.success) {
AppProvider.getRouter(context).navigateTo(context, "/Home", transition: TransitionType.fadeIn);
} else {
AppProvider.getRouter(context).navigateTo(context, "/Login", transition: TransitionType.fadeIn);
}
});
}
}
}