Start work on integrating Retrofit - Update Account entity to be fetched with Retrofit and parsed with GSON

This commit is contained in:
Eugen Rochko 2017-03-08 23:19:03 +01:00
parent 348d2c8b4f
commit 3120fbed4c
12 changed files with 219 additions and 193 deletions

View File

@ -1,92 +0,0 @@
/* Copyright 2017 Andrew Dawson
*
* This file is part of Tusky.
*
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
* <http://www.gnu.org/licenses/>. */
package com.keylesspalace.tusky;
import android.text.Spanned;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
class Account {
String id;
String username;
String displayName;
Spanned note;
String url;
String avatar;
String header;
String followersCount;
String followingCount;
String statusesCount;
public static Account parse(JSONObject object) throws JSONException {
Account account = new Account();
account.id = object.getString("id");
account.username = object.getString("acct");
account.displayName = object.getString("display_name");
if (account.displayName.isEmpty()) {
account.displayName = object.getString("username");
}
account.note = HtmlUtils.fromHtml(object.getString("note"));
account.url = object.getString("url");
String avatarUrl = object.getString("avatar");
if (!avatarUrl.equals("/avatars/original/missing.png")) {
account.avatar = avatarUrl;
} else {
account.avatar = null;
}
String headerUrl = object.getString("header");
if (!headerUrl.equals("/headers/original/missing.png")) {
account.header = headerUrl;
} else {
account.header = null;
}
account.followersCount = object.getString("followers_count");
account.followingCount = object.getString("following_count");
account.statusesCount = object.getString("statuses_count");
return account;
}
public static List<Account> parse(JSONArray array) throws JSONException {
List<Account> accounts = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
JSONObject object = array.getJSONObject(i);
Account account = parse(object);
accounts.add(account);
}
return accounts;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object other) {
if (this.id == null) {
return this == other;
} else if (!(other instanceof Account)) {
return false;
}
Account account = (Account) other;
return account.id.equals(this.id);
}
}

View File

@ -45,6 +45,7 @@ import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.keylesspalace.tusky.entity.Account;
import com.pkmmte.view.CircularImageView;
import com.squareup.picasso.Picasso;
@ -55,6 +56,9 @@ import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
public class AccountActivity extends BaseActivity {
private static final String TAG = "AccountActivity"; // Volley request tag and logging tag
@ -71,6 +75,7 @@ public class AccountActivity extends BaseActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_account);
createMastodonAPI();
Intent intent = getIntent();
accountId = intent.getStringExtra("id");
@ -169,37 +174,17 @@ public class AccountActivity extends BaseActivity {
}
private void obtainAccount() {
String endpoint = String.format(getString(R.string.endpoint_accounts), accountId);
String url = "https://" + domain + endpoint;
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Account account;
try {
account = Account.parse(response);
} catch (JSONException e) {
onObtainAccountFailure();
return;
}
onObtainAccountSuccess(account);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onObtainAccountFailure();
}
}) {
mastodonAPI.account(accountId).enqueue(new Callback<Account>() {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + accessToken);
return headers;
public void onResponse(Call<Account> call, retrofit2.Response<Account> response) {
onObtainAccountSuccess(response.body());
}
};
request.setTag(TAG);
VolleySingleton.getInstance(this).addToRequestQueue(request);
@Override
public void onFailure(Call<Account> call, Throwable t) {
onObtainAccountFailure();
}
});
}
private void onObtainAccountSuccess(Account account) {

View File

@ -17,6 +17,8 @@ package com.keylesspalace.tusky;
import android.support.v7.widget.RecyclerView;
import com.keylesspalace.tusky.entity.Account;
import java.util.ArrayList;
import java.util.List;

View File

@ -34,16 +34,16 @@ import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.StringRequest;
import org.json.JSONArray;
import org.json.JSONException;
import com.keylesspalace.tusky.entity.Account;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
public class AccountFragment extends Fragment implements AccountActionListener,
FooterActionListener {
private static final String TAG = "Account"; // logging tag and Volley request tag
@ -170,55 +170,39 @@ public class AccountFragment extends Fragment implements AccountActionListener,
}
private void fetchAccounts(final String fromId) {
MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
Callback<List<Account>> cb = new Callback<List<Account>>() {
@Override
public void onResponse(Call<List<Account>> call, retrofit2.Response<List<Account>> response) {
onFetchAccountsSuccess(response.body(), fromId);
}
@Override
public void onFailure(Call<List<Account>> call, Throwable t) {
onFetchAccountsFailure((Exception) t);
}
};
String endpoint;
switch (type) {
default:
case FOLLOWS: {
endpoint = String.format(getString(R.string.endpoint_following), accountId);
api.accountFollowing(accountId, fromId, null, null).enqueue(cb);
break;
}
case FOLLOWERS: {
endpoint = String.format(getString(R.string.endpoint_followers), accountId);
api.accountFollowers(accountId, fromId, null, null).enqueue(cb);
break;
}
case BLOCKS: {
endpoint = getString(R.string.endpoint_blocks);
api.blocks(fromId, null, null).enqueue(cb);
break;
}
}
String url = "https://" + domain + endpoint;
if (fromId != null) {
url += "?max_id=" + fromId;
}
JsonArrayRequest request = new JsonArrayRequest(url,
new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
List<Account> accounts;
try {
accounts = Account.parse(response);
} catch (JSONException e) {
onFetchAccountsFailure(e);
return;
}
onFetchAccountsSuccess(accounts, fromId);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onFetchAccountsFailure(error);
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + accessToken);
return headers;
}
};
request.setTag(TAG);
VolleySingleton.getInstance(getContext()).addToRequestQueue(request);
}
private void fetchAccounts() {

View File

@ -25,9 +25,13 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.text.Spanned;
import android.util.TypedValue;
import android.view.Menu;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import okhttp3.Interceptor;
@ -99,10 +103,14 @@ public class BaseActivity extends AppCompatActivity {
})
.build();
Gson gson = new GsonBuilder()
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(getBaseUrl())
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
mastodonAPI = retrofit.create(MastodonAPI.class);

