diff --git a/build.gradle b/build.gradle index 256c79c..dbc9625 100644 --- a/build.gradle +++ b/build.gradle @@ -41,4 +41,9 @@ dependencies { } provided 'com.squareup.okhttp:okhttp:2.7.4' provided 'com.facebook.fresco:fbcore:0.9.0' + provided 'com.facebook.fresco:imagepipeline-okhttp:0.9.0' + + provided('io.socket:socket.io-client:0.7.0') { + exclude group: 'org.json', module: 'json' + } } diff --git a/src/main/java/ru/touchin/roboswag/components/socket/SocketConnection.java b/src/main/java/ru/touchin/roboswag/components/socket/SocketConnection.java new file mode 100644 index 0000000..fe92be3 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/socket/SocketConnection.java @@ -0,0 +1,143 @@ +package ru.touchin.roboswag.components.socket; + + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.util.HashMap; +import java.util.Map; + +import io.socket.client.Socket; +import io.socket.emitter.Emitter; +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.utils.android.RxAndroidUtils; +import rx.Observable; +import rx.Scheduler; +import rx.functions.Action1; +import rx.subjects.BehaviorSubject; + +/** + * Created by Gavriil Sitnikov on 29/02/16. + * TODO: description + */ +public abstract class SocketConnection { + + private final BehaviorSubject stateSubject = BehaviorSubject.create(State.DISCONNECTED); + private final Scheduler scheduler = RxAndroidUtils.createLooperScheduler(); + private final Map messagesObservableCache = new HashMap<>(); + + @Nullable + private Observable socketObservable; + + @NonNull + public Scheduler getScheduler() { + return scheduler; + } + + protected Observable getSocket() { + synchronized (scheduler) { + if (socketObservable == null) { + socketObservable = createSocketObservable(); + } + } + return socketObservable; + } + + protected abstract Socket createSocket() throws Exception; + + private Observable createSocketObservable() { + return Observable + .create(subscriber -> { + try { + final Socket socket = createSocket(); + socket.on(Socket.EVENT_CONNECT, args -> stateSubject.onNext(State.CONNECTED)); + socket.on(Socket.EVENT_CONNECTING, args -> stateSubject.onNext(State.CONNECTING)); + socket.on(Socket.EVENT_CONNECT_ERROR, args -> stateSubject.onNext(State.CONNECTION_ERROR)); + socket.on(Socket.EVENT_CONNECT_TIMEOUT, args -> stateSubject.onNext(State.CONNECTION_ERROR)); + socket.on(Socket.EVENT_DISCONNECT, args -> stateSubject.onNext(State.DISCONNECTED)); + socket.on(Socket.EVENT_RECONNECT_ATTEMPT, args -> stateSubject.onNext(State.CONNECTING)); + socket.on(Socket.EVENT_RECONNECTING, args -> stateSubject.onNext(State.CONNECTING)); + socket.on(Socket.EVENT_RECONNECT, args -> stateSubject.onNext(State.CONNECTED)); + socket.on(Socket.EVENT_RECONNECT_ERROR, args -> stateSubject.onNext(State.CONNECTION_ERROR)); + socket.on(Socket.EVENT_RECONNECT_FAILED, args -> stateSubject.onNext(State.CONNECTION_ERROR)); + subscriber.onNext(socket); + } catch (final Exception exception) { + Lc.assertion(exception); + } + subscriber.onCompleted(); + }) + .subscribeOn(scheduler) + .switchMap(socket -> Observable.just(socket) + .doOnSubscribe(socket::connect) + .doOnUnsubscribe(socket::disconnect)) + .replay(1) + .refCount(); + } + + @SuppressWarnings("unchecked") + protected Observable observeEvent(@NonNull final SocketEvent socketEvent) { + Observable result; + synchronized (scheduler) { + result = messagesObservableCache.get(socketEvent); + if (result != null) { + result = getSocket() + .switchMap(socket -> Observable + .create(subscriber -> socket.on(socketEvent.getName(), + new SocketListener<>(socketEvent, subscriber::onNext))) + .doOnUnsubscribe(() -> socket.off(socketEvent.getName()))); + messagesObservableCache.put(socketEvent, result); + } + } + return result; + } + + public Observable observeSocketState() { + return stateSubject.distinctUntilChanged(); + } + + public enum State { + DISCONNECTED, + CONNECTING, + CONNECTED, + CONNECTION_ERROR + } + + public class SocketListener implements Emitter.Listener { + + @NonNull + private final SocketEvent socketEvent; + @NonNull + private final Action1 action; + + public SocketListener(@NonNull final SocketEvent socketEvent, + @NonNull final Action1 action) { + this.socketEvent = socketEvent; + this.action = action; + } + + @Override + public void call(final Object... args) { + try { + if (args != null) { + final String response = args[0].toString(); + Lc.d("Got socket message: %s", response); + T message = socketEvent.parse(response); + if (socketEvent.getEventDataHandler() != null) { + socketEvent.getEventDataHandler().handleMessage(message); + } + action.call(message); + } + } catch (final RuntimeException throwable) { + Lc.assertion(throwable); + } catch (final JsonProcessingException exception) { + Lc.assertion(exception); + } catch (final Exception exception) { + Lc.e("Socket processing error", exception); + } + } + + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/socket/SocketEvent.java b/src/main/java/ru/touchin/roboswag/components/socket/SocketEvent.java new file mode 100644 index 0000000..7df3545 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/socket/SocketEvent.java @@ -0,0 +1,59 @@ +package ru.touchin.roboswag.components.socket; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; + +import java.io.StringReader; + +/** + * Created by Ilia Kurtov on 22.01.2016. + */ +public class SocketEvent { + + private static final JsonFactory DEFAULT_JSON_FACTORY = new JacksonFactory(); + + @NonNull + private final String name; + @NonNull + private final Class clz; + @Nullable + private final SocketMessageHandler eventDataHandler; + + public SocketEvent(@NonNull final String name, @NonNull final Class clz, @Nullable final SocketMessageHandler eventDataHandler) { + this.name = name; + this.clz = clz; + this.eventDataHandler = eventDataHandler; + } + + @NonNull + public String getName() { + return name; + } + + @Nullable + public SocketMessageHandler getEventDataHandler() { + return eventDataHandler; + } + + @NonNull + public T parse(@NonNull final String source) throws Exception { + return DEFAULT_JSON_FACTORY.createJsonObjectParser().parseAndClose(new StringReader(source), clz); + } + + @Override + public boolean equals(final Object o) { + return o instanceof SocketEvent + && ((SocketEvent) o).name.equals(name) + && ((SocketEvent) o).clz.equals(clz) + && ((SocketEvent) o).eventDataHandler == eventDataHandler; + } + + @Override + public int hashCode() { + return name.hashCode() + clz.hashCode(); + } + +} diff --git a/src/main/java/ru/touchin/roboswag/components/socket/SocketMessageHandler.java b/src/main/java/ru/touchin/roboswag/components/socket/SocketMessageHandler.java new file mode 100644 index 0000000..231c170 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/components/socket/SocketMessageHandler.java @@ -0,0 +1,13 @@ +package ru.touchin.roboswag.components.socket; + +import android.support.annotation.NonNull; + +/** + * Created by Gavriil Sitnikov on 29/02/16. + * TODO: description + */ +public interface SocketMessageHandler { + + T handleMessage(@NonNull T message) throws Exception; + +} diff --git a/src/main/java/ru/touchin/roboswag/components/utils/FrescoUtils.java b/src/main/java/ru/touchin/roboswag/components/utils/FrescoUtils.java index 55646cd..59d63a1 100644 --- a/src/main/java/ru/touchin/roboswag/components/utils/FrescoUtils.java +++ b/src/main/java/ru/touchin/roboswag/components/utils/FrescoUtils.java @@ -19,10 +19,27 @@ package ru.touchin.roboswag.components.utils; +import android.content.Context; +import android.graphics.Bitmap; import android.net.Uri; import android.support.annotation.NonNull; +import com.facebook.cache.common.CacheKey; +import com.facebook.common.executors.CallerThreadExecutor; +import com.facebook.common.references.CloseableReference; import com.facebook.common.util.UriUtil; +import com.facebook.datasource.DataSource; +import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; +import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; +import com.facebook.imagepipeline.image.CloseableImage; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; +import com.facebook.imagepipeline.request.Postprocessor; +import com.facebook.drawee.backends.pipeline.Fresco; + +import javax.annotation.Nullable; + +import rx.functions.Action1; /** * Created by Gavriil Sitnikov on 20/10/2015. @@ -30,12 +47,67 @@ import com.facebook.common.util.UriUtil; */ public final class FrescoUtils { + private static final BaseBitmapDataSubscriber EMPTY_CALLBACK = new BaseBitmapDataSubscriber() { + + @Override + protected void onNewResultImpl(final Bitmap bitmap) { + //do nothing + } + + @Override + protected void onFailureImpl(final DataSource> dataSource) { + //do nothing + } + + }; + @NonNull public static Uri getResourceUri(final int resourceId) { return new Uri.Builder().scheme(UriUtil.LOCAL_RESOURCE_SCHEME).path(String.valueOf(resourceId)).build(); } + public static void loadAndHandleBitmap(@NonNull final Context context, + @NonNull final Uri imageUrl, + @NonNull final Action1 bitmapHandler) { + final ImageRequest imageRequest = ImageRequestBuilder + .newBuilderWithSource(imageUrl) + .setPostprocessor(new RealCallback(bitmapHandler)) + .build(); + Fresco.getImagePipeline() + .fetchDecodedImage(imageRequest, context) + .subscribe(EMPTY_CALLBACK, CallerThreadExecutor.getInstance()); + } + private FrescoUtils() { } + private static class RealCallback implements Postprocessor { + + private final Action1 bitmapHandler; + + private RealCallback(final Action1 bitmapHandler) { + this.bitmapHandler = bitmapHandler; + } + + @Override + public CloseableReference process(final Bitmap sourceBitmap, final PlatformBitmapFactory bitmapFactory) { + final CloseableReference result + = bitmapFactory.createBitmap(sourceBitmap.getWidth(), sourceBitmap.getHeight()); + bitmapHandler.call(result.get()); + return result; + } + + @Override + public String getName() { + return null; + } + + @Nullable + @Override + public CacheKey getPostprocessorCacheKey() { + return null; + } + + } + }