habitica-android/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java

407 lines
19 KiB
Java
Raw Normal View History

package com.habitrpg.android.habitica;
2016-06-06 16:45:09 +00:00
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
2016-05-09 14:08:33 +00:00
import com.amplitude.api.Amplitude;
import com.crashlytics.android.Crashlytics;
2016-06-06 16:45:09 +00:00
import com.habitrpg.android.habitica.database.CheckListItemExcludeStrategy;
2015-06-20 18:46:53 +00:00
import com.magicmicky.habitrpgwrapper.lib.api.ApiService;
import com.magicmicky.habitrpgwrapper.lib.api.Server;
2016-06-06 16:45:09 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.ChatMessage;
import com.magicmicky.habitrpgwrapper.lib.models.ContentResult;
import com.magicmicky.habitrpgwrapper.lib.models.Customization;
import com.magicmicky.habitrpgwrapper.lib.models.FAQArticle;
import com.magicmicky.habitrpgwrapper.lib.models.Group;
2016-05-09 14:08:33 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser;
2015-11-29 20:10:32 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.PurchaseValidationRequest;
import com.magicmicky.habitrpgwrapper.lib.models.PurchaseValidationResult;
2016-06-06 16:45:09 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.Purchases;
import com.magicmicky.habitrpgwrapper.lib.models.Skill;
import com.magicmicky.habitrpgwrapper.lib.models.TutorialStep;
import com.magicmicky.habitrpgwrapper.lib.models.UserAuth;
import com.magicmicky.habitrpgwrapper.lib.models.UserAuthResponse;
2015-11-13 19:33:26 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.UserAuthSocial;
import com.magicmicky.habitrpgwrapper.lib.models.UserAuthSocialTokens;
2016-06-06 16:45:09 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.inventory.Egg;
import com.magicmicky.habitrpgwrapper.lib.models.inventory.Food;
import com.magicmicky.habitrpgwrapper.lib.models.inventory.HatchingPotion;
import com.magicmicky.habitrpgwrapper.lib.models.inventory.Mount;
import com.magicmicky.habitrpgwrapper.lib.models.inventory.Pet;
import com.magicmicky.habitrpgwrapper.lib.models.inventory.QuestContent;
import com.magicmicky.habitrpgwrapper.lib.models.responses.FeedResponse;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ChecklistItem;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ItemData;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.RemindersItem;
2015-08-10 13:56:52 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task;
2016-05-09 14:08:33 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskList;
2016-06-06 16:45:09 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskTag;
import com.magicmicky.habitrpgwrapper.lib.utils.BooleanAsIntAdapter;
import com.magicmicky.habitrpgwrapper.lib.utils.ChatMessageDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.ChecklistItemSerializer;
import com.magicmicky.habitrpgwrapper.lib.utils.ContentDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.CustomizationDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.DateDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.EggListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.FAQArticleListDeserilializer;
import com.magicmicky.habitrpgwrapper.lib.utils.FeedResponseDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.FoodListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.GroupSerialization;
import com.magicmicky.habitrpgwrapper.lib.utils.HatchingPotionListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.ItemDataListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.MountListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PetListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PurchasedDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.QuestListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.RemindersItemSerializer;
2016-06-06 16:45:09 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.SkillDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskSerializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskTagDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TutorialStepListDeserializer;
import com.raizlabs.android.dbflow.structure.ModelAdapter;
2016-05-09 14:08:33 +00:00
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Activity;
import android.support.v7.app.AlertDialog;
2015-06-20 18:46:53 +00:00
import java.io.IOException;
2016-05-09 14:08:33 +00:00
import java.lang.annotation.Annotation;
2016-06-06 16:45:09 +00:00
import java.lang.reflect.Type;
2016-05-09 14:08:33 +00:00
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
2016-05-18 18:17:18 +00:00
import java.util.ArrayList;
2016-06-06 16:45:09 +00:00
import java.util.Date;
import java.util.HashMap;
import java.util.List;
2016-05-18 18:17:18 +00:00
import java.util.Map;
import javax.net.ssl.SSLException;
2016-05-09 14:08:33 +00:00
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
2016-06-22 17:35:04 +00:00
import okio.Buffer;
2016-05-09 14:08:33 +00:00
import retrofit2.Call;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.HttpException;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
public class APIHelper implements Action1<Throwable> {
2015-11-08 18:45:05 +00:00
// I think we don't need the APIHelper anymore we could just use ApiService
public final ApiService apiService;
2016-06-21 11:36:51 +00:00
final Observable.Transformer apiCallTransformer =
observable -> ((Observable) observable).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this);
2016-05-09 14:08:33 +00:00
private final GsonConverterFactory gsonConverter;
2016-06-02 08:58:22 +00:00
private final HostConfig hostConfig;
2016-05-09 14:08:33 +00:00
private final Retrofit retrofitAdapter;
private AlertDialog displayedAlert;
2015-11-08 18:45:05 +00:00
//private OnHabitsAPIResult mResultListener;
//private HostConfig mConfig;
2016-06-02 08:58:22 +00:00
public APIHelper(GsonConverterFactory gsonConverter, HostConfig hostConfig) {
this.gsonConverter = gsonConverter;
this.hostConfig = hostConfig;
Crashlytics.getInstance().core.setUserIdentifier(this.hostConfig.getUser());
Crashlytics.getInstance().core.setUserName(this.hostConfig.getUser());
Amplitude.getInstance().setUserId(this.hostConfig.getUser());
2015-11-08 18:45:05 +00:00
2016-05-09 14:08:33 +00:00
Interceptor remove_data_interceptor = chain -> {
Response response = chain.proceed(chain.request());
String stringJson = response.body().string();
JSONObject jsonObject = null;
String dataString = null;
try {
jsonObject = new JSONObject(stringJson);
if (jsonObject.has("data")) {
dataString = jsonObject.getString("data");
}
} catch (JSONException e) {
e.printStackTrace();
}
MediaType contentType = response.body().contentType();
ResponseBody body = null;
if (dataString != null) {
body = ResponseBody.create(contentType, dataString);
} else {
body = ResponseBody.create(contentType, stringJson);
}
return response.newBuilder().body(body).build();
};
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
2016-06-22 17:35:04 +00:00
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
2016-05-09 14:08:33 +00:00
}
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(remove_data_interceptor)
.addInterceptor(logging)
.addNetworkInterceptor(chain -> {
Request original = chain.request();
2016-06-02 08:58:22 +00:00
if (this.hostConfig.getUser() != null) {
2016-05-09 14:08:33 +00:00
Request request = original.newBuilder()
2016-06-02 08:58:22 +00:00
.header("x-api-key", this.hostConfig.getApi())
.header("x-api-user", this.hostConfig.getUser())
2016-05-09 14:08:33 +00:00
.header("x-client", "habitica-android")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
} else {
return chain.proceed(original);
}
})
.build();
2016-06-02 08:58:22 +00:00
Server server = new Server(this.hostConfig.getAddress());
2016-05-09 14:08:33 +00:00
retrofitAdapter = new Retrofit.Builder()
.client(client)
.baseUrl(server.toString())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(gsonConverter)
2015-11-08 18:45:05 +00:00
.build();
2016-05-09 14:08:33 +00:00
this.apiService = retrofitAdapter.create(ApiService.class);
}
2016-06-06 16:45:09 +00:00
public static GsonConverterFactory createGsonFactory() {
2016-06-21 11:36:51 +00:00
Type taskTagClassListType = new TypeToken<List<TaskTag>>() {
}.getType();
Type skillListType = new TypeToken<List<Skill>>() {
}.getType();
Type customizationListType = new TypeToken<List<Customization>>() {
}.getType();
Type tutorialStepListType = new TypeToken<List<TutorialStep>>() {
}.getType();
Type faqArticleListType = new TypeToken<List<FAQArticle>>() {
}.getType();
Type itemDataListType = new TypeToken<List<ItemData>>() {
}.getType();
Type eggListType = new TypeToken<List<Egg>>() {
}.getType();
Type foodListType = new TypeToken<List<Food>>() {
}.getType();
Type hatchingPotionListType = new TypeToken<List<HatchingPotion>>() {
}.getType();
Type questContentListType = new TypeToken<List<QuestContent>>() {
}.getType();
Type petListType = new TypeToken<HashMap<String, Pet>>() {
}.getType();
Type mountListType = new TypeToken<HashMap<String, Mount>>() {
}.getType();
2016-06-06 16:45:09 +00:00
//Exclusion strategy needed for DBFlow https://github.com/Raizlabs/DBFlow/issues/121
Gson gson = new GsonBuilder()
.setExclusionStrategies(new CheckListItemExcludeStrategy())
.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaredClass().equals(ModelAdapter.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer())
.registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter())
.registerTypeAdapter(boolean.class, new BooleanAsIntAdapter())
.registerTypeAdapter(skillListType, new SkillDeserializer())
.registerTypeAdapter(ChecklistItem.class, new ChecklistItemSerializer())
.registerTypeAdapter(RemindersItem.class, new RemindersItemSerializer())
2016-06-06 16:45:09 +00:00
.registerTypeAdapter(TaskList.class, new TaskListDeserializer())
.registerTypeAdapter(Purchases.class, new PurchasedDeserializer())
.registerTypeAdapter(customizationListType, new CustomizationDeserializer())
.registerTypeAdapter(tutorialStepListType, new TutorialStepListDeserializer())
.registerTypeAdapter(faqArticleListType, new FAQArticleListDeserilializer())
.registerTypeAdapter(Group.class, new GroupSerialization())
.registerTypeAdapter(Date.class, new DateDeserializer())
.registerTypeAdapter(itemDataListType, new ItemDataListDeserializer())
.registerTypeAdapter(eggListType, new EggListDeserializer())
.registerTypeAdapter(foodListType, new FoodListDeserializer())
.registerTypeAdapter(hatchingPotionListType, new HatchingPotionListDeserializer())
.registerTypeAdapter(questContentListType, new QuestListDeserializer())
.registerTypeAdapter(petListType, new PetListDeserializer())
.registerTypeAdapter(mountListType, new MountListDeserializer())
.registerTypeAdapter(ChatMessage.class, new ChatMessageDeserializer())
.registerTypeAdapter(Task.class, new TaskSerializer())
.registerTypeAdapter(ContentResult.class, new ContentDeserializer())
.registerTypeAdapter(FeedResponse.class, new FeedResponseDeserializer())
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.create();
return GsonConverterFactory.create(gson);
}
2016-05-09 14:08:33 +00:00
public Observable<UserAuthResponse> registerUser(String username, String email, String password, String confirmPassword) {
2015-12-07 20:49:49 +00:00
UserAuth auth = new UserAuth();
auth.setUsername(username);
auth.setPassword(password);
auth.setConfirmPassword(confirmPassword);
auth.setEmail(email);
2016-06-07 12:27:27 +00:00
return this.apiService.registerUser(auth);
2015-11-08 18:45:05 +00:00
}
2016-05-09 14:08:33 +00:00
public Observable<UserAuthResponse> connectUser(String username, String password) {
UserAuth auth = new UserAuth();
auth.setUsername(username);
auth.setPassword(password);
2016-06-07 12:27:27 +00:00
return this.apiService.connectLocal(auth);
2015-11-08 18:45:05 +00:00
}
2016-06-21 11:36:51 +00:00
public Observable<UserAuthResponse> connectSocial(String userId, String accessToken) {
UserAuthSocial auth = new UserAuthSocial();
auth.setNetwork("facebook");
2015-11-13 19:33:26 +00:00
UserAuthSocialTokens authResponse = new UserAuthSocialTokens();
authResponse.setClient_id(userId);
authResponse.setAccess_token(accessToken);
auth.setAuthResponse(authResponse);
2016-06-21 11:36:51 +00:00
return this.apiService.connectSocial(auth);
}
2015-11-13 19:33:26 +00:00
2016-05-09 14:08:33 +00:00
@Override
public void call(Throwable throwable) {
final Class<?> throwableClass = throwable.getClass();
if (throwableClass.equals(ConnectException.class) || throwableClass.isAssignableFrom(SSLException.class)) {
2016-05-09 14:08:33 +00:00
this.showConnectionProblemDialog(R.string.internal_error_api);
} else if (throwableClass.equals(SocketTimeoutException.class) || throwableClass.equals(UnknownHostException.class)) {
this.showConnectionProblemDialog(R.string.network_error_no_network_body);
} else if (throwableClass.equals(HttpException.class)) {
2016-06-21 11:36:51 +00:00
HttpException error = (HttpException) throwable;
2016-05-09 14:08:33 +00:00
retrofit2.Response<?> response = error.response();
ErrorResponse res = null;
2016-05-09 14:08:33 +00:00
Converter<ResponseBody, ?> errorConverter =
gsonConverter
.responseBodyConverter(ErrorResponse.class, new Annotation[0], retrofitAdapter);
try {
2016-05-09 14:08:33 +00:00
res = (ErrorResponse) errorConverter.convert(response.errorBody());
} catch (IOException e) {
e.printStackTrace();
}
2016-05-09 14:08:33 +00:00
int status = error.code();
2016-06-23 10:37:14 +00:00
if (status >= 400 && status < 500) {
2016-06-21 11:36:51 +00:00
if (res != null && res.message != null && !res.message.isEmpty()) {
2016-05-09 14:08:33 +00:00
showConnectionProblemDialog("", res.message);
2016-06-23 10:37:14 +00:00
} else if (status == 401) {
showConnectionProblemDialog(R.string.authentication_error_title, R.string.authentication_error_body);
}
2015-11-18 12:05:38 +00:00
} else if (status >= 500 && status < 600) {
this.showConnectionProblemDialog(R.string.internal_error_api);
2016-05-09 14:08:33 +00:00
} else {
showConnectionProblemDialog(R.string.internal_error_api);
2015-11-18 12:05:38 +00:00
}
2016-05-26 10:27:17 +00:00
} else {
Crashlytics.logException(throwable);
2016-05-09 14:08:33 +00:00
}
}
2015-11-18 12:05:38 +00:00
2016-05-09 14:08:33 +00:00
public Observable<HabitRPGUser> retrieveUser(boolean withTasks) {
Observable<HabitRPGUser> userObservable = apiService.getUser();
if (withTasks) {
Observable<TaskList> tasksObservable = apiService.getTasks();
userObservable = Observable.zip(userObservable, tasksObservable, (habitRPGUser, tasks) -> {
2016-05-18 18:17:18 +00:00
habitRPGUser.setHabits(sortTasks(tasks.tasks, habitRPGUser.getTasksOrder().getHabits()));
habitRPGUser.setDailys(sortTasks(tasks.tasks, habitRPGUser.getTasksOrder().getDailys()));
habitRPGUser.setTodos(sortTasks(tasks.tasks, habitRPGUser.getTasksOrder().getTodos()));
habitRPGUser.setRewards(sortTasks(tasks.tasks, habitRPGUser.getTasksOrder().getRewards()));
2016-05-09 14:08:33 +00:00
return habitRPGUser;
});
}
2016-06-06 16:45:09 +00:00
return userObservable;
2016-05-09 14:08:33 +00:00
}
2016-06-21 11:36:51 +00:00
private List<Task> sortTasks(Map<String, Task> taskMap, List<String> taskOrder) {
2016-05-18 18:17:18 +00:00
List<Task> taskList = new ArrayList<>();
int position = 0;
for (String taskId : taskOrder) {
Task task = taskMap.get(taskId);
2016-05-23 17:53:55 +00:00
if (task != null) {
task.position = position;
taskList.add(task);
position++;
}
2016-05-18 18:17:18 +00:00
}
return taskList;
}
2016-06-17 10:44:06 +00:00
public boolean hasAuthenticationKeys() {
return this.hostConfig.getUser() != null;
}
private void showConnectionProblemDialog(final int resourceMessageString) {
showConnectionProblemDialog(R.string.network_error_title, resourceMessageString);
2015-11-18 12:05:38 +00:00
}
private void showConnectionProblemDialog(final int resourceTitleString, final int resourceMessageString) {
Activity currentActivity = HabiticaApplication.currentActivity;
if (currentActivity != null) {
showConnectionProblemDialog(currentActivity.getString(resourceTitleString), currentActivity.getString(resourceMessageString));
}
}
2016-06-21 11:36:51 +00:00
private void showConnectionProblemDialog(final String resourceTitleString, final String resourceMessageString) {
2016-04-12 13:04:10 +00:00
HabiticaApplication.currentActivity.runOnUiThread(() -> {
2016-05-09 14:08:33 +00:00
if (!(HabiticaApplication.currentActivity).isFinishing() && displayedAlert == null) {
2016-04-12 13:04:10 +00:00
AlertDialog.Builder builder = new AlertDialog.Builder(HabiticaApplication.currentActivity)
.setTitle(resourceTitleString)
.setMessage(resourceMessageString)
.setNeutralButton(android.R.string.ok, (dialog, which) -> {
2016-05-09 14:08:33 +00:00
displayedAlert = null;
2016-04-12 13:04:10 +00:00
});
if (!resourceTitleString.isEmpty()) {
builder.setIcon(R.drawable.ic_warning_black);
}
2016-04-12 13:04:10 +00:00
2016-05-09 14:08:33 +00:00
displayedAlert = builder.show();
}
});
}
2016-05-09 14:08:33 +00:00
public PurchaseValidationResult validatePurchase(PurchaseValidationRequest request) throws IOException {
2016-06-21 11:36:51 +00:00
Call<PurchaseValidationResult> response = apiService.validatePurchase(request);
2016-05-09 14:08:33 +00:00
return response.execute().body();
}
2015-11-29 20:10:32 +00:00
2016-05-09 14:08:33 +00:00
@SuppressWarnings("unchecked")
public <T> Observable.Transformer<T, T> configureApiCallObserver() {
return (Observable.Transformer<T, T>) apiCallTransformer;
2015-11-29 20:10:32 +00:00
}
2016-06-02 08:58:22 +00:00
public void updateAuthenticationCredentials(String userID, String apiToken) {
this.hostConfig.setUser(userID);
this.hostConfig.setApi(apiToken);
Crashlytics.getInstance().core.setUserIdentifier(this.hostConfig.getUser());
Crashlytics.getInstance().core.setUserName(this.hostConfig.getUser());
Amplitude.getInstance().setUserId(this.hostConfig.getUser());
}
2016-06-21 11:36:51 +00:00
public class ErrorResponse {
public String message;
}
}