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

434 lines
20 KiB
Java
Raw Normal View History

package com.habitrpg.android.habitica;
2015-07-06 15:47:12 +00:00
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
2015-06-20 18:46:53 +00:00
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
2015-06-20 18:46:53 +00:00
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
2016-05-09 14:08:33 +00:00
import com.amplitude.api.Amplitude;
import com.crashlytics.android.Crashlytics;
2015-11-29 13:35:07 +00:00
import com.habitrpg.android.habitica.database.CheckListItemExcludeStrategy;
2015-06-20 18:46:53 +00:00
import com.magicmicky.habitrpgwrapper.lib.api.ApiService;
2015-11-29 20:10:32 +00:00
import com.magicmicky.habitrpgwrapper.lib.api.InAppPurchasesApiService;
import com.magicmicky.habitrpgwrapper.lib.api.MaintenanceApiService;
import com.magicmicky.habitrpgwrapper.lib.api.Server;
import com.magicmicky.habitrpgwrapper.lib.models.ChatMessage;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.ContentResult;
import com.magicmicky.habitrpgwrapper.lib.models.Customization;
2016-01-22 17:13:41 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.FAQArticle;
2016-03-05 13:54:19 +00:00
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;
import com.magicmicky.habitrpgwrapper.lib.models.Purchases;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.Skill;
2016-01-22 17:13:41 +00:00
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-03-28 16:43:15 +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;
2016-05-09 14:08:33 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.responses.FeedResponse;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ChecklistItem;
2016-03-10 13:05:32 +00:00
import com.magicmicky.habitrpgwrapper.lib.models.tasks.ItemData;
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;
import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskTag;
import com.magicmicky.habitrpgwrapper.lib.utils.ChatMessageDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.ChecklistItemSerializer;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.ContentDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.CustomizationDeserializer;
2016-01-21 13:02:09 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.DateDeserializer;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.EggListDeserializer;
2016-01-22 17:13:41 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.FAQArticleListDeserilializer;
2016-05-09 14:08:33 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.FeedResponseDeserializer;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.FoodListDeserializer;
2016-03-06 15:49:35 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.GroupSerialization;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.HatchingPotionListDeserializer;
2016-03-10 13:05:32 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.ItemDataListDeserializer;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.MountListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PetListDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.PurchasedDeserializer;
2016-03-28 16:43:15 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.QuestListDeserializer;
2015-11-25 17:47:56 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.SkillDeserializer;
import com.magicmicky.habitrpgwrapper.lib.utils.TaskListDeserializer;
2016-03-16 16:06:11 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.TaskSerializer;
2016-05-09 14:08:33 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.TaskTagDeserializer;
2016-01-22 17:13:41 +00:00
import com.magicmicky.habitrpgwrapper.lib.utils.TutorialStepListDeserializer;
2015-07-06 15:47:12 +00:00
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;
2016-05-23 17:53:55 +00:00
import android.util.Log;
2016-05-09 14:08:33 +00:00
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;
import java.lang.reflect.Type;
2016-05-09 14:08:33 +00:00
import java.net.ConnectException;
2016-05-18 18:17:18 +00:00
import java.util.ArrayList;
2016-01-21 13:02:09 +00:00
import java.util.Date;
2016-04-07 18:14:05 +00:00
import java.util.HashMap;
import java.util.List;
2016-05-18 18:17:18 +00:00
import java.util.Map;
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;
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;
2015-11-29 20:10:32 +00:00
private final InAppPurchasesApiService inAppPurchasesService;
public final MaintenanceApiService maintenanceService;
2016-05-09 14:08:33 +00:00
private final GsonConverterFactory gsonConverter;
private final Retrofit retrofitAdapter;
2015-11-29 20:10:32 +00:00
private HostConfig cfg;
2015-06-20 18:46:53 +00:00
2016-05-09 14:08:33 +00:00
final Observable.Transformer apiCallTransformer =
observable -> ((Observable)observable).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnError(this);
2016-05-12 11:44:29 +00:00
2016-05-09 14:08:33 +00:00
private AlertDialog displayedAlert;
2015-11-08 18:45:05 +00:00
//private OnHabitsAPIResult mResultListener;
//private HostConfig mConfig;
public APIHelper(final HostConfig cfg) {
2015-11-29 20:10:32 +00:00
this.cfg = cfg;
Crashlytics.getInstance().core.setUserIdentifier(cfg.getUser());
Crashlytics.getInstance().core.setUserName(cfg.getUser());
2016-02-02 20:19:07 +00:00
Amplitude.getInstance().setUserId(cfg.getUser());
2015-06-20 18:46:53 +00:00
2015-11-08 18:45:05 +00:00
Type taskTagClassListType = new TypeToken<List<TaskTag>>() {
}.getType();
2016-03-28 16:43:15 +00:00
Type skillListType = new TypeToken<List<Skill>>() {}.getType();
Type customizationListType = new TypeToken<List<Customization>>() {}.getType();
2016-01-22 17:13:41 +00:00
Type tutorialStepListType = new TypeToken<List<TutorialStep>>() {}.getType();
Type faqArticleListType = new TypeToken<List<FAQArticle>>() {}.getType();
2016-03-10 13:05:32 +00:00
Type itemDataListType = new TypeToken<List<ItemData>>() {}.getType();
2016-03-28 16:43:15 +00:00
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();
2016-04-07 18:14:05 +00:00
Type petListType = new TypeToken<HashMap<String, Pet>>() {}.getType();
Type mountListType = new TypeToken<HashMap<String, Mount>>() {}.getType();
2016-05-09 14:08:33 +00:00
//Exclusion strategy needed for DBFlow https://github.com/Raizlabs/DBFlow/issues/121
2015-11-08 18:45:05 +00:00
Gson gson = new GsonBuilder()
2015-11-29 13:35:07 +00:00
.setExclusionStrategies(new CheckListItemExcludeStrategy())
2015-11-08 18:45:05 +00:00
.setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaredClass().equals(ModelAdapter.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
2016-05-09 14:08:33 +00:00
.registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer())
2015-11-08 18:45:05 +00:00
.registerTypeAdapter(Boolean.class, booleanAsIntAdapter)
.registerTypeAdapter(boolean.class, booleanAsIntAdapter)
2016-03-28 16:43:15 +00:00
.registerTypeAdapter(skillListType, new SkillDeserializer())
.registerTypeAdapter(ChecklistItem.class, new ChecklistItemSerializer())
2016-05-09 14:08:33 +00:00
.registerTypeAdapter(TaskList.class, new TaskListDeserializer())
.registerTypeAdapter(Purchases.class, new PurchasedDeserializer())
.registerTypeAdapter(customizationListType, new CustomizationDeserializer())
2016-01-22 17:13:41 +00:00
.registerTypeAdapter(tutorialStepListType, new TutorialStepListDeserializer())
.registerTypeAdapter(faqArticleListType, new FAQArticleListDeserilializer())
2016-03-06 15:49:35 +00:00
.registerTypeAdapter(Group.class, new GroupSerialization())
2016-01-21 13:02:09 +00:00
.registerTypeAdapter(Date.class, new DateDeserializer())
2016-03-10 13:05:32 +00:00
.registerTypeAdapter(itemDataListType, new ItemDataListDeserializer())
2016-03-28 16:43:15 +00:00
.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())
2016-03-16 16:06:11 +00:00
.registerTypeAdapter(Task.class, new TaskSerializer())
2016-03-28 16:43:15 +00:00
.registerTypeAdapter(ContentResult.class, new ContentDeserializer())
2016-05-09 14:08:33 +00:00
.registerTypeAdapter(FeedResponse.class, new FeedResponseDeserializer())
2015-11-08 18:45:05 +00:00
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.create();
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) {
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
}
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(remove_data_interceptor)
.addInterceptor(logging)
.addNetworkInterceptor(chain -> {
Request original = chain.request();
if (cfg.getUser() != null) {
Request request = original.newBuilder()
.header("x-api-key", cfg.getApi())
.header("x-api-user", cfg.getUser())
.header("x-client", "habitica-android")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
} else {
return chain.proceed(original);
}
})
.build();
gsonConverter = GsonConverterFactory.create(gson);
2015-11-08 18:45:05 +00:00
Server server = new Server(cfg.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);
2015-06-20 18:46:53 +00:00
2015-11-29 20:10:32 +00:00
server = new Server(cfg.getAddress(), false);
2016-05-09 14:08:33 +00:00
Retrofit adapter = new Retrofit.Builder()
.baseUrl(server.toString())
.addConverterFactory(gsonConverter)
2015-11-29 20:10:32 +00:00
.build();
this.inAppPurchasesService = adapter.create(InAppPurchasesApiService.class);
2016-05-09 14:08:33 +00:00
adapter = new Retrofit.Builder()
.baseUrl("https://habitica-assets.s3.amazonaws.com/mobileApp/endpoint/")
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(gsonConverter)
.build();
this.maintenanceService = adapter.create(MaintenanceApiService.class);
}
2015-11-08 18:45:05 +00:00
private static final TypeAdapter<Boolean> booleanAsIntAdapter = new TypeAdapter<Boolean>() {
@Override
public void write(JsonWriter out, Boolean value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value);
}
}
@Override
public Boolean read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
switch (peek) {
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
case NUMBER:
return in.nextInt() != 0;
case STRING:
return Boolean.parseBoolean(in.nextString());
default:
throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek);
}
}
};
2015-06-20 18:46:53 +00:00
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-05-09 14:08:33 +00:00
return this.apiService.registerUser(auth)
.compose(this.configureApiCallObserver());
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-05-09 14:08:33 +00:00
return this.apiService.connectLocal(auth)
.compose(this.configureApiCallObserver());
2015-11-08 18:45:05 +00:00
}
2016-05-09 14:08:33 +00:00
public Observable<UserAuthResponse> connectSocial(String userId, String accessToken) {
2015-11-13 19:33:26 +00:00
UserAuthSocial auth = new UserAuthSocial();
auth.setNetwork("facebook");
UserAuthSocialTokens authResponse = new UserAuthSocialTokens();
authResponse.setClient_id(userId);
authResponse.setAccess_token(accessToken);
auth.setAuthResponse(authResponse);
2016-05-09 14:08:33 +00:00
return this.apiService.connectSocial(auth)
.compose(this.configureApiCallObserver());
2015-11-13 19:33:26 +00:00
}
2016-05-09 14:08:33 +00:00
@Override
public void call(Throwable throwable) {
if (throwable.getClass().equals(ConnectException.class)) {
this.showConnectionProblemDialog(R.string.internal_error_api);
} else if (throwable.getClass().equals(HttpException.class)) {
HttpException error = (HttpException)throwable;
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();
2015-11-18 12:05:38 +00:00
if (status == 401) {
2016-05-09 14:08:33 +00:00
if(res != null && res.message != null && !res.message.isEmpty()) {
showConnectionProblemDialog("", res.message);
} else {
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-03-29 18:00:24 +00:00
} else if (status == 400) {
2016-05-09 14:08:33 +00:00
if(res != null && res.message != null && !res.message.isEmpty()) {
showConnectionProblemDialog("", res.message);
2016-03-29 18:00:24 +00:00
}
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;
});
}
return userObservable.compose(configureApiCallObserver());
}
2016-05-18 18:17:18 +00:00
private List<Task> sortTasks(Map<String, Task> taskMap, List<String> taskOrder){
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-05-09 14:08:33 +00:00
public class ErrorResponse{
public String message;
}
2015-06-20 18:46:53 +00:00
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));
}
}
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 {
Call<PurchaseValidationResult> response = inAppPurchasesService.validatePurchase(cfg.getUser(), cfg.getApi(), request);
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
}
}