From 4ea6ed857b609c02ccd330e0b13ec39f7aa07d1d Mon Sep 17 00:00:00 2001 From: Denis Karmyshakov Date: Tue, 21 Aug 2018 12:17:05 +0300 Subject: [PATCH] Added modules: api-logansquare, lifecycle-common, lifecycle-rx --- api-logansquare/.gitignore | 1 + api-logansquare/build.gradle | 27 +++ api-logansquare/src/main/AndroidManifest.xml | 2 + .../logansquare/ConverterUtils.java | 22 ++ .../java/ru/touchin/templates/ApiModel.java | 137 +++++++++++ .../LoganSquareBigDecimalConverter.java | 70 ++++++ .../logansquare/LoganSquareEnum.java | 33 +++ .../logansquare/LoganSquareEnumConverter.java | 77 ++++++ .../LoganSquareJodaTimeConverter.java | 84 +++++++ .../logansquare/LoganSquareJsonFactory.java | 141 +++++++++++ .../logansquare/LoganSquareJsonModel.java | 45 ++++ .../logansquare/LoganSquarePreferences.java | 158 ++++++++++++ .../retrofit/JsonRequestBodyConverter.java | 63 +++++ .../retrofit/JsonResponseBodyConverter.java | 112 +++++++++ build.gradle | 6 +- lifecycle-common/.gitignore | 1 + lifecycle-common/build.gradle | 27 +++ lifecycle-common/src/main/AndroidManifest.xml | 2 + .../templates/livedata/SingleLiveEvent.kt | 25 ++ .../viewmodel/LifecycleViewModelProviders.kt | 49 ++++ .../templates/viewmodel/ViewModelFactory.kt | 17 ++ .../viewmodel/ViewModelFactoryProvider.kt | 7 + lifecycle-rx/.gitignore | 1 + lifecycle-rx/build.gradle | 24 ++ lifecycle-rx/src/main/AndroidManifest.xml | 3 + .../livedata}/destroyable/BaseDestroyable.kt | 2 +- .../livedata}/destroyable/Destroyable.kt | 2 +- .../dispatcher/BaseLiveDataDispatcher.kt | 57 +++++ .../livedata/dispatcher/LiveDataDispatcher.kt | 27 +++ .../livedata/event/CompletableEvent.kt | 14 ++ .../ru/touchin/livedata/event/MaybeEvent.kt | 16 ++ .../touchin/livedata/event/ObservableEvent.kt | 16 ++ .../ru/touchin/livedata/event/SingleEvent.kt | 14 ++ .../templates/viewmodel/RxViewModel.kt | 24 ++ lifecycle-rx/src/main/res/values/strings.xml | 3 + logging/build.gradle | 2 +- navigation/build.gradle | 3 +- .../navigation/OnFragmentStartedListener.java | 39 --- .../SimpleActionBarDrawerToggle.java | 3 +- .../navigation/activities/BaseActivity.java | 25 +- .../activities/OnBackPressedListener.java | 15 ++ .../fragments/ViewControllerFragment.java | 168 ++++++++----- .../navigation/fragments/ViewFragment.java | 229 ------------------ sample/build.gradle | 11 +- settings.gradle | 2 +- storable/build.gradle | 2 +- utils/build.gradle | 2 +- .../roboswag/core/utils/BiConsumer.java | 19 -- .../ru/touchin/templates/DeviceUtils.java | 146 +++++++++++ 49 files changed, 1585 insertions(+), 390 deletions(-) create mode 100644 api-logansquare/.gitignore create mode 100644 api-logansquare/build.gradle create mode 100644 api-logansquare/src/main/AndroidManifest.xml create mode 100644 api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java create mode 100644 api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java create mode 100644 lifecycle-common/.gitignore create mode 100644 lifecycle-common/build.gradle create mode 100644 lifecycle-common/src/main/AndroidManifest.xml create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt create mode 100644 lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt create mode 100644 lifecycle-rx/.gitignore create mode 100644 lifecycle-rx/build.gradle create mode 100644 lifecycle-rx/src/main/AndroidManifest.xml rename {utils/src/main/java/ru/touchin/roboswag/components/utils => lifecycle-rx/src/main/java/ru/touchin/livedata}/destroyable/BaseDestroyable.kt (97%) rename {utils/src/main/java/ru/touchin/roboswag/components/utils => lifecycle-rx/src/main/java/ru/touchin/livedata}/destroyable/Destroyable.kt (99%) create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt create mode 100644 lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt create mode 100644 lifecycle-rx/src/main/res/values/strings.xml delete mode 100644 navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java create mode 100644 navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java delete mode 100644 navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java delete mode 100644 utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java create mode 100644 utils/src/main/java/ru/touchin/templates/DeviceUtils.java diff --git a/api-logansquare/.gitignore b/api-logansquare/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/api-logansquare/.gitignore @@ -0,0 +1 @@ +/build diff --git a/api-logansquare/build.gradle b/api-logansquare/build.gradle new file mode 100644 index 0000000..9daa9da --- /dev/null +++ b/api-logansquare/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + maven { url "http://dl.bintray.com/touchin/touchin-tools" } +} + +dependencies { + api project(":storable") + api 'net.danlew:android.joda:2.9.9.4' + + implementation "com.android.support:support-annotations:$versions.supportLibrary" + implementation "com.squareup.retrofit2:retrofit:$versions.retrofit" + implementation 'ru.touchin:logansquare:1.4.3' +} diff --git a/api-logansquare/src/main/AndroidManifest.xml b/api-logansquare/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d48ece2 --- /dev/null +++ b/api-logansquare/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java b/api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java new file mode 100644 index 0000000..f8f7bba --- /dev/null +++ b/api-logansquare/src/main/java/com/bluelinelabs/logansquare/ConverterUtils.java @@ -0,0 +1,22 @@ +package com.bluelinelabs.logansquare; + +import android.support.annotation.NonNull; + +import java.lang.reflect.Type; + +/** + * Utility class for the {@link ru.touchin.templates.logansquare.LoganSquareJsonFactory}. This resides in LoganSquare's + * main package in order to take advantage of the package-visible ConcreteParameterizedType class, which is essential + * to the support of generic classes in the Retrofit converter. + */ +public final class ConverterUtils { + + @NonNull + public static ParameterizedType parameterizedTypeOf(@NonNull final Type type) { + return new ParameterizedType.ConcreteParameterizedType(type); + } + + private ConverterUtils() { + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java b/api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java new file mode 100644 index 0000000..5755445 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/ApiModel.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates; + +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.roboswag.core.log.LcGroup; + +/** + * Created by Gavriil Sitnikov on 11/08/2016. + * Just model from getting from API. + */ +public abstract class ApiModel implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * Logging group to log API validation errors. + */ + public static final LcGroup API_VALIDATION_LC_GROUP = new LcGroup("API_VALIDATION"); + + /** + * Validates list of objects. Use it if objects in list extends {@link ApiModel}. + * + * @param collection Collection of items to check; + * @param collectionValidationRule Rule explaining what to do if invalid items found; + * @throws ValidationException Exception of validation. + */ + @SuppressWarnings({"PMD.PreserveStackTrace", "PMD.CyclomaticComplexity"}) + // PreserveStackTrace: it's ok - we are logging it on Lc.e() + public static void validateCollection(@NonNull final Collection collection, @NonNull final CollectionValidationRule collectionValidationRule) + throws ValidationException { + boolean haveValidItem = false; + int position = 0; + final Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + final Object item = iterator.next(); + if (!(item instanceof ApiModel)) { + if (item != null) { + // let's just think that all of items are not ApiModels + break; + } + continue; + } + + try { + ((ApiModel) item).validate(); + haveValidItem = true; + } catch (final ValidationException exception) { + switch (collectionValidationRule) { + case EXCEPTION_IF_ANY_INVALID: + throw exception; + case EXCEPTION_IF_ALL_INVALID: + iterator.remove(); + API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position); + if (!iterator.hasNext() && !haveValidItem) { + throw new ValidationException("Whole list is invalid at " + Lc.getCodePoint(null, 1)); + } + break; + case REMOVE_INVALID_ITEMS: + iterator.remove(); + API_VALIDATION_LC_GROUP.e(exception, "Item %s is invalid at " + Lc.getCodePoint(null, 1), position); + break; + default: + Lc.assertion("Unexpected rule " + collectionValidationRule); + break; + } + } + position++; + } + } + + /** + * Validates collection on emptiness. + * + * @param collection Collection to check; + * @throws ValidationException Exception of validation. + */ + protected static void validateCollectionNotEmpty(@NonNull final Collection collection) + throws ValidationException { + if (collection.isEmpty()) { + throw new ValidationException("List is empty at " + Lc.getCodePoint(null, 1)); + } + } + + /** + * Validates this object. Override it to write your own logic. + * + * @throws ValidationException Exception of validation. + */ + @CallSuper + public void validate() throws ValidationException { + //do nothing + } + + public enum CollectionValidationRule { + EXCEPTION_IF_ANY_INVALID, + EXCEPTION_IF_ALL_INVALID, + REMOVE_INVALID_ITEMS, + } + + /** + * Class of exceptions throws during {@link ApiModel} validation. + */ + public static class ValidationException extends IOException { + + public ValidationException(@NonNull final String reason) { + super(reason); + } + + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java new file mode 100644 index 0000000..9f08d6f --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareBigDecimalConverter.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package ru.touchin.templates.logansquare; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bluelinelabs.logansquare.typeconverters.TypeConverter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; + +import java.io.IOException; +import java.math.BigDecimal; + +import ru.touchin.roboswag.core.log.Lc; + +/** + * LoganSquare converter for java.math.BigDecimal + */ +@SuppressWarnings("CPD-START") // similar to LoganSquareJodaTimeConverter +public class LoganSquareBigDecimalConverter implements TypeConverter { + + @Nullable + @Override + public BigDecimal parse(@NonNull final JsonParser jsonParser) throws IOException { + final String dateString = jsonParser.getValueAsString(); + if (dateString == null) { + return null; + } + try { + return new BigDecimal(dateString); + } catch (final RuntimeException exception) { + Lc.assertion(exception); + } + return null; + } + + @Override + public void serialize(@Nullable final BigDecimal object, + @Nullable final String fieldName, + final boolean writeFieldNameForObject, + @NonNull final JsonGenerator jsonGenerator) + throws IOException { + if (fieldName != null) { + jsonGenerator.writeStringField(fieldName, object != null ? object.toString() : null); + } else { + jsonGenerator.writeString(object != null ? object.toString() : null); + } + } + +} + diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java new file mode 100644 index 0000000..a5d0963 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnum.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.logansquare; + +import android.support.annotation.NonNull; + +/** + * Created by Gavriil Sitnikov. + * LoganSquare enum base class. + */ +public interface LoganSquareEnum { + + @NonNull + String getValueName(); + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java new file mode 100644 index 0000000..c274667 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareEnumConverter.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.logansquare; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter; + +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov. + * LoganSquare converter from String to Enum. + */ +@SuppressWarnings("PMD.UseVarargs") +public class LoganSquareEnumConverter extends StringBasedTypeConverter { + + @NonNull + private final T[] enumValues; + @Nullable + private final T defaultValue; + + public LoganSquareEnumConverter(@NonNull final T[] enumValues) { + this(enumValues, null); + } + + public LoganSquareEnumConverter(@NonNull final T[] enumValues, @Nullable final T defaultValue) { + super(); + this.enumValues = enumValues; + this.defaultValue = defaultValue; + } + + @Nullable + @Override + public T getFromString(@Nullable final String string) { + if (string == null) { + return defaultValue; + } + for (final T value : enumValues) { + if (value.getValueName().equals(string)) { + return value; + } + } + if (defaultValue != null) { + return defaultValue; + } + throw new ShouldNotHappenException("Enum parsing exception for value: " + string); + } + + @Nullable + @Override + public String convertToString(@Nullable final T object) { + if (object == null) { + return null; + } + return object.getValueName(); + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java new file mode 100644 index 0000000..11f52b8 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJodaTimeConverter.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + + +package ru.touchin.templates.logansquare; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.bluelinelabs.logansquare.typeconverters.TypeConverter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormatter; + +import java.io.IOException; + +import ru.touchin.roboswag.core.log.Lc; + +/** + * LoganSquare converter for joda.time.DateTime + */ +public class LoganSquareJodaTimeConverter implements TypeConverter { + + @Nullable + private final DateTimeFormatter formatter; + + public LoganSquareJodaTimeConverter() { + this.formatter = null; + } + + public LoganSquareJodaTimeConverter(@Nullable final DateTimeFormatter formatter) { + this.formatter = formatter; + } + + @Nullable + @Override + public DateTime parse(@NonNull final JsonParser jsonParser) throws IOException { + final String dateString = jsonParser.getValueAsString(); + if (dateString == null || dateString.isEmpty()) { + return null; + } + try { + return DateTime.parse(dateString); + } catch (final RuntimeException exception) { + Lc.assertion(exception); + } + return null; + } + + @Override + public void serialize( + @Nullable final DateTime object, + @Nullable final String fieldName, + final boolean writeFieldNameForObject, + @NonNull final JsonGenerator jsonGenerator + ) throws IOException { + final String serializedValue = object != null ? object.toString(formatter) : null; + if (fieldName != null) { + jsonGenerator.writeStringField(fieldName, !TextUtils.isEmpty(serializedValue) ? serializedValue : null); + } else { + jsonGenerator.writeString(!TextUtils.isEmpty(serializedValue) ? serializedValue : null); + } + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java new file mode 100644 index 0000000..20c5b15 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonFactory.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.logansquare; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bluelinelabs.logansquare.ConverterUtils; +import com.bluelinelabs.logansquare.LoganSquare; +import com.fasterxml.jackson.core.JsonGenerator; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; +import ru.touchin.templates.retrofit.JsonRequestBodyConverter; +import ru.touchin.templates.retrofit.JsonResponseBodyConverter; + +/** + * Created by Gavriil Sitnikov on 2/06/2016. + * LoganSquareConverter class to use with {@link Retrofit} to parse and generate models based on Logan Square library. + */ +public class LoganSquareJsonFactory extends Converter.Factory { + + @NonNull + @Override + public Converter responseBodyConverter(@NonNull final Type type, + @NonNull final Annotation[] annotations, + @NonNull final Retrofit retrofit) { + return new LoganSquareJsonResponseBodyConverter<>(type); + } + + @NonNull + @Override + public Converter requestBodyConverter(@NonNull final Type type, + @NonNull final Annotation[] parameterAnnotations, + @NonNull final Annotation[] methodAnnotations, + @NonNull final Retrofit retrofit) { + return new LoganSquareRequestBodyConverter<>(); + } + + @Nullable + @Override + public Converter stringConverter(@NonNull final Type type, @NonNull final Annotation[] annotations, @NonNull final Retrofit retrofit) { + if (type instanceof Class && ((Class) type).getSuperclass() == Enum.class) { + return new LoganSquareStringEnumConverter<>(); + } else { + return super.stringConverter(type, annotations, retrofit); + } + } + + public static class LoganSquareJsonResponseBodyConverter extends JsonResponseBodyConverter { + + @NonNull + private final Type type; + + public LoganSquareJsonResponseBodyConverter(@NonNull final Type type) { + super(); + this.type = type; + } + + @SuppressWarnings("unchecked") + @NonNull + @Override + protected T parseResponse(@NonNull final ResponseBody value) throws IOException { + if (type instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + final Type[] typeArguments = parameterizedType.getActualTypeArguments(); + final Type firstType = typeArguments[0]; + + final Type rawType = parameterizedType.getRawType(); + if (rawType == Map.class) { + return (T) LoganSquare.parseMap(value.byteStream(), (Class) typeArguments[1]); + } else if (rawType == List.class) { + return (T) LoganSquare.parseList(value.byteStream(), (Class) firstType); + } else { + // Generics + return (T) LoganSquare.parse(value.byteStream(), ConverterUtils.parameterizedTypeOf(type)); + } + } else { + return (T) LoganSquare.parse(value.byteStream(), (Class) type); + } + } + + } + + public static class LoganSquareRequestBodyConverter extends JsonRequestBodyConverter { + + @Override + protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) throws IOException { + LoganSquare.serialize(value, byteArrayOutputStream); + } + + } + + public static class LoganSquareStringEnumConverter implements Converter { + + @Nullable + @SuppressWarnings({"unchecked", "TryFinallyCanBeTryWithResources"}) + @Override + public String convert(@NonNull final T value) throws IOException { + final StringWriter writer = new StringWriter(); + try { + final JsonGenerator generator = LoganSquare.JSON_FACTORY.createGenerator(writer); + LoganSquare.typeConverterFor((Class) value.getClass()).serialize(value, null, false, generator); + generator.close(); + return writer.toString().replaceAll("\"", ""); + } finally { + writer.close(); + } + } + + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java new file mode 100644 index 0000000..ea67fbe --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquareJsonModel.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.logansquare; + +import android.support.annotation.Nullable; + +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.templates.ApiModel; + +/** + * Created by Gavriil Sitnikov. + * Just model from getting from API via LoganSquare. + */ +public abstract class LoganSquareJsonModel extends ApiModel { + + /** + * Throws exception if object is missed or null. + * + * @param object Value of field to check; + * @throws ValidationException Exception of validation. + */ + protected static void validateNotNull(@Nullable final Object object) throws ValidationException { + if (object == null) { + throw new ValidationException("Not nullable object is null or missed at " + Lc.getCodePoint(null, 1)); + } + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java new file mode 100644 index 0000000..763ccf0 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/logansquare/LoganSquarePreferences.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.logansquare; + +import android.content.SharedPreferences; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bluelinelabs.logansquare.LoganSquare; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.List; + +import ru.touchin.roboswag.components.utils.storables.PreferenceStore; +import ru.touchin.roboswag.core.observables.storable.Converter; +import ru.touchin.roboswag.core.observables.storable.NonNullStorable; +import ru.touchin.roboswag.core.observables.storable.Storable; +import ru.touchin.roboswag.core.utils.ShouldNotHappenException; + +/** + * Created by Gavriil Sitnikov on 26/12/2016. + * Utility class to get {@link Storable} that is storing LoganSquare (Json) generated object into preferences. + */ +@SuppressWarnings("CPD-START") +//CPD: it is same code as in GoogleJsonPreferences +public final class LoganSquarePreferences { + + @NonNull + public static Storable jsonStorable(@NonNull final String name, + @NonNull final Class jsonClass, + @NonNull final SharedPreferences preferences) { + return new Storable.Builder(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>()) + .setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE) + .build(); + } + + @NonNull + public static NonNullStorable jsonStorable(@NonNull final String name, + @NonNull final Class jsonClass, + @NonNull final SharedPreferences preferences, + @NonNull final T defaultValue) { + return new Storable.Builder(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>()) + .setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE) + .setDefaultValue(defaultValue) + .build(); + } + + @NonNull + public static Storable, String> jsonListStorable(@NonNull final String name, + @NonNull final Class jsonListItemClass, + @NonNull final SharedPreferences preferences) { + return new Storable.Builder<>(name, List.class, String.class, new PreferenceStore<>(preferences), new JsonListConverter<>(jsonListItemClass)) + .setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE) + .build(); + } + + @NonNull + public static NonNullStorable, String> jsonListStorable(@NonNull final String name, + @NonNull final Class jsonListItemClass, + @NonNull final SharedPreferences preferences, + @NonNull final List defaultValue) { + return new Storable.Builder<>(name, List.class, String.class, new PreferenceStore<>(preferences), new JsonListConverter<>(jsonListItemClass)) + .setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE) + .setDefaultValue(defaultValue) + .build(); + } + + private LoganSquarePreferences() { + } + + public static class JsonConverter implements Converter { + + @Nullable + @Override + public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final TJsonObject object) { + if (object == null) { + return null; + } + try { + return LoganSquare.serialize(object); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public TJsonObject toObject(@NonNull final Type jsonObjectClass, @NonNull final Type storeObjectType, @Nullable final String storeValue) { + if (storeValue == null) { + return null; + } + try { + return LoganSquare.parse(storeValue, (Class) jsonObjectClass); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + } + + public static class JsonListConverter implements Converter, String> { + + @NonNull + private final Class itemClass; + + public JsonListConverter(@NonNull final Class itemClass) { + this.itemClass = itemClass; + } + + @Nullable + @Override + @SuppressWarnings("unchecked") + public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final List object) { + if (object == null) { + return null; + } + try { + return LoganSquare.serialize(object, itemClass); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + @Nullable + @Override + public List toObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final String storeValue) { + if (storeValue == null) { + return null; + } + try { + return LoganSquare.parseList(storeValue, itemClass); + } catch (final IOException exception) { + throw new ShouldNotHappenException(exception); + } + } + + } + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java new file mode 100644 index 0000000..e0ea113 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonRequestBodyConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2017 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.retrofit; + + +import android.support.annotation.NonNull; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import retrofit2.Converter; +import ru.touchin.templates.ApiModel; + +/** + * Created by Gavriil Sitnikov on 14/02/2017. + * Object to serialize bodies of remote requests for Retrofit. + * + * @param Type of body object. + */ +public abstract class JsonRequestBodyConverter implements Converter { + + private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); + + @NonNull + @Override + public RequestBody convert(@NonNull final T value) throws IOException { + if (value instanceof ApiModel) { + ((ApiModel) value).validate(); + } + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + writeValueToByteArray(value, byteArrayOutputStream); + return RequestBody.create(MEDIA_TYPE, byteArrayOutputStream.toByteArray()); + } + + /** + * Serializing value to byte stream. + * + * @param value Value to serialize; + * @param byteArrayOutputStream Byte stream to write serialized bytes; + * @throws IOException Throws on serialization. + */ + protected abstract void writeValueToByteArray(@NonNull T value, @NonNull ByteArrayOutputStream byteArrayOutputStream) throws IOException; + +} diff --git a/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java new file mode 100644 index 0000000..614c8e2 --- /dev/null +++ b/api-logansquare/src/main/java/ru/touchin/templates/retrofit/JsonResponseBodyConverter.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates.retrofit; + +import android.support.annotation.NonNull; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.util.Collection; +import java.util.Map; + +import javax.net.ssl.SSLException; + +import okhttp3.ResponseBody; +import okhttp3.internal.http2.StreamResetException; +import retrofit2.Converter; +import ru.touchin.roboswag.core.log.Lc; +import ru.touchin.templates.ApiModel; + +/** + * Created by Gavriil Sitnikov on 14/02/2017. + * Object to deserialize responses of remote requests from Retrofit. + * + * @param Type of response object. + */ +public abstract class JsonResponseBodyConverter implements Converter { + + @SuppressWarnings("PMD.AvoidInstanceofChecksInCatchClause") + //AvoidInstanceofChecksInCatchClause: we just don't need assertion on specific exceptions + @NonNull + @Override + public T convert(@NonNull final ResponseBody value) throws IOException { + final T result; + try { + result = parseResponse(value); + } catch (final IOException exception) { + if (!(exception instanceof SocketException) + && !(exception instanceof InterruptedIOException) + && !(exception instanceof SSLException) + && !(exception instanceof StreamResetException)) { + Lc.assertion(exception); + } + throw exception; + } finally { + value.close(); + } + + if (result instanceof ApiModel) { + validateModel((ApiModel) result); + } + if (result instanceof Collection) { + validateCollection((Collection) result); + } + if (result instanceof Map) { + validateCollection(((Map) result).values()); + } + + return result; + } + + private void validateModel(@NonNull final ApiModel result) throws IOException { + try { + result.validate(); + } catch (final ApiModel.ValidationException validationException) { + Lc.assertion(validationException); + throw validationException; + } + } + + private void validateCollection(@NonNull final Collection result) throws IOException { + try { + ApiModel.validateCollection(result, getValidateCollectionRule()); + } catch (final ApiModel.ValidationException validationException) { + Lc.assertion(validationException); + throw validationException; + } + } + + @NonNull + protected ApiModel.CollectionValidationRule getValidateCollectionRule() { + return ApiModel.CollectionValidationRule.EXCEPTION_IF_ANY_INVALID; + } + + /** + * Parses response to specific object. + * + * @param value Response to parse; + * @return Parsed object; + * @throws IOException Throws during parsing. + */ + @NonNull + protected abstract T parseResponse(@NonNull ResponseBody value) throws IOException; + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7accccb..d489a4e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { - ext.kotlin_version = '1.2.60' + ext.kotlin_version = '1.2.61' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.1.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -24,7 +24,7 @@ task clean(type: Delete) { ext { versions = [ compileSdk : 27, - minSdk : 19, + minSdk : 16, supportLibrary: '27.1.1', navigation : '1.0.0-alpha04', lifecycle : '1.1.1', diff --git a/lifecycle-common/.gitignore b/lifecycle-common/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lifecycle-common/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lifecycle-common/build.gradle b/lifecycle-common/build.gradle new file mode 100644 index 0000000..e7ab339 --- /dev/null +++ b/lifecycle-common/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + api project(":navigation") + + compileOnly "javax.inject:javax.inject:1" + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" + + implementation "android.arch.lifecycle:extensions:$versions.lifecycle" +} diff --git a/lifecycle-common/src/main/AndroidManifest.xml b/lifecycle-common/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f617b1f --- /dev/null +++ b/lifecycle-common/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt b/lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt new file mode 100644 index 0000000..27b0a1d --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/livedata/SingleLiveEvent.kt @@ -0,0 +1,25 @@ +package ru.touchin.templates.livedata + +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.Observer +import java.util.concurrent.atomic.AtomicBoolean + +class SingleLiveEvent : MutableLiveData() { + + private val pending = AtomicBoolean(false) + + override fun observe(owner: LifecycleOwner, observer: Observer) { + super.observe(owner, Observer { value -> + if (pending.compareAndSet(true, false)) { + observer.onChanged(value) + } + }) + } + + override fun setValue(value: T) { + pending.set(true) + super.setValue(value) + } + +} diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt new file mode 100644 index 0000000..41ee808 --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/LifecycleViewModelProviders.kt @@ -0,0 +1,49 @@ +package ru.touchin.templates.viewmodel + +import android.app.Activity +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentActivity +import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController + +object LifecycleViewModelProviders { + + /** + * Creates a {@link ViewModelProvider}, which retains ViewModels while a scope of given + * {@code lifecycleOwner} is alive. More detailed explanation is in {@link ViewModel}. + *

+ * It uses the given {@link Factory} to instantiate new ViewModels. + * + * @param lifecycleOwner a lifecycle owner, in whose scope ViewModels should be retained (ViewController, Fragment, Activity) + * @param factory a {@code Factory} to instantiate new ViewModels + * @return a ViewModelProvider instance + */ + fun of(lifecycleOwner: LifecycleOwner, factory: ViewModelProvider.Factory = getViewModelFactory(lifecycleOwner)): ViewModelProvider = + when (lifecycleOwner) { + is ViewController<*, *> -> ViewModelProviders.of(lifecycleOwner.fragment, factory) + is Fragment -> ViewModelProviders.of(lifecycleOwner, factory) + is FragmentActivity -> ViewModelProviders.of(lifecycleOwner, factory) + else -> throw IllegalArgumentException("Not supported LifecycleOwner.") + } + + /** + * Returns ViewModelProvider.Factory instance from current lifecycleOwner. + * Search #ViewModelFactoryProvider are produced according to priorities: + * 1. View controller; + * 2. Fragment; + * 3. Parent fragment recursively; + * 4. Activity; + * 5. Application. + */ + fun getViewModelFactory(provider: Any): ViewModelProvider.Factory = + when (provider) { + is ViewModelFactoryProvider -> provider.viewModelFactory + is ViewController<*, *> -> getViewModelFactory(provider.fragment) + is Fragment -> getViewModelFactory(provider.parentFragment ?: provider.requireActivity()) + is Activity -> getViewModelFactory(provider.application) + else -> throw IllegalArgumentException("View model factory not found.") + } + +} diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt new file mode 100644 index 0000000..8061ba6 --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactory.kt @@ -0,0 +1,17 @@ +package ru.touchin.templates.viewmodel + +import android.arch.lifecycle.ViewModel +import android.arch.lifecycle.ViewModelProvider + +import javax.inject.Inject +import javax.inject.Provider + +class ViewModelFactory @Inject constructor( + private val creators: Map, @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T + = (creators[modelClass]?.get() as? T) ?: throw IllegalArgumentException("Unknown model class $modelClass") + +} diff --git a/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt new file mode 100644 index 0000000..b1684e2 --- /dev/null +++ b/lifecycle-common/src/main/java/ru/touchin/templates/viewmodel/ViewModelFactoryProvider.kt @@ -0,0 +1,7 @@ +package ru.touchin.templates.viewmodel + +interface ViewModelFactoryProvider { + + val viewModelFactory: ViewModelFactory + +} diff --git a/lifecycle-rx/.gitignore b/lifecycle-rx/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/lifecycle-rx/.gitignore @@ -0,0 +1 @@ +/build diff --git a/lifecycle-rx/build.gradle b/lifecycle-rx/build.gradle new file mode 100644 index 0000000..0956030 --- /dev/null +++ b/lifecycle-rx/build.gradle @@ -0,0 +1,24 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion versions.compileSdk + + defaultConfig { + minSdkVersion versions.minSdk + } +} + +dependencies { + api project(":utils") + api project(":logging") + + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" + + implementation "android.arch.lifecycle:extensions:$versions.lifecycle" + + implementation "io.reactivex.rxjava2:rxjava:$versions.rxJava" + implementation "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid" +} diff --git a/lifecycle-rx/src/main/AndroidManifest.xml b/lifecycle-rx/src/main/AndroidManifest.xml new file mode 100644 index 0000000..68016e6 --- /dev/null +++ b/lifecycle-rx/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt similarity index 97% rename from utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt rename to lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt index fafa855..cca546b 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/BaseDestroyable.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/BaseDestroyable.kt @@ -1,4 +1,4 @@ -package ru.touchin.roboswag.components.utils.destroyable +package ru.touchin.livedata.destroyable import io.reactivex.Completable import io.reactivex.Flowable diff --git a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt similarity index 99% rename from utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt rename to lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt index 8ad3012..93dd9b8 100644 --- a/utils/src/main/java/ru/touchin/roboswag/components/utils/destroyable/Destroyable.kt +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/destroyable/Destroyable.kt @@ -1,4 +1,4 @@ -package ru.touchin.roboswag.components.utils.destroyable +package ru.touchin.livedata.destroyable import io.reactivex.Completable import io.reactivex.Flowable diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt new file mode 100644 index 0000000..afcc376 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/BaseLiveDataDispatcher.kt @@ -0,0 +1,57 @@ +package ru.touchin.livedata.dispatcher + +import android.arch.lifecycle.MutableLiveData +import io.reactivex.Completable +import io.reactivex.Flowable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.disposables.Disposable +import ru.touchin.livedata.destroyable.BaseDestroyable +import ru.touchin.livedata.destroyable.Destroyable +import ru.touchin.livedata.event.CompletableEvent +import ru.touchin.livedata.event.MaybeEvent +import ru.touchin.livedata.event.ObservableEvent +import ru.touchin.livedata.event.SingleEvent + +class BaseLiveDataDispatcher(private val destroyable: BaseDestroyable = BaseDestroyable()) : LiveDataDispatcher, Destroyable by destroyable { + + override fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable { + liveData.value = ObservableEvent.Loading(liveData.value?.data) + return untilDestroy( + { data -> liveData.value = ObservableEvent.Success(data) }, + { throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) }, + { liveData.value = ObservableEvent.Completed(liveData.value?.data) }) + } + + override fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable { + liveData.value = ObservableEvent.Loading(liveData.value?.data) + return untilDestroy( + { data -> liveData.value = ObservableEvent.Success(data) }, + { throwable -> liveData.value = ObservableEvent.Error(throwable, liveData.value?.data) }, + { liveData.value = ObservableEvent.Completed(liveData.value?.data) }) + } + + override fun Single.dispatchTo(liveData: MutableLiveData>): Disposable { + liveData.value = SingleEvent.Loading(liveData.value?.data) + return untilDestroy( + { data -> liveData.value = SingleEvent.Success(data) }, + { throwable -> liveData.value = SingleEvent.Error(throwable, liveData.value?.data) }) + } + + override fun Completable.dispatchTo(liveData: MutableLiveData): Disposable { + liveData.value = CompletableEvent.Loading + return untilDestroy( + { liveData.value = CompletableEvent.Completed }, + { throwable -> liveData.value = CompletableEvent.Error(throwable) }) + } + + override fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable { + liveData.value = MaybeEvent.Loading(liveData.value?.data) + return untilDestroy( + { data -> liveData.value = MaybeEvent.Success(data) }, + { throwable -> liveData.value = MaybeEvent.Error(throwable, liveData.value?.data) }, + { liveData.value = MaybeEvent.Completed(liveData.value?.data) }) + } + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt new file mode 100644 index 0000000..e1d7d8f --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/dispatcher/LiveDataDispatcher.kt @@ -0,0 +1,27 @@ +package ru.touchin.livedata.dispatcher + +import android.arch.lifecycle.MutableLiveData +import io.reactivex.Completable +import io.reactivex.Flowable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.disposables.Disposable +import ru.touchin.livedata.event.CompletableEvent +import ru.touchin.livedata.event.MaybeEvent +import ru.touchin.livedata.event.ObservableEvent +import ru.touchin.livedata.event.SingleEvent + +interface LiveDataDispatcher { + + fun Flowable.dispatchTo(liveData: MutableLiveData>): Disposable + + fun Observable.dispatchTo(liveData: MutableLiveData>): Disposable + + fun Single.dispatchTo(liveData: MutableLiveData>): Disposable + + fun Completable.dispatchTo(liveData: MutableLiveData): Disposable + + fun Maybe.dispatchTo(liveData: MutableLiveData>): Disposable + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt new file mode 100644 index 0000000..d0832ef --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/CompletableEvent.kt @@ -0,0 +1,14 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Completable]. + */ +sealed class CompletableEvent { + + object Loading: CompletableEvent() + + object Completed: CompletableEvent() + + data class Error(val throwable: Throwable): CompletableEvent() + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt new file mode 100644 index 0000000..0f88077 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/MaybeEvent.kt @@ -0,0 +1,16 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Maybe]. + */ +sealed class MaybeEvent(open val data: T?) { + + class Loading(data: T?): MaybeEvent(data) + + class Success(override val data: T): MaybeEvent(data) + + class Error(val throwable: Throwable, data: T?): MaybeEvent(data) + + class Completed(data: T?): MaybeEvent(data) + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt new file mode 100644 index 0000000..5a6b8e2 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/ObservableEvent.kt @@ -0,0 +1,16 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Observable]. + */ +sealed class ObservableEvent(open val data: T?) { + + class Loading(data: T? = null): ObservableEvent(data) + + class Success(override val data: T): ObservableEvent(data) + + class Error(val throwable: Throwable, data: T? = null): ObservableEvent(data) + + class Completed(data: T? = null): ObservableEvent(data) + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt new file mode 100644 index 0000000..8b53f63 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/livedata/event/SingleEvent.kt @@ -0,0 +1,14 @@ +package ru.touchin.livedata.event + +/** + * Event class that emits from [io.reactivex.Single]. + */ +sealed class SingleEvent(open val data: T?) { + + class Loading(data: T?): SingleEvent(data) + + class Success(override val data: T): SingleEvent(data) + + class Error(val throwable: Throwable, data: T?): SingleEvent(data) + +} diff --git a/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt b/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt new file mode 100644 index 0000000..e27a860 --- /dev/null +++ b/lifecycle-rx/src/main/java/ru/touchin/templates/viewmodel/RxViewModel.kt @@ -0,0 +1,24 @@ +package ru.touchin.templates.viewmodel + +import android.arch.lifecycle.ViewModel +import android.support.annotation.CallSuper +import ru.touchin.livedata.dispatcher.BaseLiveDataDispatcher +import ru.touchin.livedata.dispatcher.LiveDataDispatcher +import ru.touchin.livedata.destroyable.BaseDestroyable +import ru.touchin.livedata.destroyable.Destroyable + +/** + * Base class of ViewModel with [io.reactivex.disposables.Disposable] handling. + */ +open class RxViewModel( + private val destroyable: BaseDestroyable = BaseDestroyable(), + private val liveDataDispatcher: BaseLiveDataDispatcher = BaseLiveDataDispatcher(destroyable) +) : ViewModel(), Destroyable by destroyable, LiveDataDispatcher by liveDataDispatcher { + + @CallSuper + override fun onCleared() { + super.onCleared() + destroyable.onDestroy() + } + +} diff --git a/lifecycle-rx/src/main/res/values/strings.xml b/lifecycle-rx/src/main/res/values/strings.xml new file mode 100644 index 0000000..b340b3d --- /dev/null +++ b/lifecycle-rx/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + lifecycle-rx + diff --git a/logging/build.gradle b/logging/build.gradle index 217c17b..69333c1 100644 --- a/logging/build.gradle +++ b/logging/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/navigation/build.gradle b/navigation/build.gradle index 26d4258..8a95e1c 100644 --- a/navigation/build.gradle +++ b/navigation/build.gradle @@ -1,10 +1,11 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java deleted file mode 100644 index 8d68057..0000000 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/OnFragmentStartedListener.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package ru.touchin.roboswag.components.navigation; - -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; - -/** - * Created by Gavriil Sitnikov on 08/10/2014. - * Base interface to listen child fragments start. - * Usually it helps to determine that fragment have showed on screen and we can change {@link android.app.Activity}'s navigation state for example. - */ -public interface OnFragmentStartedListener { - - /** - * Calls by child fragment (added via {@link android.support.v4.app.FragmentManager}) on it'sstart. - * - * @param fragment Child fragment which called this method. - */ - void onFragmentStarted(@NonNull Fragment fragment); - -} diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java index e2d88f6..090f9c0 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/SimpleActionBarDrawerToggle.java @@ -29,6 +29,7 @@ import android.view.MenuItem; import android.view.View; import ru.touchin.roboswag.components.navigation.activities.BaseActivity; +import ru.touchin.roboswag.components.navigation.activities.OnBackPressedListener; import ru.touchin.roboswag.components.utils.UiUtils; /** @@ -36,7 +37,7 @@ import ru.touchin.roboswag.components.utils.UiUtils; * Simple realization of one-side {@link ActionBarDrawerToggle}. */ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle - implements FragmentManager.OnBackStackChangedListener, BaseActivity.OnBackPressedListener { + implements FragmentManager.OnBackStackChangedListener, OnBackPressedListener { @NonNull private final BaseActivity activity; diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java index ee39486..2e09da4 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/BaseActivity.java @@ -25,7 +25,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.ArraySet; import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; import java.util.Set; @@ -96,13 +95,9 @@ public abstract class BaseActivity extends AppCompatActivity { } @Override - public boolean onOptionsItemSelected(@NonNull final MenuItem item) { - if (item.getItemId() == android.R.id.home) { - onBackPressed(); - return true; - } else { - return super.onOptionsItemSelected(item); - } + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; } public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) { @@ -123,18 +118,4 @@ public abstract class BaseActivity extends AppCompatActivity { super.onBackPressed(); } - /* - * Interface to be implemented for someone who want to intercept device back button pressing event. - */ - public interface OnBackPressedListener { - - /** - * Calls when user presses device back button. - * - * @return True if it is processed by this object. - */ - boolean onBackPressed(); - - } - } diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java new file mode 100644 index 0000000..0f052f2 --- /dev/null +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/activities/OnBackPressedListener.java @@ -0,0 +1,15 @@ +package ru.touchin.roboswag.components.navigation.activities; + +/** + * Interface to be implemented for someone who want to intercept device back button pressing event. + */ +public interface OnBackPressedListener { + + /** + * Calls when user presses device back button. + * + * @return True if it is processed by this object. + */ + boolean onBackPressed(); + +} diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java index 065f06b..34e4b32 100644 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java +++ b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewControllerFragment.java @@ -20,6 +20,8 @@ package ru.touchin.roboswag.components.navigation.fragments; import android.animation.Animator; +import android.annotation.SuppressLint; +import android.arch.lifecycle.Lifecycle; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; @@ -27,8 +29,10 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; +import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.view.LayoutInflater; import android.view.Menu; @@ -41,6 +45,7 @@ import android.widget.FrameLayout; import java.lang.reflect.Constructor; +import ru.touchin.roboswag.components.navigation.BuildConfig; import ru.touchin.roboswag.components.navigation.viewcontrollers.ViewController; import ru.touchin.roboswag.core.log.Lc; import ru.touchin.roboswag.core.log.LcGroup; @@ -54,24 +59,15 @@ import ru.touchin.roboswag.core.utils.ShouldNotHappenException; * @param Type of {@link FragmentActivity} where fragment could be attached to. */ @SuppressWarnings("PMD.TooManyMethods") -public class ViewControllerFragment extends ViewFragment { +public class ViewControllerFragment extends Fragment { private static final String VIEW_CONTROLLER_CLASS_EXTRA = "VIEW_CONTROLLER_CLASS_EXTRA"; private static final String VIEW_CONTROLLER_STATE_EXTRA = "VIEW_CONTROLLER_STATE_EXTRA"; - private static boolean inDebugMode; private static long acceptableUiCalculationTime = 100; - /** - * Enables debugging features like serialization of {@link #getState()} every creation. - */ - public static void setInDebugMode() { - inDebugMode = true; - } - /** * Sets acceptable UI calculation time so there will be warnings in logs if ViewController's inflate/layout actions will take more than that time. - * Works only if {@link #setInDebugMode()} called. * It's 100ms by default. */ public static void setAcceptableUiCalculationTime(final long acceptableUiCalculationTime) { @@ -113,6 +109,8 @@ public class ViewControllerFragment>) getArguments().getSerializable(VIEW_CONTROLLER_CLASS_EXTRA); state = savedInstanceState != null ? savedInstanceState.getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : (getArguments() != null ? getArguments().getParcelable(VIEW_CONTROLLER_STATE_EXTRA) : null); if (state != null) { - if (inDebugMode) { + if (BuildConfig.DEBUG) { state = reserialize(state); } } else { @@ -148,43 +146,6 @@ public class ViewControllerFragment constructor = viewControllerClass.getConstructors()[0]; - final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view); - final long creationTime = inDebugMode ? SystemClock.elapsedRealtime() : 0; - try { - switch (constructor.getParameterTypes().length) { - case 2: - return (ViewController) constructor.newInstance(creationContext, savedInstanceState); - case 3: - return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState); - default: - throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length); - } - } catch (@NonNull final Exception exception) { - throw new ShouldNotHappenException(exception); - } finally { - checkCreationTime(creationTime); - } - } - - private void checkCreationTime(final long creationTime) { - if (inDebugMode) { - final long creationPeriod = SystemClock.elapsedRealtime() - creationTime; - if (creationPeriod > acceptableUiCalculationTime) { - LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); - } - } - } - @NonNull @Override public final View onCreateView( @@ -199,7 +160,7 @@ public class ViewControllerFragment constructor = viewControllerClass.getConstructors()[0]; + final ViewController.CreationContext creationContext = new ViewController.CreationContext(activity, this, view); + final long creationTime = BuildConfig.DEBUG ? SystemClock.elapsedRealtime() : 0; + try { + switch (constructor.getParameterTypes().length) { + case 2: + return (ViewController) constructor.newInstance(creationContext, savedInstanceState); + case 3: + return (ViewController) constructor.newInstance(this, creationContext, savedInstanceState); + default: + throw new ShouldNotHappenException("Wrong constructor parameters count: " + constructor.getParameterTypes().length); + } + } catch (@NonNull final Exception exception) { + throw new ShouldNotHappenException(exception); + } finally { + checkCreationTime(creationTime); + } + } + + private void checkCreationTime(final long creationTime) { + if (BuildConfig.DEBUG) { + final long creationPeriod = SystemClock.elapsedRealtime() - creationTime; + if (creationPeriod > acceptableUiCalculationTime) { + LcGroup.UI_METRICS.w("Creation of %s took too much: %dms", viewControllerClass, creationPeriod); + } + } + } + private static class PlaceholderView extends FrameLayout { @NonNull @@ -353,7 +389,7 @@ public class ViewControllerFragment 0) { + if (BuildConfig.DEBUG && lastMeasureTime > 0) { final long layoutTime = SystemClock.uptimeMillis() - lastMeasureTime; if (layoutTime > acceptableUiCalculationTime) { LcGroup.UI_METRICS.w("Measure and layout of %s took too much: %dms", tagName, layoutTime); diff --git a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java b/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java deleted file mode 100644 index bca1f28..0000000 --- a/navigation/src/main/java/ru/touchin/roboswag/components/navigation/fragments/ViewFragment.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package ru.touchin.roboswag.components.navigation.fragments; - -import android.arch.lifecycle.Lifecycle; -import android.os.Bundle; -import android.support.annotation.CallSuper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import ru.touchin.roboswag.components.navigation.OnFragmentStartedListener; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.utils.BiConsumer; - -/** - * Created by Gavriil Sitnikov on 21/10/2015. - * Non-background fragment that have specific activity as a parent. - * - * @param Type of activity which to such fragment could be attached. - */ -public abstract class ViewFragment extends Fragment implements OnFragmentStartedListener { - - private boolean appeared; - - /** - * Returns if fragment have parent fragment. - * - * @return Returns true if fragment is in some fragment's children stack. - */ - public boolean isChildFragment() { - return getParentFragment() != null; - } - - /** - * Returns specific activity which to this fragment could be attached. - * - * @return Returns parent activity. - */ - @SuppressWarnings("unchecked") - @Nullable - protected final TActivity getBaseActivity() { - if (getActivity() == null) { - return null; - } - - try { - return (TActivity) getActivity(); - } catch (final ClassCastException exception) { - Lc.assertion(exception); - return null; - } - } - - @NonNull - @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - throw new IllegalStateException("Method onCreateView() should be overridden"); - } - - @Override - @CallSuper - public void onFragmentStarted(@NonNull final Fragment fragment) { - //do nothing - } - - @Override - public void onActivityCreated(@Nullable final Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if (getView() == null || getBaseActivity() == null) { - Lc.assertion("View and activity shouldn't be null"); - } - } - - private void callMethodAfterInstantiation(@NonNull final BiConsumer action) { - if (getView() == null || getBaseActivity() == null) { - Lc.assertion("View and activity shouldn't be null"); - return; - } - try { - action.accept(getView(), getBaseActivity()); - } catch (final Exception exception) { - Lc.assertion(exception); - } - } - - @Deprecated - @Override - public void onStart() { - super.onStart(); - callMethodAfterInstantiation(this::onStart); - } - - /** - * Replacement of {@link #onStart} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - @SuppressWarnings("RestrictedApi") - //RestrictedApi: we need isMenuVisible() to check analytics rightly! - protected void onStart(@NonNull final View view, @NonNull final TActivity activity) { - if (getParentFragment() instanceof OnFragmentStartedListener) { - ((OnFragmentStartedListener) getParentFragment()).onFragmentStarted(this); - } else if (activity instanceof OnFragmentStartedListener) { - ((OnFragmentStartedListener) activity).onFragmentStarted(this); - } - if (!appeared && isMenuVisible()) { - onAppear(view, activity); - } - } - - /** - * Called when fragment is moved in started state and it's {@link #isMenuVisible()} sets to true. - * Usually it is indicating that user can't see fragment on screen and useful to track analytics events. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - protected void onAppear(@NonNull final View view, @NonNull final TActivity activity) { - appeared = true; - } - - @Deprecated - @Override - public void onResume() { - super.onResume(); - callMethodAfterInstantiation(this::onResume); - } - - /** - * Replacement of {@link #onResume} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - protected void onResume(@NonNull final View view, @NonNull final TActivity activity) { - //do nothing - } - - @Override - public void setMenuVisibility(final boolean menuVisible) { - super.setMenuVisibility(menuVisible); - if (getBaseActivity() != null && getView() != null) { - final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED); - if (!appeared && menuVisible && started) { - onAppear(getView(), getBaseActivity()); - } - if (appeared && (!menuVisible || !started)) { - onDisappear(getView(), getBaseActivity()); - } - } - } - - @Deprecated - @Override - public void onPause() { - callMethodAfterInstantiation(this::onPause); - super.onPause(); - } - - /** - * Replacement of {@link #onPause} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - protected void onPause(@NonNull final View view, @NonNull final TActivity activity) { - // do nothing - } - - /** - * Called when fragment is moved in stopped state or it's {@link #isMenuVisible()} sets to false. - * Usually it is indicating that user can't see fragment on screen and useful to track analytics events. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - protected void onDisappear(@NonNull final View view, @NonNull final TActivity activity) { - appeared = false; - } - - @Deprecated - @Override - public void onStop() { - callMethodAfterInstantiation(this::onStop); - super.onStop(); - } - - /** - * Replacement of {@link #onStop} with non null activity as first parameter. - * - * @param view Instantiated view. - * @param activity Activity which fragment attached to. - */ - @CallSuper - protected void onStop(@NonNull final View view, @NonNull final TActivity activity) { - if (appeared) { - onDisappear(view, activity); - } - } - -} diff --git a/sample/build.gradle b/sample/build.gradle index 65044a9..24ac6ff 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -3,14 +3,16 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 27 + compileSdkVersion versions.compileSdk + defaultConfig { applicationId "ru.touchin.roboswag.components" - minSdkVersion 16 - targetSdkVersion 27 + minSdkVersion versions.minSdk + targetSdkVersion versions.compileSdk versionCode 1 versionName "1.0" } + buildTypes { release { minifyEnabled false @@ -21,6 +23,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:appcompat-v7:27.1.1' + + implementation "com.android.support:appcompat-v7:$versions.supportLibrary" implementation 'com.android.support.constraint:constraint-layout:1.1.2' } diff --git a/settings.gradle b/settings.gradle index 9e1f446..c6b0741 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':sample', ':utils', ':logging', ':navigation', ':storable' +include ':sample', ':utils', ':logging', ':navigation', ':storable', ':api-logansquare', ':lifecycle-common', ':lifecycle-rx' diff --git a/storable/build.gradle b/storable/build.gradle index 2a6be21..18cb730 100644 --- a/storable/build.gradle +++ b/storable/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/utils/build.gradle b/utils/build.gradle index 217c17b..69333c1 100644 --- a/utils/build.gradle +++ b/utils/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion versions.compileSdk defaultConfig { - minSdkVersion 16 + minSdkVersion versions.minSdk } compileOptions { diff --git a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java b/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java deleted file mode 100644 index c13ac22..0000000 --- a/utils/src/main/java/ru/touchin/roboswag/core/utils/BiConsumer.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.touchin.roboswag.core.utils; - -import android.support.annotation.Nullable; - -/** - * A functional interface (callback) that accepts two values (of possibly different types). - * @param the first value type - * @param the second value type - */ -public interface BiConsumer { - - /** - * Performs an operation on the given values. - * @param t1 the first value - * @param t2 the second value - * @throws Exception on error - */ - void accept(@Nullable T1 t1, @Nullable T2 t2) throws Exception; -} diff --git a/utils/src/main/java/ru/touchin/templates/DeviceUtils.java b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java new file mode 100644 index 0000000..8d8284d --- /dev/null +++ b/utils/src/main/java/ru/touchin/templates/DeviceUtils.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2016 Touch Instinct + * + * This file is part of RoboSwag library. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ru.touchin.templates; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Process; +import android.support.annotation.NonNull; +import android.telephony.TelephonyManager; + +/** + * Utility class that is providing common methods related to android device. + */ +public final class DeviceUtils { + + /** + * Detects active network type. + * + * @param context Application context + * @return Active network type {@link NetworkType} + */ + @NonNull + public static NetworkType getNetworkType(@NonNull final Context context) { + if (context.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, Process.myPid(), Process.myUid()) + != PackageManager.PERMISSION_GRANTED) { + return NetworkType.UNKNOWN; + } + final ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + @SuppressLint("MissingPermission") final NetworkInfo info = cm.getActiveNetworkInfo(); + if (info == null || !info.isConnected()) { + return NetworkType.NONE; + } + if (info.getType() == ConnectivityManager.TYPE_WIFI) { + return NetworkType.WI_FI; + } + if (info.getType() == ConnectivityManager.TYPE_MOBILE) { + final int networkType = info.getSubtype(); + switch (networkType) { + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + return NetworkType.MOBILE_2G; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + return NetworkType.MOBILE_3G; + case TelephonyManager.NETWORK_TYPE_LTE: + case 19: // NETWORK_TYPE_LTE_CA is hide + return NetworkType.MOBILE_LTE; + case TelephonyManager.NETWORK_TYPE_UNKNOWN: + default: + return NetworkType.UNKNOWN; + } + } + return NetworkType.UNKNOWN; + } + + /** + * Detects if some network connected. + * + * @param context Application context + * @return true if network connected, false otherwise. + */ + public static boolean isNetworkConnected(@NonNull final Context context) { + return getNetworkType(context) != NetworkType.NONE; + } + + private DeviceUtils() { + } + + /** + * Available network types. + */ + public enum NetworkType { + /** + * Mobile 2G network. + */ + MOBILE_2G("2g"), + /** + * Mobile 3G network. + */ + MOBILE_3G("3g"), + /** + * Mobile LTE network. + */ + MOBILE_LTE("lte"), + /** + * Wi-Fi network. + */ + WI_FI("Wi-Fi"), + /** + * Unknown network type. + */ + UNKNOWN("unknown"), + /** + * No network. + */ + NONE("none"); + + @NonNull + private final String name; + + NetworkType(@NonNull final String name) { + this.name = name; + } + + /** + * @return Network type readable name. + */ + @NonNull + public String getName() { + return name; + } + + } + +}