diff --git a/README.md b/README.md index 9ca208b..04ea071 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ A self hosted photo library with user management & authentication. Cross platform Client supporting Android, iOS, and Web. ## Server +### Dependencies + +- libvips 8.3+ (8.8+ recommended) + ### Running CONFIG_PATH=$(pwd) DATA_PATH=$(pwd) go run cmd/main.go serve diff --git a/cmd/server/server.go b/cmd/server/server.go index 95d3b65..d010e19 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -5,6 +5,7 @@ import ( "net/http" "time" + "github.com/davidbyttow/govips/v2/vips" log "github.com/sirupsen/logrus" "reichard.io/imagini/internal/api" @@ -44,6 +45,9 @@ func (s *Server) StartServer() { } go func() { + vips.Startup(nil) + defer vips.Shutdown() + err := s.httpServer.ListenAndServe() if err != nil { log.Error("Error starting server ", err) diff --git a/go.mod b/go.mod index 34eab88..8729720 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.15 require ( github.com/99designs/gqlgen v0.13.0 github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect + github.com/davidbyttow/govips/v2 v2.5.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/disintegration/imaging v1.6.2 // indirect github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 diff --git a/go.sum b/go.sum index e6f8078..a464912 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,9 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSY github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidbyttow/govips v0.0.0-20201026223743-b1b72c7305d9 h1:nXNjG6pGALu++DSxtrdO79AZhtU+jZnyMlq3FTkFH74= +github.com/davidbyttow/govips/v2 v2.5.0 h1:CLSVkXwZYfF7bOR5bZwlUFL1TIWXwyuNF33UrlKscM4= +github.com/davidbyttow/govips/v2 v2.5.0/go.mod h1:goq38QD8XEMz2aWEeucEZqRxAWsemIN40vbUqfPfTAw= github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -92,6 +95,8 @@ github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/h2non/bimg v1.1.5 h1:o3xsUBxM8s7+e7PmpiWIkEYdeYayJ94eh4cJLx67m1k= +github.com/h2non/bimg v1.1.5/go.mod h1:R3+UiYwkK4rQl6KVFTOFJHitgLbZXBZNFh2cv3AEbp8= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= @@ -145,6 +150,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -213,12 +219,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -244,6 +252,8 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -266,10 +276,15 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -311,6 +326,7 @@ gopkg.in/Acconut/lockfile.v1 v1.1.0/go.mod h1:6UCz3wJ8tSFUsPR6uP/j8uegEtDuEEqFxl gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/h2non/gock.v1 v1.0.14/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag= gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/init_pkg_config_path b/init_pkg_config_path new file mode 100755 index 0000000..e5acabe --- /dev/null +++ b/init_pkg_config_path @@ -0,0 +1,5 @@ +# nix-env -i vips +PKG1=$(find /nix/store/*/lib/pkgconfig/* -name 'vips.pc') +PKG2=$(find /nix/store/*/lib/pkgconfig/* -name 'gobject-2.0.pc') + +export PKG_CONFIG_PATH=$PKG1:$PKG2 diff --git a/internal/api/media.go b/internal/api/media.go index 873b924..7994247 100644 --- a/internal/api/media.go +++ b/internal/api/media.go @@ -1,10 +1,17 @@ package api import ( + "bytes" + "errors" + "io/ioutil" "net/http" "os" "path" + "strconv" + "time" + "github.com/davidbyttow/govips/v2/vips" + log "github.com/sirupsen/logrus" "reichard.io/imagini/graph/model" ) @@ -23,21 +30,14 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) { } // Acquire Width & Height Parameters - query := r.URL.Query() - width := query["width"] - height := query["height"] - _ = width - _ = height - - // TODO: Caching & Resizing - // - If both, force resize with new scale - // - If one, scale resize proportionally + width, _ := strconv.Atoi(r.URL.Query().Get("width")) // Pull out userID authContext := r.Context().Value("auth").(*model.AuthContext) rawUserID, _ := (*authContext.AccessToken).Get("sub") userID := rawUserID.(string) + // TODO: Derive Cache & Width Location // Derive Path fileName := path.Base(r.URL.Path) folderPath := path.Join("/" + api.Config.DataPath + "/media/" + userID) @@ -46,10 +46,54 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) { // Check if File Exists _, err := os.Stat(mediaPath) if os.IsNotExist(err) { - // TODO: Different HTTP Response Code? - w.WriteHeader(http.StatusMethodNotAllowed) + log.Warn("[media] Image does not exist on disk") + w.WriteHeader(http.StatusInternalServerError) return } - http.ServeFile(w, r, mediaPath) + mediaFile, err := resizeAndConvertImage(mediaPath, width) + if err != nil { + log.Warn("[media] Image conversion failed:", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + // TODO: Cache mediaFile + + http.ServeContent(w, r, fileName, time.Time{}, bytes.NewReader(mediaFile)) +} + +func resizeAndConvertImage(path string, desiredWidth int) ([]byte, error) { + inputImage, err := vips.NewImageFromFile(path) + if err != nil { + return nil, errors.New("[media] Unable to read image") + } + + // If we're viewing full image, we want full quality + desiredQuality := 100 + + // Do we need to scale? + if desiredWidth != 0 && inputImage.Width() > desiredWidth { + desiredQuality = 50 + desiredScale := float64(desiredWidth) / float64(inputImage.Width()) + err := inputImage.Resize(desiredScale, vips.KernelLanczos3) + if err != nil { + return nil, errors.New("[media] Unable to resize") + } + } else if inputImage.Format() == vips.ImageTypeJPEG { + // Return raw file + return ioutil.ReadFile(path) + } + + // Return non-converted but scaled image + if inputImage.Format() == vips.ImageTypeJPEG { + imageBytes, _, err := inputImage.ExportNative() + return imageBytes, err + } + + // Convert + ep := vips.NewJpegExportParams() + ep.Quality = desiredQuality + imageBytes, _, err := inputImage.ExportJpeg(ep) + return imageBytes, err } diff --git a/internal/api/media_item.go b/internal/api/media_item.go deleted file mode 100644 index 778f64e..0000000 --- a/internal/api/media_item.go +++ /dev/null @@ -1 +0,0 @@ -package api diff --git a/web_native/lib/api/api_provider.dart b/web_native/lib/api/api_provider.dart index b1647cf..9fcfad0 100644 --- a/web_native/lib/api/api_provider.dart +++ b/web_native/lib/api/api_provider.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:imagini/api/cookie_client/cookie_client.dart' if (dart.library.html) 'package:imagini/api/cookie_client/browser_cookie_client.dart' @@ -58,6 +59,16 @@ class APIProvider{ return response; } + Image getImage(String fileName) { + // TODO: Get headers & Server + String fullURL = fileName; + return new Image.network( + fullURL, + headers: {}, + fit: BoxFit.contain, + ); + } + Future me() async { QueryResult response = await _client.query( QueryOptions( @@ -73,8 +84,6 @@ class APIProvider{ document: MediaItemsQuery().document, ) ); - - print(response); return response; } diff --git a/web_native/lib/api/imagini_api_repository.dart b/web_native/lib/api/imagini_api_repository.dart index 657bd53..1cc4459 100644 --- a/web_native/lib/api/imagini_api_repository.dart +++ b/web_native/lib/api/imagini_api_repository.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:imagini/api/api_provider.dart'; import 'package:imagini/graphql/imagini_graphql.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:flutter/material.dart'; class ImaginiAPIRepository { APIProvider _apiProvider; @@ -25,6 +26,13 @@ class ImaginiAPIRepository { return Stream.fromFuture(_apiProvider.me()); } + Stream mediaItems() { + return Stream.fromFuture(_apiProvider.mediaItems().then((QueryResult resp) { + final mediaItemsResponse = MediaItems$Query.fromJson(resp.data); + return mediaItemsResponse; + })); + } + Stream isAuthenticated() { return Stream.fromFuture(_apiProvider.me().then((QueryResult resp) { if (resp.exception != null) @@ -32,4 +40,8 @@ class ImaginiAPIRepository { return true; })); } + + Image getImage(String fileName) { + return _apiProvider.getImage(fileName); + } } diff --git a/web_native/lib/blocs/home_bloc.dart b/web_native/lib/blocs/home_bloc.dart new file mode 100644 index 0000000..831a35a --- /dev/null +++ b/web_native/lib/blocs/home_bloc.dart @@ -0,0 +1,52 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; + +import 'package:imagini/core/imagini_application.dart'; +import 'package:imagini/api/imagini_api_repository.dart'; +import 'package:imagini/graphql/imagini_graphql.dart'; + +class HomeBloc{ + + final ImaginiApplication _application; + ImaginiAPIRepository _imaginiAPI; + + final _mediaItemsController = StreamController.broadcast(); + Stream get mediaItemsResult => _mediaItemsController.stream; + + final _loginController = StreamController.broadcast(); + Stream get loginResult => _loginController.stream; + + final _authenticatedController = StreamController.broadcast(); + Stream get authenticatedResult => _authenticatedController.stream; + + HomeBloc(this._application){ + _init(); + } + + void _init(){ + _imaginiAPI = _application.imaginiAPI; + getMediaItems(); + } + + void dispose(){ + _loginController.close(); + _authenticatedController.close(); + _mediaItemsController.close(); + } + + Image getImage(String fileName){ + return _imaginiAPI.getImage(fileName); + } + + getMediaItems(){ + _mediaItemsController.addStream(_imaginiAPI.mediaItems()); + } + + checkAuthentication(){ + _authenticatedController.addStream(_imaginiAPI.isAuthenticated()); + } + + attemptHome(String username, password, server){ + _loginController.addStream(_imaginiAPI.login(username, password, server)); + } +} diff --git a/web_native/lib/screens/home_screen.dart b/web_native/lib/screens/home_screen.dart index 8d0c0bb..1343c7b 100644 --- a/web_native/lib/screens/home_screen.dart +++ b/web_native/lib/screens/home_screen.dart @@ -3,6 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:imagini/blocs/home_bloc.dart'; +import 'package:imagini/core/app_provider.dart'; +import 'package:imagini/graphql/imagini_graphql.dart'; + class HomeScreen extends StatefulWidget { static const String PATH = '/Home'; @@ -14,19 +18,7 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { - // HomeBloc bloc; - - void _init(){ - // if(null == bloc){ - // bloc = HomeBloc(AppProvider.getApplication(context)); - // } - } - - @override - void dispose() { - super.dispose(); - // bloc.dispose(); - } + HomeBloc bloc; @override Widget build(BuildContext context) { @@ -38,6 +30,21 @@ class _HomeScreenState extends State { ); } + void _init(){ + if(bloc != null) + return; + + bloc = HomeBloc(AppProvider.getApplication(context)); + // bloc.mediaItemsResult.listen((bool status) { + // }); + } + + @override + void dispose() { + super.dispose(); + // bloc.dispose(); + } + Widget _buildNavBar() { return PlatformNavBar( currentIndex: 0, @@ -76,29 +83,80 @@ class _HomeScreenState extends State { // ); - return new StaggeredGridView.countBuilder( - crossAxisCount: 4, - itemCount: 80, - itemBuilder: (BuildContext context, int index) => _buildCard("https://i.imgur.com/CgSGqUz.jpeg"), - // itemBuilder: (BuildContext context, int index) => new Container( - // color: Colors.green, - // child: new Center( - // child: new CircleAvatar( - // backgroundColor: Colors.white, - // child: new Text('$index'), - // ), - // )), - staggeredTileBuilder: (int index) => - // new StaggeredTile.count(2, index.isEven ? 2 : 1), - new StaggeredTile.fit(2), - mainAxisSpacing: 4.0, - crossAxisSpacing: 4.0, + // return new StaggeredGridView.countBuilder( + // crossAxisCount: 4, + // itemCount: 80, + // itemBuilder: (BuildContext context, int index) => _buildCard("https://i.imgur.com/CgSGqUz.jpeg"), + // // itemBuilder: (BuildContext context, int index) => new Container( + // // color: Colors.green, + // // child: new Center( + // // child: new CircleAvatar( + // // backgroundColor: Colors.white, + // // child: new Text('$index'), + // // ), + // // )), + // staggeredTileBuilder: (int index) => + // // new StaggeredTile.count(2, index.isEven ? 2 : 1), + // new StaggeredTile.fit(2), + // mainAxisSpacing: 4.0, + // crossAxisSpacing: 4.0, + // ); + + return StreamBuilder( + stream: bloc.mediaItemsResult, + builder: (context, snapshot) { + if (snapshot.data == null) + return _appLoading(); + + List allItems = snapshot.data.mediaItems.data; + + return new StaggeredGridView.countBuilder( + crossAxisCount: 4, + itemCount: allItems.length, + itemBuilder: (BuildContext context, int index) { + String fileName = allItems[index].fileName; + print(fileName); + return _buildCard("https://i.imgur.com/CgSGqUz.jpeg"); + }, + staggeredTileBuilder: (int index) => + // new StaggeredTile.count(2, index.isEven ? 2 : 1), + new StaggeredTile.fit(2), + mainAxisSpacing: 4.0, + crossAxisSpacing: 4.0, + ); + } + ); + } + + Widget _appLoading(){ + return Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 500), + child: Container( + margin: EdgeInsets.fromLTRB(50, 0, 50, 0), + height: 270, + child: Column( + children: [ + Container( + child: FittedBox( + fit: BoxFit.contain, + child: const FlutterLogo(), + ), + width: 175, + margin: EdgeInsets.fromLTRB(0, 0, 0, 50), + ), + PlatformCircularProgressIndicator() + ], + ), + ), + ), ); } Widget _buildCard(charImageUrl) { return new Image.network( charImageUrl, + headers: {}, fit: BoxFit.contain, ); } diff --git a/web_native/lib/screens/login_screen.dart b/web_native/lib/screens/login_screen.dart index 34a29ff..f7dcbee 100644 --- a/web_native/lib/screens/login_screen.dart +++ b/web_native/lib/screens/login_screen.dart @@ -19,12 +19,6 @@ class _LoginScreenState extends State { LoginBloc bloc; - @override - void dispose() { - super.dispose(); - // bloc.dispose(); - } - @override Widget build(BuildContext context) { _init(); @@ -42,9 +36,8 @@ class _LoginScreenState extends State { } void _init(){ - if(bloc != null){ + if(bloc != null) return; - } bloc = LoginBloc(AppProvider.getApplication(context)); bloc.authenticatedResult.listen((bool status) { @@ -60,6 +53,12 @@ class _LoginScreenState extends State { }); } + @override + void dispose() { + super.dispose(); + // bloc.dispose(); + } + Widget _appLoading(){ return Center( child: ConstrainedBox( diff --git a/web_native/lib/widgets/imagini_image_provider.dart.tmp b/web_native/lib/widgets/imagini_image_provider.dart.tmp new file mode 100644 index 0000000..d4e2818 --- /dev/null +++ b/web_native/lib/widgets/imagini_image_provider.dart.tmp @@ -0,0 +1,40 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class ImaginiImageProvider extends ImageProvider { + @override + final String url; + + @override + final double scale; + + @override + final Map headers; + + ImaginiImageProvider(this.url, {this.scale = 1.0, this.headers}) + : assert(url != null), + assert(scale != null); + + NetworkImage _netImg; + + @override + ImageStreamCompleter load(ImaginiImageProvider key, DecoderCallback decode) { + return MultiFrameImageStreamCompleter( + codec: _loadAsync(decode), + scale: 1.0, + debugLabel: fileId, + informationCollector: () sync* { + yield ErrorDescription('Path: $fileId'); + }, + ); + } + + Future _loadAsync(DecoderCallback decode) async { + _netImg._loadAsync(decode); + } + + @override + Future obtainKey(ImageConfiguration configuration) { + + } +}