View File

@ -23,6 +23,7 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.keylesspalace.tusky.entity.Account;
import com.squareup.picasso.Picasso;
import java.util.HashSet;

View File

@ -23,6 +23,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.keylesspalace.tusky.entity.Account;
import com.squareup.picasso.Picasso;
/** Both for follows and following lists. */

View File

@ -1,5 +1,6 @@
package com.keylesspalace.tusky;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Media;
import com.keylesspalace.tusky.entity.Relationship;
import com.keylesspalace.tusky.entity.StatusContext;
@ -21,18 +22,33 @@ import retrofit2.http.Query;
public interface MastodonAPI {
@GET("api/v1/timelines/home")
Call<List<Status>> homeTimeline(@Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Status>> homeTimeline(
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/timelines/public")
Call<List<Status>> publicTimeline(@Query("local") boolean local, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Status>> publicTimeline(
@Query("local") Boolean local,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/timelines/tag/{hashtag}")
Call<List<Status>> hashtagTimeline(@Path("hashtag") String hashtag, @Query("local") boolean local, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Status>> hashtagTimeline(
@Path("hashtag") String hashtag,
@Query("local") Boolean local,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/notifications")
Call<List<Notification>> notifications(@Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Notification>> notifications(
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@POST("api/v1/notifications/clear")
Call<ResponseBody> clearNotifications();
@GET("api/v1/notifications/{id}")
Call<Notification> notification(@Path("id") int notificationId);
Call<Notification> notification(@Path("id") String notificationId);
@Multipart
@POST("api/v1/media")
@ -40,67 +56,108 @@ public interface MastodonAPI {
@FormUrlEncoded
@POST("api/v1/statuses")
Call<Status> createStatus(@Field("status") String text, @Field("in_reply_to_id") int inReplyToId, @Field("spoiler_text") String warningText, @Field("visibility") String visibility, @Field("sensitive") boolean sensitive, @Field("media_ids[]") List<Integer> mediaIds);
Call<Status> createStatus(
@Field("status") String text,
@Field("in_reply_to_id") String inReplyToId,
@Field("spoiler_text") String warningText,
@Field("visibility") String visibility,
@Field("sensitive") Boolean sensitive,
@Field("media_ids[]") List<String> mediaIds);
@GET("api/v1/statuses/{id}")
Call<Status> status(@Path("id") int statusId);
Call<Status> status(@Path("id") String statusId);
@GET("api/v1/statuses/{id}/context")
Call<StatusContext> statusContext(@Path("id") int statusId);
Call<StatusContext> statusContext(@Path("id") String statusId);
@GET("api/v1/statuses/{id}/reblogged_by")
Call<List<Account>> statusRebloggedBy(@Path("id") int statusId, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> statusRebloggedBy(
@Path("id") String statusId,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/statuses/{id}/favourited_by")
Call<List<Account>> statusFavouritedBy(@Path("id") int statusId, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> statusFavouritedBy(
@Path("id") String statusId,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@DELETE("api/v1/statuses/{id}")
Call<ResponseBody> deleteStatus(@Path("id") int statusId);
Call<ResponseBody> deleteStatus(@Path("id") String statusId);
@POST("api/v1/statuses/{id}/reblog")
Call<Status> reblogStatus(@Path("id") int statusId);
Call<Status> reblogStatus(@Path("id") String statusId);
@POST("api/v1/statuses/{id}/unreblog")
Call<Status> unreblogStatus(@Path("id") int statusId);
Call<Status> unreblogStatus(@Path("id") String statusId);
@POST("api/v1/statuses/{id}/favourite")
Call<Status> favouriteStatus(@Path("id") int statusId);
Call<Status> favouriteStatus(@Path("id") String statusId);
@POST("api/v1/statuses/{id}/unfavourite")
Call<Status> unfavouriteStatus(@Path("id") int statusId);
Call<Status> unfavouriteStatus(@Path("id") String statusId);
@GET("api/v1/accounts/verify_credentials")
Call<Account> accountVerifyCredentials();
@GET("api/v1/accounts/search")
Call<List<Account>> searchAccounts(@Query("q") String q, @Query("resolve") boolean resolve, @Query("limit") int limit);
Call<List<Account>> searchAccounts(
@Query("q") String q,
@Query("resolve") Boolean resolve,
@Query("limit") Integer limit);
@GET("api/v1/accounts/{id}")
Call<Account> account(@Path("id") int accountId);
Call<Account> account(@Path("id") String accountId);
@GET("api/v1/accounts/{id}/statuses")
Call<List<Status>> accountStatuses(@Path("id") int accountId, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Status>> accountStatuses(
@Path("id") String accountId,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/accounts/{id}/followers")
Call<List<Account>> accountFollowers(@Path("id") int accountId, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> accountFollowers(
@Path("id") String accountId,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/accounts/{id}/following")
Call<List<Account>> accountFollowing(@Path("id") int accountId, @Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> accountFollowing(
@Path("id") String accountId,
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@POST("api/v1/accounts/{id}/follow")
Call<Relationship> followAccount(@Path("id") int accountId);
Call<Relationship> followAccount(@Path("id") String accountId);
@POST("api/v1/accounts/{id}/unfollow")
Call<Relationship> unfollowAccount(@Path("id") int accountId);
Call<Relationship> unfollowAccount(@Path("id") String accountId);
@POST("api/v1/accounts/{id}/block")
Call<Relationship> blockAccount(@Path("id") int accountId);
Call<Relationship> blockAccount(@Path("id") String accountId);
@POST("api/v1/accounts/{id}/unblock")
Call<Relationship> unblockAccount(@Path("id") int accountId);
Call<Relationship> unblockAccount(@Path("id") String accountId);
@POST("api/v1/accounts/{id}/mute")
Call<Relationship> muteAccount(@Path("id") int accountId);
Call<Relationship> muteAccount(@Path("id") String accountId);
@POST("api/v1/accounts/{id}/unmute")
Call<Relationship> unmuteAccount(@Path("id") int accountId);
Call<Relationship> unmuteAccount(@Path("id") String accountId);
@GET("api/v1/accounts/relationships")
Call<List<Relationship>> relationships(@Query("id[]") List<Integer> accountIds);
@GET("api/v1/blocks")
Call<List<Account>> blocks(@Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> blocks(
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/mutes")
Call<List<Account>> mutes(@Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> mutes(
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/favourites")
Call<List<Account>> favourites(@Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> favourites(
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@GET("api/v1/follow_requests")
Call<List<Account>> followRequests(@Query("max_id") int maxId, @Query("since_id") int sinceId, @Query("limit") int limit);
Call<List<Account>> followRequests(
@Query("max_id") String maxId,
@Query("since_id") String sinceId,
@Query("limit") Integer limit);
@POST("api/v1/follow_requests/{id}/authorize")
Call<Relationship> authorizeFollowRequest(@Path("id") int accountId);
Call<Relationship> authorizeFollowRequest(@Path("id") String accountId);
@POST("api/v1/follow_requests/{id}/reject")
Call<Relationship> rejectFollowRequest(@Path("id") int accountId);
Call<Relationship> rejectFollowRequest(@Path("id") String accountId);
}

View File

@ -0,0 +1,17 @@
package com.keylesspalace.tusky;
import android.text.Spanned;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
class SpannedTypeAdapter implements JsonDeserializer<Spanned> {
@Override
public Spanned deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return HtmlUtils.fromHtml(json.getAsString());
}
}

View File

@ -0,0 +1,63 @@
/* Copyright 2017 Andrew Dawson
*
* This file is part of Tusky.
*
* Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Tusky. If not, see
* <http://www.gnu.org/licenses/>. */
package com.keylesspalace.tusky.entity;
import android.text.Spanned;
import com.google.gson.annotations.SerializedName;
public class Account {
public String id;
@SerializedName("acct")
public String username;
@SerializedName("display_name")
public String displayName;
public Spanned note;
public String url;
public String avatar;
public String header;
@SerializedName("followers_count")
public String followersCount;
@SerializedName("following_count")
public String followingCount;
@SerializedName("statuses_count")
public String statusesCount;
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object other) {
if (this.id == null) {
return this == other;
} else if (!(other instanceof Account)) {
return false;
}
Account account = (Account) other;
return account.id.equals(this.id);
}
}

View File

@ -3,7 +3,7 @@ package com.keylesspalace.tusky.entity;
import com.google.gson.annotations.SerializedName;
public class Media {
public int id;
public String id;
public String type;

View File

@ -3,7 +3,7 @@ package com.keylesspalace.tusky.entity;
import com.google.gson.annotations.SerializedName;
public class Relationship {
public int id;
public String id;
public boolean following;