Merge 01a75bffbf into e6dd2c5bcd
|
|
@ -1,30 +1,8 @@
|
|||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
/*/build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
.gradle
|
||||
.idea
|
||||
.DS_Store
|
||||
/captures
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "BuildScripts"]
|
||||
path = BuildScripts
|
||||
url = git@github.com:TouchInstinct/BuildScripts.git
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3fe4b2a1aea149050b34c60ec7e2a876dfd8c0b2
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.templates.logansquare"/>
|
||||
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<BigDecimal> {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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<T extends Enum & LoganSquareEnum> extends StringBasedTypeConverter<T> {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<DateTime> {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<ResponseBody, ?> responseBodyConverter(@NonNull final Type type,
|
||||
@NonNull final Annotation[] annotations,
|
||||
@NonNull final Retrofit retrofit) {
|
||||
return new LoganSquareJsonResponseBodyConverter<>(type);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Converter<?, RequestBody> requestBodyConverter(@NonNull final Type type,
|
||||
@NonNull final Annotation[] parameterAnnotations,
|
||||
@NonNull final Annotation[] methodAnnotations,
|
||||
@NonNull final Retrofit retrofit) {
|
||||
return new LoganSquareRequestBodyConverter<>();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Converter<?, String> 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<T> extends JsonResponseBodyConverter<T> {
|
||||
|
||||
@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<T> extends JsonRequestBodyConverter<T> {
|
||||
|
||||
@Override
|
||||
protected void writeValueToByteArray(@NonNull final T value, @NonNull final ByteArrayOutputStream byteArrayOutputStream) throws IOException {
|
||||
LoganSquare.serialize(value, byteArrayOutputStream);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class LoganSquareStringEnumConverter<T> implements Converter<T, String> {
|
||||
|
||||
@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<T>) value.getClass()).serialize(value, null, false, generator);
|
||||
generator.close();
|
||||
return writer.toString().replaceAll("\"", "");
|
||||
} finally {
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <T> Storable<String, T, String> jsonStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonClass,
|
||||
@NonNull final SharedPreferences preferences) {
|
||||
return new Storable.Builder<String, T, String>(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>())
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> NonNullStorable<String, T, String> jsonStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final T defaultValue) {
|
||||
return new Storable.Builder<String, T, String>(name, jsonClass, String.class, new PreferenceStore<>(preferences), new JsonConverter<>())
|
||||
.setObserveStrategy(Storable.ObserveStrategy.CACHE_ACTUAL_VALUE)
|
||||
.setDefaultValue(defaultValue)
|
||||
.build();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <T> Storable<String, List<T>, String> jsonListStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> 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 <T> NonNullStorable<String, List<T>, String> jsonListStorable(@NonNull final String name,
|
||||
@NonNull final Class<T> jsonListItemClass,
|
||||
@NonNull final SharedPreferences preferences,
|
||||
@NonNull final List<T> 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<TJsonObject> implements Converter<TJsonObject, String> {
|
||||
|
||||
@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<TJsonObject>) jsonObjectClass);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JsonListConverter<T> implements Converter<List<T>, String> {
|
||||
|
||||
@NonNull
|
||||
private final Class<T> itemClass;
|
||||
|
||||
public JsonListConverter(@NonNull final Class<T> itemClass) {
|
||||
this.itemClass = itemClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public String toStoreObject(@NonNull final Type jsonObjectType, @NonNull final Type stringType, @Nullable final List<T> object) {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LoganSquare.serialize(object, itemClass);
|
||||
} catch (final IOException exception) {
|
||||
throw new ShouldNotHappenException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<T> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <T> Type of body object.
|
||||
*/
|
||||
public abstract class JsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
|
@ -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 <T> Type of response object.
|
||||
*/
|
||||
public abstract class JsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
53
build.gradle
|
|
@ -1,24 +1,39 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion compileSdk
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.2.61'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'de.aaschmid:gradle-cpd-plugin:1.0'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':libraries:core')
|
||||
|
||||
compileOnly "com.android.support:appcompat-v7:$supportLibraryVersion"
|
||||
compileOnly "com.android.support:recyclerview-v7:$supportLibraryVersion"
|
||||
|
||||
compileOnly "io.reactivex:rxandroid:$rxAndroidVersion"
|
||||
compileOnly "io.reactivex:rxjava:$rxJavaVersion"
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "http://dl.bintray.com/touchin/touchin-tools" }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
ext {
|
||||
versions = [
|
||||
compileSdk : 27,
|
||||
minSdk : 16,
|
||||
supportLibrary: '27.1.1',
|
||||
navigation : '1.0.0-alpha04',
|
||||
lifecycle : '1.1.1',
|
||||
dagger : '2.16',
|
||||
retrofit : '2.4.0',
|
||||
rxJava : '2.1.17',
|
||||
rxAndroid : '2.0.2',
|
||||
crashlytics : '2.9.4'
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
org.gradle.parallel=true
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#Sun Aug 05 23:37:20 MSK 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:recyclerview-v7:$versions.supportLibrary"
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.components.extensions"/>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.touchin.roboswag.components.extensions
|
||||
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.properties.ObservableProperty
|
||||
import kotlin.properties.ReadWriteProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Simple observable delegate only for notification of new value.
|
||||
*/
|
||||
inline fun <T> Delegates.observable(
|
||||
initialValue: T,
|
||||
crossinline onChange: (newValue: T) -> Unit
|
||||
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
|
||||
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(newValue)
|
||||
}
|
||||
|
||||
inline fun <T> Delegates.distinctUntilChanged(
|
||||
initialValue: T,
|
||||
crossinline onChange: (newValue: T) -> Unit
|
||||
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
|
||||
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) =
|
||||
if (newValue != null && oldValue != newValue) onChange(newValue) else Unit
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package ru.touchin.roboswag.components.extensions
|
||||
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
|
||||
private const val RIPPLE_EFFECT_DELAY = 150L
|
||||
|
||||
/**
|
||||
* Sets click listener to view. On click it will call something after delay.
|
||||
*
|
||||
* @param delay Delay after which click listener will be called;
|
||||
* @param listener Click listener.
|
||||
*/
|
||||
fun View.setOnRippleClickListener(delay: Long = RIPPLE_EFFECT_DELAY, listener: (View) -> Unit) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setOnClickListener { view -> postDelayed({ if (hasWindowFocus()) listener(view) }, delay) }
|
||||
} else {
|
||||
setOnClickListener(listener)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package ru.touchin.roboswag.components.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.annotation.ColorRes
|
||||
import android.support.annotation.DrawableRes
|
||||
import android.support.annotation.IdRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
|
||||
fun <T : View> RecyclerView.ViewHolder.findViewById(@IdRes resId: Int): T = itemView.findViewById(resId)
|
||||
|
||||
val RecyclerView.ViewHolder.context: Context
|
||||
get() = itemView.context
|
||||
|
||||
fun RecyclerView.ViewHolder.getText(@StringRes resId: Int): CharSequence = context.getText(resId)
|
||||
|
||||
fun RecyclerView.ViewHolder.getString(@StringRes resId: Int): String = context.getString(resId)
|
||||
|
||||
@SuppressWarnings("SpreadOperator") // it's OK for small arrays
|
||||
fun RecyclerView.ViewHolder.getString(@StringRes resId: Int, vararg args: Any): String = context.getString(resId, *args)
|
||||
|
||||
@ColorInt
|
||||
fun RecyclerView.ViewHolder.getColor(@ColorRes resId: Int): Int = ContextCompat.getColor(context, resId)
|
||||
|
||||
fun RecyclerView.ViewHolder.getColorStateList(@ColorRes resId: Int): ColorStateList? = ContextCompat.getColorStateList(context, resId)
|
||||
|
||||
fun RecyclerView.ViewHolder.getDrawable(@DrawableRes resId: Int): Drawable? = ContextCompat.getDrawable(context, resId)
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.lifecycle.common"/>
|
||||
|
|
@ -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<T> : MutableLiveData<T>() {
|
||||
|
||||
private val pending = AtomicBoolean(false)
|
||||
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<T?>) {
|
||||
super.observe(owner, Observer { value ->
|
||||
if (pending.compareAndSet(true, false)) {
|
||||
observer.onChanged(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun setValue(value: T) {
|
||||
pending.set(true)
|
||||
super.setValue(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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}.
|
||||
* <p>
|
||||
* 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.")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T
|
||||
= (creators[modelClass]?.get() as? T) ?: throw IllegalArgumentException("Unknown model class $modelClass")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.touchin.templates.viewmodel
|
||||
|
||||
interface ViewModelFactoryProvider {
|
||||
|
||||
val viewModelFactory: ViewModelFactory
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.lifecycle.rx"/>
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package ru.touchin.livedata.destroyable
|
||||
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.Maybe
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
||||
/**
|
||||
* Created by Oksana Pokrovskaya on 7/03/18.
|
||||
* Simple implementation of [Destroyable]. Could be used to not implement interface but use such object inside.
|
||||
*/
|
||||
open class BaseDestroyable : Destroyable {
|
||||
|
||||
private val subscriptions = CompositeDisposable()
|
||||
|
||||
override fun clearSubscriptions() = subscriptions.clear()
|
||||
|
||||
/**
|
||||
* Call it on parent's onDestroy method.
|
||||
*/
|
||||
fun onDestroy() = subscriptions.dispose()
|
||||
|
||||
override fun <T> Flowable<T>.untilDestroy(
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit,
|
||||
onComplete: () -> Unit
|
||||
): Disposable = observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onNext, onError, onComplete)
|
||||
.also { subscriptions.add(it) }
|
||||
|
||||
override fun <T> Observable<T>.untilDestroy(
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit,
|
||||
onComplete: () -> Unit
|
||||
): Disposable = observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onNext, onError, onComplete)
|
||||
.also { subscriptions.add(it) }
|
||||
|
||||
override fun <T> Single<T>.untilDestroy(
|
||||
onSuccess: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit
|
||||
): Disposable = observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError)
|
||||
.also { subscriptions.add(it) }
|
||||
|
||||
override fun Completable.untilDestroy(
|
||||
onComplete: () -> Unit,
|
||||
onError: (Throwable) -> Unit
|
||||
): Disposable = observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onComplete, onError)
|
||||
.also { subscriptions.add(it) }
|
||||
|
||||
override fun <T> Maybe<T>.untilDestroy(
|
||||
onSuccess: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit,
|
||||
onComplete: () -> Unit
|
||||
): Disposable = observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(onSuccess, onError, onComplete)
|
||||
.also { subscriptions.add(it) }
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
package ru.touchin.livedata.destroyable
|
||||
|
||||
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 io.reactivex.internal.functions.Functions
|
||||
import ru.touchin.roboswag.core.log.Lc
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException
|
||||
|
||||
/**
|
||||
* Created by Oksana Pokrovskaya on 7/03/18.
|
||||
* Interface that should be implemented by ([android.arch.lifecycle.ViewModel] etc.)
|
||||
* to not manually manage subscriptions.
|
||||
* Use [.untilDestroy] method to subscribe to observable where you want and unsubscribe onDestroy.
|
||||
*/
|
||||
interface Destroyable {
|
||||
|
||||
companion object {
|
||||
private fun getActionThrowableForAssertion(codePoint: String, method: String = "untilDestroy"): (Throwable) -> Unit = { throwable ->
|
||||
Lc.assertion(ShouldNotHappenException("Unexpected error on $method at $codePoint", throwable))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all subscriptions
|
||||
*/
|
||||
fun clearSubscriptions()
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that observable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events.
|
||||
* Don't forget to process errors if observable can emit them.
|
||||
*
|
||||
* @param onNext Action which will raise on every [io.reactivex.Emitter.onNext] item;
|
||||
* @param onError Action which will raise on every [io.reactivex.Emitter.onError] throwable;
|
||||
* @param onComplete Action which will raise on every [io.reactivex.Emitter.onComplete] item;
|
||||
* @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
fun <T> Flowable<T>.untilDestroy(
|
||||
onNext: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
|
||||
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)),
|
||||
onComplete: () -> Unit = Functions.EMPTY_ACTION::run
|
||||
): Disposable
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that observable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to the observable and calls onNextAction and onErrorAction on observable events.
|
||||
* Don't forget to process errors if observable can emit them.
|
||||
*
|
||||
* @param onNext Action which will raise on every [io.reactivex.Emitter.onNext] item;
|
||||
* @param onError Action which will raise on every [io.reactivex.Emitter.onError] throwable;
|
||||
* @param onComplete Action which will raise on every [io.reactivex.Emitter.onComplete] item;
|
||||
* @return [Disposable] which is wrapping source observable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
fun <T> Observable<T>.untilDestroy(
|
||||
onNext: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
|
||||
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)),
|
||||
onComplete: () -> Unit = Functions.EMPTY_ACTION::run
|
||||
): Disposable
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that single won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to the single and calls onSuccessAction and onErrorAction on single events.
|
||||
* Don't forget to process errors if single can emit them.
|
||||
*
|
||||
* @param onSuccess Action which will raise on every [io.reactivex.SingleEmitter.onSuccess] item;
|
||||
* @param onError Action which will raise on every [io.reactivex.SingleEmitter.onError] throwable;
|
||||
* @return [Disposable] which is wrapping source single to unsubscribe from it onDestroy.
|
||||
*/
|
||||
fun <T> Single<T>.untilDestroy(
|
||||
onSuccess: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
|
||||
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2))
|
||||
): Disposable
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that completable won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to the completable and calls onCompletedAction and onErrorAction on completable events.
|
||||
* Don't forget to process errors if completable can emit them.
|
||||
*
|
||||
* @param onComplete Action which will raise on every [io.reactivex.CompletableEmitter.onComplete] item;
|
||||
* @param onError Action which will raise on every [io.reactivex.CompletableEmitter.onError] throwable;
|
||||
* @return [Disposable] which is wrapping source completable to unsubscribe from it onDestroy.
|
||||
*/
|
||||
fun Completable.untilDestroy(
|
||||
onComplete: () -> Unit = Functions.EMPTY_ACTION::run,
|
||||
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2))
|
||||
): Disposable
|
||||
|
||||
/**
|
||||
* Method should be used to guarantee that maybe won't be subscribed after onDestroy.
|
||||
* It is automatically subscribing to the maybe and calls onSuccessAction and onErrorAction on maybe events.
|
||||
* Don't forget to process errors if completable can emit them.
|
||||
*
|
||||
* @param onSuccess Action which will raise on every [io.reactivex.MaybeEmitter.onSuccess] ()} item;
|
||||
* @param onError Action which will raise on every [io.reactivex.MaybeEmitter.onError] throwable;
|
||||
* @param onComplete Action which will raise on every [io.reactivex.MaybeEmitter.onComplete] item;
|
||||
* @return [Disposable] which is wrapping source maybe to unsubscribe from it onDestroy.
|
||||
*/
|
||||
fun <T> Maybe<T>.untilDestroy(
|
||||
onSuccess: (T) -> Unit = Functions.emptyConsumer<T>()::accept,
|
||||
onError: (Throwable) -> Unit = getActionThrowableForAssertion(Lc.getCodePoint(this, 2)),
|
||||
onComplete: () -> Unit = Functions.EMPTY_ACTION::run
|
||||
): Disposable
|
||||
|
||||
}
|
||||
|
|
@ -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.templates.livedata.event.CompletableEvent
|
||||
import ru.touchin.templates.livedata.event.MaybeEvent
|
||||
import ru.touchin.templates.livedata.event.ObservableEvent
|
||||
import ru.touchin.templates.livedata.event.SingleEvent
|
||||
|
||||
class BaseLiveDataDispatcher(private val destroyable: BaseDestroyable = BaseDestroyable()) : LiveDataDispatcher, Destroyable by destroyable {
|
||||
|
||||
override fun <T> Flowable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): 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 <T> Observable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): 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 <T> Single<out T>.dispatchTo(liveData: MutableLiveData<SingleEvent<T>>): 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<CompletableEvent>): Disposable {
|
||||
liveData.value = CompletableEvent.Loading
|
||||
return untilDestroy(
|
||||
{ liveData.value = CompletableEvent.Completed },
|
||||
{ throwable -> liveData.value = CompletableEvent.Error(throwable) })
|
||||
}
|
||||
|
||||
override fun <T> Maybe<out T>.dispatchTo(liveData: MutableLiveData<MaybeEvent<T>>): 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) })
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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.templates.livedata.event.CompletableEvent
|
||||
import ru.touchin.templates.livedata.event.MaybeEvent
|
||||
import ru.touchin.templates.livedata.event.ObservableEvent
|
||||
import ru.touchin.templates.livedata.event.SingleEvent
|
||||
|
||||
interface LiveDataDispatcher {
|
||||
|
||||
fun <T> Flowable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable
|
||||
|
||||
fun <T> Observable<out T>.dispatchTo(liveData: MutableLiveData<ObservableEvent<T>>): Disposable
|
||||
|
||||
fun <T> Single<out T>.dispatchTo(liveData: MutableLiveData<SingleEvent<T>>): Disposable
|
||||
|
||||
fun Completable.dispatchTo(liveData: MutableLiveData<CompletableEvent>): Disposable
|
||||
|
||||
fun <T> Maybe<out T>.dispatchTo(liveData: MutableLiveData<MaybeEvent<T>>): Disposable
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package ru.touchin.templates.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()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Maybe].
|
||||
*/
|
||||
sealed class MaybeEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T?): MaybeEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): MaybeEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T?): MaybeEvent<T>(data)
|
||||
|
||||
class Completed<out T>(data: T?): MaybeEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Observable].
|
||||
*/
|
||||
sealed class ObservableEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): ObservableEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
class Completed<out T>(data: T? = null): ObservableEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package ru.touchin.templates.livedata.event
|
||||
|
||||
/**
|
||||
* Event class that emits from [io.reactivex.Single].
|
||||
*/
|
||||
sealed class SingleEvent<out T>(open val data: T?) {
|
||||
|
||||
class Loading<out T>(data: T?): SingleEvent<T>(data)
|
||||
|
||||
class Success<out T>(override val data: T): SingleEvent<T>(data)
|
||||
|
||||
class Error<out T>(val throwable: Throwable, data: T?): SingleEvent<T>(data)
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">lifecycle-rx</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.android.support:support-annotations:$versions.supportLibrary"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<manifest package="ru.touchin.roboswag.core.log" />
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Simple {@link LogProcessor} implementation which is logging messages to console (logcat).
|
||||
*/
|
||||
public class ConsoleLogProcessor extends LogProcessor {
|
||||
|
||||
private static final int MAX_LOG_LENGTH = 4000;
|
||||
|
||||
public ConsoleLogProcessor(@NonNull final LcLevel lclevel) {
|
||||
super(lclevel);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String normalize(@NonNull final String message) {
|
||||
return message.replace("\r\n", "\n").replace("\0", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"WrongConstant", "LogConditional"})
|
||||
//WrongConstant, LogConditional: level.getPriority() is not wrong constant!
|
||||
public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level,
|
||||
@NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) {
|
||||
final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : ""));
|
||||
final int length = messageToLog.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int newline = messageToLog.indexOf('\n', i);
|
||||
newline = newline != -1 ? newline : length;
|
||||
do {
|
||||
final int end = Math.min(newline, i + MAX_LOG_LENGTH);
|
||||
Log.println(level.getPriority(), tag, messageToLog.substring(i, end));
|
||||
i = end;
|
||||
}
|
||||
while (i < newline);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* 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.core.log;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* General logging utility of RoboSwag library.
|
||||
* You can initialize {@link LogProcessor} to intercept log messages and make decision how to show them.
|
||||
* Also you can specify assertions behavior to manually make application more stable in production but intercept illegal states in some
|
||||
* third-party tool to fix them later but not crash in production.
|
||||
*/
|
||||
@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName", "PMD.ShortClassName"})
|
||||
//MethodNameCheck,ShortMethodName: log methods better be 1-symbol
|
||||
public final class Lc {
|
||||
|
||||
public static final LcGroup GENERAL_LC_GROUP = new LcGroup("GENERAL");
|
||||
|
||||
public static final int STACK_TRACE_CODE_DEPTH;
|
||||
|
||||
private static boolean crashOnAssertions = true;
|
||||
@NonNull
|
||||
private static LogProcessor logProcessor = new ConsoleLogProcessor(LcLevel.ERROR);
|
||||
|
||||
static {
|
||||
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
int stackDepth;
|
||||
for (stackDepth = 0; stackDepth < stackTrace.length; stackDepth++) {
|
||||
if (stackTrace[stackDepth].getClassName().equals(Lc.class.getName())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
STACK_TRACE_CODE_DEPTH = stackDepth + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag to crash application or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)}
|
||||
* on specific {@link LcGroup#assertion(Throwable)} points of code.
|
||||
*
|
||||
* @return True if application should crash on assertion.
|
||||
*/
|
||||
public static boolean isCrashOnAssertions() {
|
||||
return crashOnAssertions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link LogProcessor} object to intercept incoming log messages (by default it returns {@link ConsoleLogProcessor}).
|
||||
*
|
||||
* @return Specific {@link LogProcessor}.
|
||||
*/
|
||||
@NonNull
|
||||
public static LogProcessor getLogProcessor() {
|
||||
return logProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize general logging behavior.
|
||||
*
|
||||
* @param logProcessor {@link LogProcessor} to intercept all log messages;
|
||||
* @param crashOnAssertions Flag to crash application
|
||||
* or pass it to {@link LogProcessor#processLogMessage(LcGroup, LcLevel, String, String, Throwable)}
|
||||
* on specific {@link LcGroup#assertion(Throwable)} points of code.
|
||||
*/
|
||||
public static void initialize(@NonNull final LogProcessor logProcessor, final boolean crashOnAssertions) {
|
||||
Lc.crashOnAssertions = crashOnAssertions;
|
||||
Lc.logProcessor = logProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void d(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.d(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.d(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void i(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.i(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.i(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void w(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.w(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.w(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void e(@NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.e(message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message via {@link #GENERAL_LC_GROUP}.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public static void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
GENERAL_LC_GROUP.e(throwable, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param message Message that is describing assertion.
|
||||
*/
|
||||
public static void assertion(@NonNull final String message) {
|
||||
GENERAL_LC_GROUP.assertion(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param throwable Exception that is describing assertion.
|
||||
*/
|
||||
public static void assertion(@NonNull final Throwable throwable) {
|
||||
GENERAL_LC_GROUP.assertion(throwable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws assertion on main thread (to avoid Rx exceptions e.g.) and cuts top causes by type of exception class.
|
||||
*
|
||||
* @param assertion Source throwable;
|
||||
* @param exceptionsClassesToCut Classes which will be cut from top of causes stack of source throwable.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static void cutAssertion(@NonNull final Throwable assertion, @NonNull final Class<? extends Throwable>... exceptionsClassesToCut) {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
final List<Throwable> processedExceptions = new ArrayList<>();
|
||||
Throwable result = assertion;
|
||||
boolean exceptionAssignableFromIgnores;
|
||||
do {
|
||||
exceptionAssignableFromIgnores = false;
|
||||
processedExceptions.add(result);
|
||||
for (final Class exceptionClass : exceptionsClassesToCut) {
|
||||
if (result.getClass().isAssignableFrom(exceptionClass)) {
|
||||
exceptionAssignableFromIgnores = true;
|
||||
result = result.getCause();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (exceptionAssignableFromIgnores && result != null && !processedExceptions.contains(result));
|
||||
Lc.assertion(result != null ? result : assertion);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line of code from where this method called.
|
||||
*
|
||||
* @param caller Object who is calling for code point;
|
||||
* @return String represents code point.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getCodePoint(@Nullable final Object caller) {
|
||||
return getCodePoint(caller, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns line of code from where this method called.
|
||||
*
|
||||
* @param caller Object who is calling for code point;
|
||||
* @param stackShift caller Shift of stack (e.g. 2 means two elements deeper);
|
||||
* @return String represents code point.
|
||||
*/
|
||||
@NonNull
|
||||
public static String getCodePoint(@Nullable final Object caller, final int stackShift) {
|
||||
final StackTraceElement traceElement = Thread.currentThread().getStackTrace()[STACK_TRACE_CODE_DEPTH + stackShift];
|
||||
return traceElement.getMethodName() + '(' + traceElement.getFileName() + ':' + traceElement.getLineNumber() + ')'
|
||||
+ (caller != null ? " of object " + caller.getClass().getSimpleName() + '(' + Integer.toHexString(caller.hashCode()) + ')' : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints stacktrace in log with specified tag.
|
||||
*
|
||||
* @param tag Tag to be shown in logs.
|
||||
*/
|
||||
|
||||
@SuppressLint("LogConditional")
|
||||
public static void printStackTrace(@NonNull final String tag) {
|
||||
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
if (Log.isLoggable(tag, Log.DEBUG)) {
|
||||
Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length)));
|
||||
}
|
||||
}
|
||||
|
||||
private Lc() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* 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.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
import ru.touchin.roboswag.core.utils.ThreadLocalValue;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 14/05/2016.
|
||||
* Group of log messages with specific tag prefix (name of group).
|
||||
* It could be used in specific {@link LogProcessor} to filter messages by group.
|
||||
*/
|
||||
@SuppressWarnings({"checkstyle:methodname", "PMD.ShortMethodName"})
|
||||
//MethodNameCheck,ShortMethodName: log methods better be 1-symbol
|
||||
public class LcGroup {
|
||||
|
||||
/**
|
||||
* Logging group to log UI metrics (like inflation or layout time etc.).
|
||||
*/
|
||||
public static final LcGroup UI_METRICS = new LcGroup("UI_METRICS");
|
||||
/**
|
||||
* Logging group to log UI lifecycle (onCreate, onStart, onResume etc.).
|
||||
*/
|
||||
public static final LcGroup UI_LIFECYCLE = new LcGroup("UI_LIFECYCLE");
|
||||
|
||||
private static final ThreadLocalValue<SimpleDateFormat> DATE_TIME_FORMATTER
|
||||
= new ThreadLocalValue<>(() -> new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()));
|
||||
|
||||
@NonNull
|
||||
private final String name;
|
||||
private boolean disabled;
|
||||
|
||||
public LcGroup(@NonNull final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables logging of this group.
|
||||
*/
|
||||
public void disable() {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging of this group.
|
||||
*/
|
||||
public void enable() {
|
||||
disabled = false;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String createLogTag() {
|
||||
final StackTraceElement trace = Thread.currentThread().getStackTrace()[Lc.STACK_TRACE_CODE_DEPTH + 3];
|
||||
return trace.getFileName() + ':' + trace.getLineNumber();
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.AvoidCatchingThrowable")
|
||||
//AvoidCatchingThrowable: it is needed to safety format message
|
||||
@Nullable
|
||||
private String createFormattedMessage(@Nullable final String message, @NonNull final Object... args) {
|
||||
try {
|
||||
if (args.length > 0 && message == null) {
|
||||
throw new ShouldNotHappenException("Args are not empty but format message is null");
|
||||
}
|
||||
return message != null ? (args.length > 0 ? String.format(message, args) : message) : null;
|
||||
} catch (final Throwable formattingException) {
|
||||
Lc.assertion(formattingException);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String createLogMessage(@Nullable final String formattedMessage) {
|
||||
return DATE_TIME_FORMATTER.get().format(System.currentTimeMillis())
|
||||
+ ' ' + Thread.currentThread().getName()
|
||||
+ ' ' + name
|
||||
+ (formattedMessage != null ? (' ' + formattedMessage) : "");
|
||||
}
|
||||
|
||||
private void logMessage(@NonNull final LcLevel logLevel, @Nullable final String message,
|
||||
@Nullable final Throwable throwable, @NonNull final Object... args) {
|
||||
if (disabled || logLevel.lessThan(Lc.getLogProcessor().getMinLogLevel())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (throwable == null && args.length > 0 && args[0] instanceof Throwable) {
|
||||
Lc.w("Maybe you've misplaced exception with first format arg? format: %s; arg: %s", message, args[0]);
|
||||
}
|
||||
|
||||
final String formattedMessage = createFormattedMessage(message, args);
|
||||
if (logLevel == LcLevel.ASSERT && Lc.isCrashOnAssertions()) {
|
||||
throw createAssertion(formattedMessage, throwable);
|
||||
}
|
||||
|
||||
Lc.getLogProcessor().processLogMessage(this, logLevel, createLogTag(), createLogMessage(formattedMessage), throwable);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ShouldNotHappenException createAssertion(@Nullable final String message, @Nullable final Throwable exception) {
|
||||
return exception != null
|
||||
? (message != null ? new ShouldNotHappenException(message, exception)
|
||||
: (exception instanceof ShouldNotHappenException ? (ShouldNotHappenException) exception : new ShouldNotHappenException(exception)))
|
||||
: (message != null ? new ShouldNotHappenException(message) : new ShouldNotHappenException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void d(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.DEBUG, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void d(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.DEBUG, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void i(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.INFO, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs info message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void i(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.INFO, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void w(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.WARN, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void w(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.WARN, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message.
|
||||
*
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void e(@NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.ERROR, message, null, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error message.
|
||||
*
|
||||
* @param throwable Exception to log;
|
||||
* @param message Message or format of message to log;
|
||||
* @param args Arguments of formatted message.
|
||||
*/
|
||||
public void e(@NonNull final Throwable throwable, @NonNull final String message, @NonNull final Object... args) {
|
||||
logMessage(LcLevel.ERROR, message, throwable, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param message Message that is describing assertion.
|
||||
*/
|
||||
public void assertion(@NonNull final String message) {
|
||||
logMessage(LcLevel.ASSERT, "Assertion appears at %s with message: %s", null, Lc.getCodePoint(null, 2), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes assertion. Normally it will throw {@link ShouldNotHappenException} and crash app.
|
||||
* If it should crash or not is specified at {@link Lc#isCrashOnAssertions()}.
|
||||
* In some cases crash on assertions should be switched off and assertion should be processed in {@link LogProcessor}.
|
||||
* It is useful for example to not crash but log it as handled crash in Crashlitycs in production build.
|
||||
*
|
||||
* @param throwable Exception that is describing assertion.
|
||||
*/
|
||||
public void assertion(@NonNull final Throwable throwable) {
|
||||
logMessage(LcLevel.ASSERT, "Assertion appears at %s", throwable, Lc.getCodePoint(null, 2));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 14/05/2016.
|
||||
* Level of log message.
|
||||
*/
|
||||
public enum LcLevel {
|
||||
|
||||
VERBOSE(Log.VERBOSE),
|
||||
DEBUG(Log.DEBUG),
|
||||
INFO(Log.INFO),
|
||||
WARN(Log.WARN),
|
||||
ERROR(Log.ERROR),
|
||||
ASSERT(Log.ASSERT);
|
||||
|
||||
private final int priority;
|
||||
|
||||
LcLevel(final int priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard {@link Log} integer value of level represents priority of message.
|
||||
*
|
||||
* @return Integer level.
|
||||
*/
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares priorities of LcLevels and returns if current is less than another.
|
||||
*
|
||||
* @param logLevel {@link LcLevel} to compare priority with;
|
||||
* @return True if current level priority less than level passed as parameter.
|
||||
*/
|
||||
public boolean lessThan(@NonNull final LcLevel logLevel) {
|
||||
return this.priority < logLevel.priority;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.core.log;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Abstract object to intercept log messages coming from {@link LcGroup} and {@link Lc} log methods.
|
||||
*/
|
||||
public abstract class LogProcessor {
|
||||
|
||||
@NonNull
|
||||
private final LcLevel minLogLevel;
|
||||
|
||||
public LogProcessor(@NonNull final LcLevel minLogLevel) {
|
||||
this.minLogLevel = minLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum logging level.
|
||||
* Any messages with lower priority won't be passed into {@link #processLogMessage(LcGroup, LcLevel, String, String, Throwable)}.
|
||||
*
|
||||
* @return Minimum log level represented by {@link LcLevel} object.
|
||||
*/
|
||||
@NonNull
|
||||
public LcLevel getMinLogLevel() {
|
||||
return minLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core method to process any incoming log messages from {@link LcGroup} and {@link Lc} with level higher or equals {@link #getMinLogLevel()}.
|
||||
*
|
||||
* @param group {@link LcGroup} where log message came from;
|
||||
* @param level {@link LcLevel} level (priority) of message;
|
||||
* @param tag String mark of message;
|
||||
* @param message Message to log;
|
||||
* @param throwable Exception to log.
|
||||
*/
|
||||
public abstract void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level,
|
||||
@NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.core.utils;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Exception that should be threw when some unexpected code reached.
|
||||
* E.g. if some value null but it is not legal or in default case in switch if all specific cases should be processed.
|
||||
*/
|
||||
public class ShouldNotHappenException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 0;
|
||||
|
||||
public ShouldNotHappenException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ShouldNotHappenException(@NonNull final String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public ShouldNotHappenException(@NonNull final String detailMessage, @NonNull final Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public ShouldNotHappenException(@NonNull final Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,23 +17,45 @@
|
|||
*
|
||||
*/
|
||||
|
||||
package ru.touchin.roboswag.components.navigation;
|
||||
package ru.touchin.roboswag.core.utils;
|
||||
|
||||
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.
|
||||
* Created by Gavriil Sitnikov on 13/11/2015.
|
||||
* Thread local value with specified creator of value per thread.
|
||||
*/
|
||||
public interface OnFragmentStartedListener {
|
||||
public class ThreadLocalValue<T> extends ThreadLocal<T> {
|
||||
|
||||
@NonNull
|
||||
private final Fabric<T> fabric;
|
||||
|
||||
public ThreadLocalValue(@NonNull final Fabric<T> fabric) {
|
||||
super();
|
||||
this.fabric = fabric;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected T initialValue() {
|
||||
return fabric.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls by child fragment (added via {@link android.support.v4.app.FragmentManager}) on it'sstart.
|
||||
* Fabric of thread-local objects.
|
||||
*
|
||||
* @param fragment Child fragment which called this method.
|
||||
* @param <T> Type of objects.
|
||||
*/
|
||||
void onFragmentStarted(@NonNull Fragment fragment);
|
||||
public interface Fabric<T> {
|
||||
|
||||
}
|
||||
/**
|
||||
* Creates object.
|
||||
*
|
||||
* @return new instance of object.
|
||||
*/
|
||||
@NonNull
|
||||
T create();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
final String rootDir
|
||||
if (gradle.ext.has('componentsRoot')) {
|
||||
rootDir = gradle.ext['componentsRoot']
|
||||
} else {
|
||||
rootDir = settingsDir
|
||||
}
|
||||
|
||||
include ':logging'
|
||||
include ':utils'
|
||||
include ':navigation'
|
||||
include ':storable'
|
||||
include ':api-logansquare'
|
||||
include ':lifecycle-common'
|
||||
include ':lifecycle-rx'
|
||||
include ':views'
|
||||
include ':recyclerview-adapters'
|
||||
include ':kotlin-extensions'
|
||||
include ':templates'
|
||||
|
||||
project(':utils').projectDir = new File(rootDir, 'utils')
|
||||
project(':logging').projectDir = new File(rootDir, 'logging')
|
||||
project(':navigation').projectDir = new File(rootDir, 'navigation')
|
||||
project(':storable').projectDir = new File(rootDir, 'storable')
|
||||
project(':api-logansquare').projectDir = new File(rootDir, 'api-logansquare')
|
||||
project(':lifecycle-common').projectDir = new File(rootDir, 'lifecycle-common')
|
||||
project(':lifecycle-rx').projectDir = new File(rootDir, 'lifecycle-rx')
|
||||
project(':views').projectDir = new File(rootDir, 'views')
|
||||
project(':recyclerview-adapters').projectDir = new File(rootDir, 'recyclerview-adapters')
|
||||
project(':kotlin-extensions').projectDir = new File(rootDir, 'kotlin-extensions')
|
||||
project(':templates').projectDir = new File(rootDir, 'templates')
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
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(":utils")
|
||||
api project(":logging")
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="ru.touchin.roboswag.components.navigation"/>
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* 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.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.annotation.IdRes
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.app.FragmentTransaction
|
||||
import android.view.MenuItem
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 07/03/2016.
|
||||
* Navigation which is controlling fragments on activity using [android.support.v4.app.FragmentManager].
|
||||
* Basically there are 4 main actions to add fragments to activity.
|
||||
* 1) [.setInitial] means to set fragment on top and remove all previously added fragments from stack;
|
||||
* 2) [.push] means to simply add fragment on top of the stack;
|
||||
* 3) [.setAsTop] means to push fragment on top of the stack with specific [.TOP_FRAGMENT_TAG_MARK] tag.
|
||||
* It is useful to realize up/back navigation: if [.up] method will be called then stack will go to nearest fragment with TOP tag.
|
||||
* If [.back] method will be called then stack will go to previous fragment.
|
||||
* Usually such logic using to set as top fragments from sidebar and show hamburger when some of them appeared;
|
||||
* 4) [.pushForResult] means to push fragment with target fragment. It is also adding [.WITH_TARGET_FRAGMENT_TAG_MARK] tag.
|
||||
* Also if such up/back navigation logic is not OK then [.backTo] method could be used with any condition to back to.
|
||||
* In that case in any stack-change method it is allowed to setup fragment transactions.
|
||||
*/
|
||||
open class FragmentNavigation(
|
||||
private val context: Context,
|
||||
private val fragmentManager: FragmentManager,
|
||||
@IdRes private val containerViewId: Int,
|
||||
private val transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
||||
) {
|
||||
|
||||
companion object {
|
||||
const val TOP_FRAGMENT_TAG_MARK = "TOP_FRAGMENT"
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if last fragment in stack is top (added by [.setAsTop] or [.setInitial]) like fragment from sidebar menu.
|
||||
*
|
||||
* @return True if last fragment on stack has TOP_FRAGMENT_TAG_MARK.
|
||||
*/
|
||||
fun isCurrentFragmentTop(): Boolean {
|
||||
if (fragmentManager.backStackEntryCount == 0) {
|
||||
return true
|
||||
}
|
||||
val topFragmentTag = fragmentManager
|
||||
.getBackStackEntryAt(fragmentManager.backStackEntryCount - 1)
|
||||
.name
|
||||
return topFragmentTag != null && topFragmentTag.contains(TOP_FRAGMENT_TAG_MARK)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed to react on [android.app.Activity]'s menu item selection.
|
||||
*
|
||||
* @param item Selected menu item;
|
||||
* @return True if reaction fired.
|
||||
*/
|
||||
fun onOptionsItemSelected(item: MenuItem): Boolean = item.itemId == android.R.id.home && back()
|
||||
|
||||
/**
|
||||
* Base method which is adding fragment to stack.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
|
||||
* @param addToStack Flag to add this transaction to the back stack;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param backStackTag Tag of [Fragment] in back stack;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun addToStack(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
targetFragment: Fragment?,
|
||||
targetRequestCode: Int,
|
||||
addToStack: Boolean,
|
||||
args: Bundle?,
|
||||
backStackTag: String?,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)?
|
||||
) {
|
||||
if (fragmentManager.isDestroyed) {
|
||||
Lc.assertion("FragmentManager is destroyed")
|
||||
return
|
||||
}
|
||||
|
||||
val fragment = Fragment.instantiate(context, fragmentClass.name, args)
|
||||
if (targetFragment != null) {
|
||||
if (fragmentManager !== targetFragment.fragmentManager) {
|
||||
Lc.assertion("FragmentManager of target is differ then of creating fragment. Target will be lost after restoring activity. "
|
||||
+ targetFragment.fragmentManager + " != " + fragmentManager)
|
||||
}
|
||||
fragment.setTargetFragment(targetFragment, targetRequestCode)
|
||||
}
|
||||
|
||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||
transactionSetup?.invoke(fragmentTransaction)
|
||||
fragmentTransaction.replace(containerViewId, fragment, null)
|
||||
if (addToStack) {
|
||||
fragmentTransaction.addToBackStack(backStackTag).setTransition(transition)
|
||||
} else {
|
||||
fragmentTransaction.setPrimaryNavigationFragment(fragment)
|
||||
}
|
||||
fragmentTransaction.commit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Simply calls [FragmentManager.popBackStack].
|
||||
*
|
||||
* @return True if it have back to some entry in stack.
|
||||
*/
|
||||
fun back(): Boolean {
|
||||
if (fragmentManager.backStackEntryCount >= 1) {
|
||||
fragmentManager.popBackStack()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Backs to fragment which back stack's entry satisfy to specific condition.
|
||||
*
|
||||
* @param condition Condition of back stack entry to be satisfied;
|
||||
* @return True if it have back to some entry in stack.
|
||||
*/
|
||||
fun backTo(condition: (FragmentManager.BackStackEntry) -> Boolean): Boolean {
|
||||
val stackSize = fragmentManager.backStackEntryCount
|
||||
var id: Int? = null
|
||||
for (i in stackSize - 2 downTo 0) {
|
||||
val backStackEntry = fragmentManager.getBackStackEntryAt(i)
|
||||
if (condition(backStackEntry)) {
|
||||
id = backStackEntry.id
|
||||
break
|
||||
}
|
||||
}
|
||||
if (id != null) {
|
||||
fragmentManager.popBackStack(id, 0)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Backs to fragment with specific [.TOP_FRAGMENT_TAG_MARK] tag.
|
||||
* This tag is adding if fragment added to stack via [.setInitial] or [.setAsTop] methods.
|
||||
* It can be used to create simple up/back navigation.
|
||||
*
|
||||
* @return True if it have back to some entry in stack.
|
||||
*/
|
||||
fun up() {
|
||||
if (!backTo { backStackEntry -> backStackEntry.name != null && backStackEntry.name.endsWith(TOP_FRAGMENT_TAG_MARK) }) {
|
||||
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific arguments and transaction setup.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun push(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
args: Bundle? = null,
|
||||
addToStack: Boolean = true,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(fragmentClass, null, 0, addToStack, args, null, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific target fragment, arguments and transaction setup.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param targetFragment Target fragment to be set as [Fragment.getTargetFragment] of instantiated [Fragment];
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun pushForResult(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
targetFragment: Fragment,
|
||||
targetRequestCode: Int,
|
||||
args: Bundle? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
fragmentClass,
|
||||
targetFragment,
|
||||
targetRequestCode,
|
||||
true,
|
||||
args,
|
||||
null,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [Fragment] on top of stack with specific transaction setup, arguments
|
||||
* and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
fun setAsTop(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
args: Bundle? = null,
|
||||
addToStack: Boolean = true,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(fragmentClass, null, 0, addToStack, args, "${fragmentClass.name};$TOP_FRAGMENT_TAG_MARK", transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops all [Fragment]s and places new initial [Fragment] on top of stack with specific transaction setup and arguments.
|
||||
*
|
||||
* @param fragmentClass Class of [Fragment] to instantiate;
|
||||
* @param args Bundle to be set as [Fragment.getArguments] of instantiated [Fragment];
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun setInitial(
|
||||
fragmentClass: Class<out Fragment>,
|
||||
args: Bundle? = null,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
beforeSetInitialActions()
|
||||
setAsTop(fragmentClass, args, false, transactionSetup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method calls every time before initial [Fragment] will be placed.
|
||||
*/
|
||||
protected fun beforeSetInitialActions() {
|
||||
if (fragmentManager.isDestroyed) {
|
||||
Lc.assertion("FragmentManager is destroyed")
|
||||
return
|
||||
}
|
||||
|
||||
if (fragmentManager.backStackEntryCount > 0) {
|
||||
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -29,13 +29,15 @@ 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;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 11/03/16.
|
||||
* 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;
|
||||
|
|
@ -206,7 +208,7 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle
|
|||
@Override
|
||||
public void onDrawerClosed(@NonNull final View view) {
|
||||
if (isInvalidateOptionsMenuSupported) {
|
||||
activity.supportInvalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,9 +223,9 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle
|
|||
|
||||
@Override
|
||||
public void onDrawerOpened(@NonNull final View drawerView) {
|
||||
activity.hideSoftInput();
|
||||
UiUtils.OfViews.hideSoftInput(activity);
|
||||
if (isInvalidateOptionsMenuSupported) {
|
||||
activity.supportInvalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,4 +247,4 @@ public class SimpleActionBarDrawerToggle extends ActionBarDrawerToggle
|
|||
super.onDrawerSlide(drawerView, this.slideOffset);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.util.ArraySet;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 08/03/2016.
|
||||
* Base activity to use in components repository.
|
||||
*/
|
||||
public abstract class BaseActivity extends AppCompatActivity {
|
||||
|
||||
@NonNull
|
||||
private final Set<OnBackPressedListener> onBackPressedListeners = new ArraySet<>();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this) + " requestCode: " + requestCode + "; resultCode: " + resultCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull final Bundle stateToSave) {
|
||||
super.onSaveInstanceState(stateToSave);
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) {
|
||||
onBackPressedListeners.add(onBackPressedListener);
|
||||
}
|
||||
|
||||
public void removeOnBackPressedListener(@NonNull final OnBackPressedListener onBackPressedListener) {
|
||||
onBackPressedListeners.remove(onBackPressedListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
for (final OnBackPressedListener onBackPressedListener : onBackPressedListeners) {
|
||||
if (onBackPressedListener.onBackPressed()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
* 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.animation.Animator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.arch.lifecycle.Lifecycle;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Canvas;
|
||||
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;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
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;
|
||||
import ru.touchin.roboswag.core.utils.ShouldNotHappenException;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
* Fragment instantiated in specific activity of {@link TActivity} type that is holding {@link ViewController} inside.
|
||||
*
|
||||
* @param <TState> Type of object which is representing it's fragment state;
|
||||
* @param <TActivity> Type of {@link FragmentActivity} where fragment could be attached to.
|
||||
*/
|
||||
@SuppressWarnings("PMD.TooManyMethods")
|
||||
public class ViewControllerFragment<TActivity extends FragmentActivity, TState extends Parcelable> 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 long acceptableUiCalculationTime = 100;
|
||||
|
||||
/**
|
||||
* Sets acceptable UI calculation time so there will be warnings in logs if ViewController's inflate/layout actions will take more than that time.
|
||||
* It's 100ms by default.
|
||||
*/
|
||||
public static void setAcceptableUiCalculationTime(final long acceptableUiCalculationTime) {
|
||||
ViewControllerFragment.acceptableUiCalculationTime = acceptableUiCalculationTime;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static <T extends Parcelable> T reserialize(@NonNull final T parcelable) {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
parcel.writeParcelable(parcelable, 0);
|
||||
final byte[] serializableBytes = parcel.marshall();
|
||||
parcel.recycle();
|
||||
parcel = Parcel.obtain();
|
||||
parcel.unmarshall(serializableBytes, 0, serializableBytes.length);
|
||||
parcel.setDataPosition(0);
|
||||
final T result = parcel.readParcelable(Thread.currentThread().getContextClassLoader());
|
||||
parcel.recycle();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link Bundle} which will store state.
|
||||
*
|
||||
* @param state State to use into ViewController.
|
||||
* @return Returns bundle with state inside.
|
||||
*/
|
||||
@NonNull
|
||||
public static Bundle args(@NonNull final Class<? extends ViewController> viewControllerClass, @Nullable final Parcelable state) {
|
||||
final Bundle result = new Bundle();
|
||||
result.putSerializable(VIEW_CONTROLLER_CLASS_EXTRA, viewControllerClass);
|
||||
result.putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ViewController viewController;
|
||||
private Class<ViewController<TActivity, TState>> viewControllerClass;
|
||||
private TState state;
|
||||
@Nullable
|
||||
private ActivityResult pendingActivityResult;
|
||||
|
||||
private boolean appeared;
|
||||
|
||||
/**
|
||||
* Returns specific {@link Parcelable} which contains state of fragment and it's {@link ViewController}.
|
||||
*
|
||||
* @return Object represents state.
|
||||
*/
|
||||
@NonNull
|
||||
public TState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Class<ViewController<TActivity, TState>> getViewControllerClass() {
|
||||
return viewControllerClass;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setHasOptionsMenu(!isChildFragment());
|
||||
|
||||
viewControllerClass = (Class<ViewController<TActivity, TState>>) 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 (BuildConfig.DEBUG) {
|
||||
state = reserialize(state);
|
||||
}
|
||||
} else {
|
||||
Lc.assertion("State is required and null");
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public final View onCreateView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState
|
||||
) {
|
||||
return new PlaceholderView(inflater.getContext(), viewControllerClass.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
//noinspection ConstantConditions
|
||||
viewController = createViewController(requireActivity(), (ViewGroup) getView(), savedInstanceState);
|
||||
viewController.onCreate();
|
||||
if (pendingActivityResult != null) {
|
||||
viewController.onActivityResult(pendingActivityResult.requestCode, pendingActivityResult.resultCode, pendingActivityResult.data);
|
||||
pendingActivityResult = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Animation onCreateAnimation(final int transit, final boolean enter, final int nextAnim) {
|
||||
if (viewController != null) {
|
||||
return viewController.onCreateAnimation(transit, enter, nextAnim);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Animator onCreateAnimator(final int transit, final boolean enter, final int nextAnim) {
|
||||
if (viewController != null) {
|
||||
return viewController.onCreateAnimator(transit, enter, nextAnim);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
||||
super.onViewStateRestored(savedInstanceState);
|
||||
if (viewController != null) {
|
||||
viewController.onViewStateRestored(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (!appeared && isMenuVisible()) {
|
||||
onAppear();
|
||||
}
|
||||
if (viewController != null) {
|
||||
viewController.onStart();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onAppear() {
|
||||
appeared = true;
|
||||
if (viewController != null) {
|
||||
viewController.onAppear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (viewController != null) {
|
||||
viewController.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
if (viewController != null) {
|
||||
viewController.onLowMemory();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
if (viewController != null) {
|
||||
viewController.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
return (viewController != null && viewController.onOptionsItemSelected(item)) || super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (viewController != null) {
|
||||
viewController.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
if (viewController != null) {
|
||||
viewController.onSaveInstanceState(savedInstanceState);
|
||||
}
|
||||
savedInstanceState.putParcelable(VIEW_CONTROLLER_STATE_EXTRA, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@CallSuper
|
||||
protected void onDisappear() {
|
||||
appeared = false;
|
||||
if (viewController != null) {
|
||||
viewController.onDisappear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if (appeared) {
|
||||
onDisappear();
|
||||
}
|
||||
if (viewController != null) {
|
||||
viewController.onStop();
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
if (viewController != null) {
|
||||
viewController.onDestroy();
|
||||
viewController = null;
|
||||
}
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
||||
if (viewController != null) {
|
||||
viewController.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
||||
if (viewController != null) {
|
||||
viewController.onActivityResult(requestCode, resultCode, data);
|
||||
} else {
|
||||
pendingActivityResult = new ActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuVisibility(final boolean menuVisible) {
|
||||
super.setMenuVisibility(menuVisible);
|
||||
if (getActivity() != null && getView() != null) {
|
||||
final boolean started = getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
|
||||
if (!appeared && menuVisible && started) {
|
||||
onAppear();
|
||||
}
|
||||
if (appeared && (!menuVisible || !started)) {
|
||||
onDisappear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if fragment have parent fragment.
|
||||
*
|
||||
* @return Returns true if fragment is in some fragment's children stack.
|
||||
*/
|
||||
public boolean isChildFragment() {
|
||||
return getParentFragment() != null;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ViewController createViewController(
|
||||
@NonNull final FragmentActivity activity,
|
||||
@NonNull final ViewGroup view,
|
||||
@Nullable final Bundle savedInstanceState
|
||||
) {
|
||||
if (viewControllerClass.getConstructors().length != 1) {
|
||||
throw new ShouldNotHappenException("There should be single constructor for " + viewControllerClass);
|
||||
}
|
||||
final Constructor<?> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " ViewController: " + getViewControllerClass();
|
||||
}
|
||||
|
||||
private static class PlaceholderView extends FrameLayout {
|
||||
|
||||
@NonNull
|
||||
private final String tagName;
|
||||
private long lastMeasureTime;
|
||||
|
||||
public PlaceholderView(@NonNull final Context context, @NonNull final String tagName) {
|
||||
super(context);
|
||||
this.tagName = tagName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (BuildConfig.DEBUG && lastMeasureTime == 0) {
|
||||
lastMeasureTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull final Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
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);
|
||||
}
|
||||
lastMeasureTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ActivityResult {
|
||||
public final int requestCode;
|
||||
public final int resultCode;
|
||||
@Nullable
|
||||
public final Intent data;
|
||||
|
||||
ActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
||||
this.requestCode = requestCode;
|
||||
this.resultCode = resultCode;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package ru.touchin.roboswag.components.navigation.viewcontrollers
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.support.annotation.LayoutRes
|
||||
import android.support.v4.app.FragmentActivity
|
||||
|
||||
abstract class DefaultViewController<TActivity : FragmentActivity, TState : Parcelable>(
|
||||
@LayoutRes layoutRes: Int,
|
||||
creationContext: CreationContext,
|
||||
savedInstanceState: Bundle?
|
||||
) : ViewController<TActivity, TState>(
|
||||
creationContext,
|
||||
savedInstanceState
|
||||
) {
|
||||
|
||||
init {
|
||||
setContentView(layoutRes)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.touchin.roboswag.components.navigation.viewcontrollers
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
object EmptyState : Parcelable {
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) = Unit
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
||||
@JvmField
|
||||
val CREATOR = object : Parcelable.Creator<EmptyState> {
|
||||
override fun createFromParcel(parcel: Parcel) = EmptyState
|
||||
|
||||
override fun newArray(size: Int): Array<EmptyState?> = arrayOfNulls(size)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
* 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.viewcontrollers;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.arch.lifecycle.Lifecycle;
|
||||
import android.arch.lifecycle.LifecycleOwner;
|
||||
import android.arch.lifecycle.LifecycleRegistry;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.annotation.ColorRes;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment;
|
||||
import ru.touchin.roboswag.components.utils.UiUtils;
|
||||
import ru.touchin.roboswag.core.log.Lc;
|
||||
import ru.touchin.roboswag.core.log.LcGroup;
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 21/10/2015.
|
||||
* Class to control view of specific fragment, activity and application by logic bridge.
|
||||
*
|
||||
* @param <TActivity> Type of activity where such {@link ViewController} could be;
|
||||
* @param <TState> Type of state;
|
||||
*/
|
||||
public class ViewController<TActivity extends FragmentActivity, TState extends Parcelable> implements LifecycleOwner {
|
||||
|
||||
@NonNull
|
||||
private final LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
|
||||
@NonNull
|
||||
private final TActivity activity;
|
||||
@NonNull
|
||||
private final ViewControllerFragment<TActivity, TState> fragment;
|
||||
@NonNull
|
||||
private final ViewGroup container;
|
||||
|
||||
@SuppressWarnings({"unchecked", "PMD.UnusedFormalParameter"})
|
||||
//UnusedFormalParameter: savedInstanceState could be used by children
|
||||
public ViewController(@NonNull final CreationContext creationContext, @Nullable final Bundle savedInstanceState) {
|
||||
this.activity = (TActivity) creationContext.activity;
|
||||
this.fragment = creationContext.fragment;
|
||||
this.container = creationContext.container;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Lifecycle getLifecycle() {
|
||||
return lifecycleRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns activity where {@link ViewController} could be.
|
||||
*
|
||||
* @return Returns activity.
|
||||
*/
|
||||
@NonNull
|
||||
public final TActivity getActivity() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns fragment where {@link ViewController} could be.
|
||||
*
|
||||
* @return Returns fragment.
|
||||
*/
|
||||
@NonNull
|
||||
public final ViewControllerFragment<TActivity, TState> getFragment() {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns state from fragment.
|
||||
*
|
||||
* @return Returns state.
|
||||
*/
|
||||
@NonNull
|
||||
protected final TState getState() {
|
||||
return fragment.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns view instantiated in {@link #getFragment()} fragment attached to {@link #getActivity()} activity.
|
||||
* Use it to inflate your views into at construction of this {@link ViewController}.
|
||||
*
|
||||
* @return Returns view.
|
||||
*/
|
||||
@NonNull
|
||||
protected final ViewGroup getContainer() {
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view controller content from a layout resource.
|
||||
* This layout is placed directly into the container's ({@link #getContainer()}) view hierarchy.
|
||||
*
|
||||
* @param layoutResId Resource ID to be inflated.
|
||||
*/
|
||||
protected final void setContentView(@LayoutRes final int layoutResId) {
|
||||
if (getContainer().getChildCount() > 0) {
|
||||
getContainer().removeAllViews();
|
||||
}
|
||||
UiUtils.inflateAndAdd(layoutResId, getContainer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view controller content to an explicit view.
|
||||
* This view is placed directly into the container's ({@link #getContainer()}) view hierarchy.
|
||||
*
|
||||
* @param view The desired content to display.
|
||||
*/
|
||||
protected final void setContentView(@NonNull final View view) {
|
||||
setContentView(view, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view controller content to an explicit view with specific layout parameters.
|
||||
* This view is placed directly into the container's ({@link #getContainer()}) view hierarchy.
|
||||
*
|
||||
* @param view The desired content to display;
|
||||
* @param layoutParams Layout parameters for the view.
|
||||
*/
|
||||
protected final void setContentView(@NonNull final View view, @NonNull final ViewGroup.LayoutParams layoutParams) {
|
||||
if (getContainer().getChildCount() > 0) {
|
||||
getContainer().removeAllViews();
|
||||
}
|
||||
getContainer().addView(view, layoutParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a child view with the given id. If this view has the given id, return this view.
|
||||
*
|
||||
* @param id The id to search for;
|
||||
* @return The view that has the given id in the hierarchy.
|
||||
*/
|
||||
@NonNull
|
||||
public final <T extends View> T findViewById(@IdRes final int id) {
|
||||
return getContainer().findViewById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a localized, styled CharSequence from the application's package's
|
||||
* default string table.
|
||||
*
|
||||
* @param resId Resource id for the CharSequence text
|
||||
*/
|
||||
@NonNull
|
||||
public final CharSequence getText(@StringRes final int resId) {
|
||||
return activity.getText(resId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a localized string from the application's package's default string table.
|
||||
*
|
||||
* @param resId Resource id for the string
|
||||
*/
|
||||
@NonNull
|
||||
public final String getString(@StringRes final int resId) {
|
||||
return activity.getString(resId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a localized formatted string from the application's package's default string table, substituting the format arguments as defined in
|
||||
* {@link java.util.Formatter} and {@link java.lang.String#format}.
|
||||
*
|
||||
* @param resId Resource id for the format string
|
||||
* @param formatArgs The format arguments that will be used for substitution.
|
||||
*/
|
||||
@NonNull
|
||||
public final String getString(@StringRes final int resId, @NonNull final Object... formatArgs) {
|
||||
return activity.getString(resId, formatArgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the color value associated with a particular resource ID.
|
||||
* Starting in {@link android.os.Build.VERSION_CODES#M}, the returned
|
||||
* color will be styled for the specified Context's theme.
|
||||
*
|
||||
* @param resId The resource id to search for data;
|
||||
* @return int A single color value in the form 0xAARRGGBB.
|
||||
*/
|
||||
@ColorInt
|
||||
public final int getColor(@ColorRes final int resId) {
|
||||
return ContextCompat.getColor(activity, resId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color state list associated with a particular resource ID.
|
||||
*
|
||||
* <p>Starting in {@link android.os.Build.VERSION_CODES#M}, the returned
|
||||
* color state list will be styled for the specified Context's theme.
|
||||
*
|
||||
* @param resId The desired resource identifier, as generated by the aapt
|
||||
* tool. This integer encodes the package, type, and resource
|
||||
* entry. The value 0 is an invalid identifier.
|
||||
* @return A color state list, or {@code null} if the resource could not be resolved.
|
||||
* @throws android.content.res.Resources.NotFoundException if the given ID
|
||||
* does not exist.
|
||||
*/
|
||||
@Nullable
|
||||
public final ColorStateList getColorStateList(@ColorRes final int resId) {
|
||||
return ContextCompat.getColorStateList(activity, resId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a drawable object associated with a particular resource ID.
|
||||
* Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the
|
||||
* returned drawable will be styled for the specified Context's theme.
|
||||
*
|
||||
* @param resId The resource id to search for data;
|
||||
* @return Drawable An object that can be used to draw this resource.
|
||||
*/
|
||||
@Nullable
|
||||
public final Drawable getDrawable(@DrawableRes final int resId) {
|
||||
return ContextCompat.getDrawable(activity, resId);
|
||||
}
|
||||
|
||||
public final void startActivity(@NonNull final Intent intent) {
|
||||
fragment.startActivity(intent);
|
||||
}
|
||||
|
||||
public final void startActivityForResult(@NonNull final Intent intent, final int requestCode) {
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when activity configuring ActionBar, Toolbar, Sidebar etc.
|
||||
* If it will be called or not depends on {@link Fragment#hasOptionsMenu()} and {@link Fragment#isMenuVisible()}.
|
||||
*
|
||||
* @param menu The options menu in which you place your items;
|
||||
* @param inflater Helper to inflate menu items.
|
||||
*/
|
||||
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls right after construction of {@link ViewController}.
|
||||
* Happens at {@link ViewControllerFragment#onActivityCreated(Bundle)}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onCreate() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a fragment loads an animation. Note that if
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int)} was called with
|
||||
* {@link Animator} resources instead of {@link Animation} resources, {@code nextAnim}
|
||||
* will be an animator resource.
|
||||
*
|
||||
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
|
||||
* set.
|
||||
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param nextAnim The resource set in
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int)},
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
|
||||
* 0 if neither was called. The value will depend on the current operation.
|
||||
*/
|
||||
@Nullable
|
||||
public Animation onCreateAnimation(final int transit, final boolean enter, final int nextAnim) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a fragment loads an animator. This will be called when
|
||||
* {@link #onCreateAnimation(int, boolean, int)} returns null. Note that if
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int)} was called with
|
||||
* {@link Animation} resources instead of {@link Animator} resources, {@code nextAnim}
|
||||
* will be an animation resource.
|
||||
*
|
||||
* @param transit The value set in {@link FragmentTransaction#setTransition(int)} or 0 if not
|
||||
* set.
|
||||
* @param enter {@code true} when the fragment is added/attached/shown or {@code false} when
|
||||
* the fragment is removed/detached/hidden.
|
||||
* @param nextAnim The resource set in
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int)},
|
||||
* {@link FragmentTransaction#setCustomAnimations(int, int, int, int)}, or
|
||||
* 0 if neither was called. The value will depend on the current operation.
|
||||
*/
|
||||
@Nullable
|
||||
public Animator onCreateAnimator(final int transit, final boolean enter, final int nextAnim) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} saved state has been restored into the view hierarchy.
|
||||
* Happens at {@link ViewControllerFragment#onViewStateRestored}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onViewStateRestored(@Nullable final Bundle savedInstanceState) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have started.
|
||||
* Happens at {@link ViewControllerFragment#onStart()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onStart() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
|
||||
UiUtils.OfViews.hideSoftInput(getContainer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in started state and it's {@link #getFragment().isMenuVisible()} sets to true.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
public void onAppear() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have resumed.
|
||||
* Happens at {@link ViewControllerFragment#onResume()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onResume() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have goes near out of memory state.
|
||||
* Happens at {@link ViewControllerFragment#onLowMemory()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onLowMemory() {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have paused.
|
||||
* Happens at {@link ViewControllerFragment#onPause()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onPause() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} should save it's state.
|
||||
* Happens at {@link ViewControllerFragment#onSaveInstanceState(Bundle)}.
|
||||
* Try not to use such method for saving state but use {@link ViewControllerFragment#getState()} from {@link #getFragment()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when fragment is moved in stopped state or it's {@link #getFragment().isMenuVisible()} sets to false.
|
||||
* Usually it is indicating that user can't see fragment on screen and useful to track analytics events.
|
||||
*/
|
||||
public void onDisappear() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have stopped.
|
||||
* Happens at {@link ViewControllerFragment#onStop()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have destroyed.
|
||||
* Happens usually at {@link ViewControllerFragment#onDestroyView()}. In some cases at {@link ViewControllerFragment#onDestroy()}.
|
||||
*/
|
||||
@CallSuper
|
||||
public void onDestroy() {
|
||||
LcGroup.UI_LIFECYCLE.i(Lc.getCodePoint(this));
|
||||
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls when {@link ViewController} have requested permissions results.
|
||||
* Happens at {@link ViewControllerFragment#onRequestPermissionsResult(int, String[], int[])} ()}.
|
||||
*/
|
||||
@CallSuper
|
||||
@SuppressWarnings("PMD.UseVarargs")
|
||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from parent fragment.
|
||||
*/
|
||||
public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link ViewControllerFragment#onOptionsItemSelected(MenuItem)}.
|
||||
*
|
||||
* @param item Selected menu item;
|
||||
* @return True if selection processed.
|
||||
*/
|
||||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper class to simplify constructor override.
|
||||
*/
|
||||
public static class CreationContext {
|
||||
|
||||
@NonNull
|
||||
private final FragmentActivity activity;
|
||||
@NonNull
|
||||
private final ViewControllerFragment fragment;
|
||||
@NonNull
|
||||
private final ViewGroup container;
|
||||
|
||||
public CreationContext(
|
||||
@NonNull final FragmentActivity activity,
|
||||
@NonNull final ViewControllerFragment fragment,
|
||||
@NonNull final ViewGroup container
|
||||
) {
|
||||
this.activity = activity;
|
||||
this.fragment = fragment;
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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.viewcontrollers
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.support.annotation.IdRes
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentActivity
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.app.FragmentTransaction
|
||||
|
||||
import ru.touchin.roboswag.components.navigation.FragmentNavigation
|
||||
import ru.touchin.roboswag.components.navigation.fragments.ViewControllerFragment
|
||||
|
||||
/**
|
||||
* Created by Gavriil Sitnikov on 07/03/2016.
|
||||
* Navigation based on [ViewController]s which are creating by [Fragment]s.
|
||||
* So basically it is just [FragmentNavigation] where most of fragments should be inherited from [ViewControllerFragment].
|
||||
*
|
||||
* @param TActivity Type of activity where [ViewController]s should be showed.
|
||||
*/
|
||||
open class ViewControllerNavigation<TActivity : FragmentActivity>(
|
||||
context: Context,
|
||||
fragmentManager: FragmentManager,
|
||||
@IdRes containerViewId: Int,
|
||||
transition: Int = FragmentTransaction.TRANSIT_FRAGMENT_OPEN
|
||||
) : FragmentNavigation(context, fragmentManager, containerViewId, transition) {
|
||||
|
||||
/**
|
||||
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param addToStack Flag to add this transaction to the back stack;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment.
|
||||
*/
|
||||
fun <TState : Parcelable> pushViewController(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
addToStack: Boolean = true,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
ViewControllerFragment::class.java,
|
||||
null,
|
||||
0,
|
||||
addToStack,
|
||||
ViewControllerFragment.args(viewControllerClass, state),
|
||||
null,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState]
|
||||
* and with specific [TTargetFragment] and transaction setup.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param targetFragment [ViewControllerFragment] to be set as target;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment;
|
||||
* @param TTargetFragment Type of target fragment.
|
||||
*/
|
||||
fun <TState : Parcelable, TTargetFragment : Fragment> pushViewControllerForResult(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
targetFragment: TTargetFragment,
|
||||
targetRequestCode: Int,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
ViewControllerFragment::class.java,
|
||||
targetFragment,
|
||||
targetRequestCode,
|
||||
true,
|
||||
ViewControllerFragment.args(viewControllerClass, state),
|
||||
null,
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes [ViewController] on top of stack with specific [ViewControllerFragment.getState] and with specific transaction setup
|
||||
* and with [.TOP_FRAGMENT_TAG_MARK] tag used for simple up/back navigation.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment.
|
||||
*/
|
||||
fun <TState : Parcelable> setViewControllerAsTop(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
addToStack: Boolean = true,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
addToStack(
|
||||
ViewControllerFragment::class.java,
|
||||
null,
|
||||
0,
|
||||
addToStack,
|
||||
ViewControllerFragment.args(viewControllerClass, state),
|
||||
"${viewControllerClass.name};$TOP_FRAGMENT_TAG_MARK",
|
||||
transactionSetup
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pops all [Fragment]s and places new initial [ViewController] on top of stack
|
||||
* with specific [ViewControllerFragment.getState] and specific transaction setup.
|
||||
*
|
||||
* @param viewControllerClass Class of [ViewController] to be pushed;
|
||||
* @param state [Parcelable] of [ViewController]'s fragment;
|
||||
* @param transactionSetup Function to setup transaction before commit. It is useful to specify transition animations or additional info;
|
||||
* @param TState Type of state of fragment.
|
||||
*/
|
||||
fun <TState : Parcelable> setInitialViewController(
|
||||
viewControllerClass: Class<out ViewController<out TActivity, TState>>,
|
||||
state: TState,
|
||||
transactionSetup: ((FragmentTransaction) -> Unit)? = null
|
||||
) {
|
||||
beforeSetInitialActions()
|
||||
setViewControllerAsTop(viewControllerClass, state, false, transactionSetup)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion versions.minSdk
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':kotlin-extensions')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:recyclerview-v7:$versions.supportLibrary"
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest
|
||||
package="ru.touchin.roboswag.components.adapters"/>
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2017 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.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate.
|
||||
*/
|
||||
public abstract class AdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> {
|
||||
|
||||
private final int defaultItemViewType = ViewCompat.generateViewId();
|
||||
|
||||
/**
|
||||
* Unique ID of AdapterDelegate.
|
||||
*
|
||||
* @return Unique ID.
|
||||
*/
|
||||
public int getItemViewType() {
|
||||
return defaultItemViewType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
*
|
||||
* @param items Items to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public abstract boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition);
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param items Items in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ViewHolder to bind item to it later.
|
||||
*
|
||||
* @param parent Container of ViewHolder's view.
|
||||
* @return New ViewHolder.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract TViewHolder onCreateViewHolder(@NonNull final ViewGroup parent);
|
||||
|
||||
/**
|
||||
* Binds item to created by this object ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param items Items in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @param payloads Payloads;
|
||||
*/
|
||||
public abstract void onBindViewHolder(
|
||||
@NonNull final RecyclerView.ViewHolder holder,
|
||||
@NonNull final List<Object> items,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package ru.touchin.roboswag.components.adapters
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.util.SparseArray
|
||||
import android.view.ViewGroup
|
||||
|
||||
/**
|
||||
* Manager for delegation callbacks from [RecyclerView.Adapter] to delegates.
|
||||
*/
|
||||
class DelegatesManager {
|
||||
|
||||
private val delegates = SparseArray<AdapterDelegate<*>>()
|
||||
|
||||
fun getItemViewType(items: List<*>, adapterPosition: Int, collectionPosition: Int): Int {
|
||||
for (index in 0 until delegates.size()) {
|
||||
val delegate = delegates.valueAt(index)
|
||||
if (delegate.isForViewType(items, adapterPosition, collectionPosition)) {
|
||||
return delegate.itemViewType
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("Delegate not found for adapterPosition: $adapterPosition")
|
||||
}
|
||||
|
||||
fun getItemId(items: List<*>, adapterPosition: Int, collectionPosition: Int): Long {
|
||||
val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition))
|
||||
return delegate.getItemId(items, adapterPosition, collectionPosition)
|
||||
}
|
||||
|
||||
fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = getDelegate(viewType).onCreateViewHolder(parent)
|
||||
|
||||
fun onBindViewHolder(holder: RecyclerView.ViewHolder, items: List<*>, adapterPosition: Int, collectionPosition: Int, payloads: List<Any>) {
|
||||
val delegate = getDelegate(getItemViewType(items, adapterPosition, collectionPosition))
|
||||
delegate.onBindViewHolder(holder, items, adapterPosition, collectionPosition, payloads)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds [PositionAdapterDelegate] to adapter.
|
||||
*
|
||||
* @param delegate Delegate to add.
|
||||
*/
|
||||
fun addDelegate(delegate: AdapterDelegate<*>) = delegates.put(delegate.itemViewType, delegate)
|
||||
|
||||
/**
|
||||
* Removes [AdapterDelegate] from adapter.
|
||||
*
|
||||
* @param delegate Delegate to remove.
|
||||
*/
|
||||
fun removeDelegate(delegate: AdapterDelegate<*>) = delegates.remove(delegate.itemViewType)
|
||||
|
||||
private fun getDelegate(viewType: Int) = delegates[viewType] ?: throw IllegalStateException("No AdapterDelegate added for view type: $viewType")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package ru.touchin.roboswag.components.adapters
|
||||
|
||||
import android.support.v7.recyclerview.extensions.AsyncDifferConfig
|
||||
import android.support.v7.recyclerview.extensions.AsyncListDiffer
|
||||
import android.support.v7.util.DiffUtil
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.ViewGroup
|
||||
import ru.touchin.roboswag.components.extensions.setOnRippleClickListener
|
||||
|
||||
/**
|
||||
* Base adapter with delegation and diff computing on background thread.
|
||||
*/
|
||||
open class DelegationListAdapter<TItem>(config: AsyncDifferConfig<TItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
constructor(diffCallback: DiffUtil.ItemCallback<TItem>) : this(AsyncDifferConfig.Builder<TItem>(diffCallback).build())
|
||||
|
||||
var itemClickListener: ((TItem, RecyclerView.ViewHolder) -> Unit)? = null
|
||||
|
||||
private val delegatesManager = DelegatesManager()
|
||||
private var differ = AsyncListDiffer(OffsetAdapterUpdateCallback(this, ::getHeadersCount), config)
|
||||
|
||||
open fun getHeadersCount() = 0
|
||||
|
||||
open fun getFootersCount() = 0
|
||||
|
||||
override fun getItemCount() = getHeadersCount() + getList().size + getFootersCount()
|
||||
|
||||
override fun getItemViewType(position: Int) = delegatesManager.getItemViewType(getList(), position, getCollectionPosition(position))
|
||||
|
||||
override fun getItemId(position: Int) = delegatesManager.getItemId(getList(), position, getCollectionPosition(position))
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = delegatesManager.onCreateViewHolder(parent, viewType)
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
|
||||
val collectionPosition = getCollectionPosition(position)
|
||||
if (collectionPosition in 0 until getList().size) {
|
||||
if (itemClickListener != null) {
|
||||
holder.itemView.setOnRippleClickListener {
|
||||
itemClickListener?.invoke(getList()[getCollectionPosition(holder.adapterPosition)], holder)
|
||||
}
|
||||
} else {
|
||||
holder.itemView.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
delegatesManager.onBindViewHolder(holder, getList(), position, collectionPosition, payloads)
|
||||
}
|
||||
|
||||
final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = Unit
|
||||
|
||||
/**
|
||||
* Adds [AdapterDelegate] to adapter.
|
||||
*
|
||||
* @param delegate Delegate to add.
|
||||
*/
|
||||
fun addDelegate(delegate: AdapterDelegate<*>) = delegatesManager.addDelegate(delegate)
|
||||
|
||||
/**
|
||||
* Removes [AdapterDelegate] from adapter.
|
||||
*
|
||||
* @param delegate Delegate to remove.
|
||||
*/
|
||||
fun removeDelegate(delegate: AdapterDelegate<*>) = delegatesManager.removeDelegate(delegate)
|
||||
|
||||
/**
|
||||
* Submits a new list to be diffed, and displayed.
|
||||
*
|
||||
* If a list is already being displayed, a diff will be computed on a background thread, which
|
||||
* will dispatch Adapter.notifyItem events on the main thread.
|
||||
*
|
||||
* @param list The new list to be displayed.
|
||||
*/
|
||||
fun submitList(list: List<TItem>) = differ.submitList(list)
|
||||
|
||||
/**
|
||||
* Get the current List - any diffing to present this list has already been computed and
|
||||
* dispatched via the ListUpdateCallback.
|
||||
* <p>
|
||||
* If a <code>null</code> List, or no List has been submitted, an empty list will be returned.
|
||||
* <p>
|
||||
* The returned list may not be mutated - mutations to content must be done through
|
||||
* {@link #submitList(List)}.
|
||||
*
|
||||
* @return current List.
|
||||
*/
|
||||
fun getList(): List<TItem> = differ.currentList
|
||||
|
||||
fun getCollectionPosition(adapterPosition: Int) = adapterPosition - getHeadersCount()
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package ru.touchin.roboswag.components.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Such delegates are creating and binding ViewHolders for specific items.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate;
|
||||
* @param <TItem> Type of items to bind to {@link RecyclerView.ViewHolder}s.
|
||||
*/
|
||||
public abstract class ItemAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder, TItem> extends AdapterDelegate<TViewHolder> {
|
||||
|
||||
@Override
|
||||
public boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
return collectionPosition >= 0
|
||||
&& collectionPosition < items.size()
|
||||
&& isForViewType(items.get(collectionPosition), adapterPosition, collectionPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
* This item will be casted to {@link TItem} and passes to {@link #onBindViewHolder(TViewHolder, TItem, int, int, List)}.
|
||||
*
|
||||
* @param item Item to check;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public boolean isForViewType(@NonNull final Object item, final int adapterPosition, final int collectionPosition) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
//noinspection unchecked
|
||||
return getItemId((TItem) items.get(collectionPosition), adapterPosition, collectionPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param item Item in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(@NonNull final TItem item, final int adapterPosition, final int collectionPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
@NonNull final RecyclerView.ViewHolder holder,
|
||||
@NonNull final List<Object> items,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
) {
|
||||
//noinspection unchecked
|
||||
onBindViewHolder((TViewHolder) holder, (TItem) items.get(collectionPosition), adapterPosition, collectionPosition, payloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds item with payloads to created by this object ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind item to;
|
||||
* @param item Item in adapter;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param collectionPosition Position of item in collection that contains item;
|
||||
* @param payloads Payloads;
|
||||
*/
|
||||
public abstract void onBindViewHolder(
|
||||
@NonNull final TViewHolder holder,
|
||||
@NonNull final TItem item,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.touchin.roboswag.components.adapters
|
||||
|
||||
import android.support.v7.util.ListUpdateCallback
|
||||
import android.support.v7.widget.RecyclerView
|
||||
|
||||
class OffsetAdapterUpdateCallback(private val adapter: RecyclerView.Adapter<*>, private val offsetProvider: () -> Int) : ListUpdateCallback {
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
adapter.notifyItemRangeInserted(position + offsetProvider(), count)
|
||||
}
|
||||
|
||||
override fun onRemoved(position: Int, count: Int) {
|
||||
adapter.notifyItemRangeRemoved(position + offsetProvider(), count)
|
||||
}
|
||||
|
||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||
adapter.notifyItemMoved(fromPosition + offsetProvider(), toPosition + offsetProvider())
|
||||
}
|
||||
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
adapter.notifyItemRangeChanged(position + offsetProvider(), count, payload)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package ru.touchin.roboswag.components.adapters;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Objects of such class controls creation and binding of specific type of RecyclerView's ViewHolders.
|
||||
* Such delegates are creating and binding ViewHolders by position in adapter.
|
||||
* Default {@link #getItemViewType} is generating on construction of object.
|
||||
*
|
||||
* @param <TViewHolder> Type of {@link RecyclerView.ViewHolder} of delegate.
|
||||
*/
|
||||
public abstract class PositionAdapterDelegate<TViewHolder extends RecyclerView.ViewHolder> extends AdapterDelegate<TViewHolder> {
|
||||
|
||||
@Override
|
||||
public boolean isForViewType(@NonNull final List<Object> items, final int adapterPosition, final int collectionPosition) {
|
||||
return isForViewType(adapterPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if object is processable by this delegate.
|
||||
*
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @return True if item is processable by this delegate.
|
||||
*/
|
||||
public abstract boolean isForViewType(final int adapterPosition);
|
||||
|
||||
@Override
|
||||
public long getItemId(@NonNull final List<Object> objects, final int adapterPosition, final int itemsOffset) {
|
||||
return getItemId(adapterPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unique ID of item to support stable ID's logic of RecyclerView's adapter.
|
||||
*
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @return Unique item ID.
|
||||
*/
|
||||
public long getItemId(final int adapterPosition) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
@NonNull final RecyclerView.ViewHolder holder,
|
||||
@NonNull final List<Object> items,
|
||||
final int adapterPosition,
|
||||
final int collectionPosition,
|
||||
@NonNull final List<Object> payloads
|
||||
) {
|
||||
//noinspection unchecked
|
||||
onBindViewHolder((TViewHolder) holder, adapterPosition, payloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds position with payloads to ViewHolder.
|
||||
*
|
||||
* @param holder ViewHolder to bind position to;
|
||||
* @param adapterPosition Position of item in adapter;
|
||||
* @param payloads Payloads.
|
||||
*/
|
||||
public void onBindViewHolder(@NonNull final TViewHolder holder, final int adapterPosition, @NonNull final List<Object> payloads) {
|
||||
//do nothing by default
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
applicationId "ru.touchin.roboswag.components"
|
||||
minSdkVersion versions.minSdk
|
||||
targetSdkVersion versions.compileSdk
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "com.android.support:appcompat-v7:$versions.supportLibrary"
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
|
||||
}
|
||||
|
||||
ext.buildScriptsDir = "$rootDir/BuildScripts"
|
||||
apply from: "$buildScriptsDir/gradle/staticAnalysis.gradle"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="ru.touchin.roboswag.components">
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.touchin.roboswag.components
|
||||
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0"/>
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 10 KiB |