On the fly image conversion
This commit is contained in:
parent
5212d7bf70
commit
901a69bb91
@ -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
|
||||
|
@ -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)
|
||||
|
1
go.mod
1
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
|
||||
|
16
go.sum
16
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=
|
||||
|
5
init_pkg_config_path
Executable file
5
init_pkg_config_path
Executable file
@ -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
|
@ -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
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
package api
|
@ -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<QueryResult> me() async {
|
||||
QueryResult response = await _client.query(
|
||||
QueryOptions(
|
||||
@ -73,8 +84,6 @@ class APIProvider{
|
||||
document: MediaItemsQuery().document,
|
||||
)
|
||||
);
|
||||
|
||||
print(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
@ -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$Query> mediaItems() {
|
||||
return Stream.fromFuture(_apiProvider.mediaItems().then((QueryResult resp) {
|
||||
final mediaItemsResponse = MediaItems$Query.fromJson(resp.data);
|
||||
return mediaItemsResponse;
|
||||
}));
|
||||
}
|
||||
|
||||
Stream<bool> 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);
|
||||
}
|
||||
}
|
||||
|
52
web_native/lib/blocs/home_bloc.dart
Normal file
52
web_native/lib/blocs/home_bloc.dart
Normal file
@ -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<MediaItems$Query>.broadcast();
|
||||
Stream<MediaItems$Query> get mediaItemsResult => _mediaItemsController.stream;
|
||||
|
||||
final _loginController = StreamController<bool>.broadcast();
|
||||
Stream<bool> get loginResult => _loginController.stream;
|
||||
|
||||
final _authenticatedController = StreamController<bool>.broadcast();
|
||||
Stream<bool> 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));
|
||||
}
|
||||
}
|
@ -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<HomeScreen> {
|
||||
|
||||
// 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<HomeScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
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,18 +83,41 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
// );
|
||||
|
||||
|
||||
// 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<MediaItems$Query>(
|
||||
stream: bloc.mediaItemsResult,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null)
|
||||
return _appLoading();
|
||||
|
||||
List<MediaItems$Query$MediaItemResponse$MediaItem> allItems = snapshot.data.mediaItems.data;
|
||||
|
||||
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'),
|
||||
// ),
|
||||
// )),
|
||||
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),
|
||||
@ -95,10 +125,38 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
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: <Widget>[
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
@ -19,12 +19,6 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
|
||||
LoginBloc bloc;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
// bloc.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_init();
|
||||
@ -42,9 +36,8 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
}
|
||||
|
||||
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<LoginScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
// bloc.dispose();
|
||||
}
|
||||
|
||||
Widget _appLoading(){
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
|
40
web_native/lib/widgets/imagini_image_provider.dart.tmp
Normal file
40
web_native/lib/widgets/imagini_image_provider.dart.tmp
Normal file
@ -0,0 +1,40 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImaginiImageProvider extends ImageProvider<ImaginiImageProvider> {
|
||||
@override
|
||||
final String url;
|
||||
|
||||
@override
|
||||
final double scale;
|
||||
|
||||
@override
|
||||
final Map<String, String> 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<Codec> _loadAsync(DecoderCallback decode) async {
|
||||
_netImg._loadAsync(decode);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ImaginiImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user