hashtag and location fixes

1. convert api to kotlin
2. fix follow button for hashtags
This commit is contained in:
Austin Huang 2021-07-24 19:29:59 -04:00
parent e26d9db6bf
commit 94a5fdc6fe
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
16 changed files with 256 additions and 451 deletions

View File

@ -9,12 +9,13 @@ import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.GraphQLRepository;
import awais.instagrabber.webservices.HashtagRepository;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.Dispatchers;
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
private final TagsService tagsService;
private final HashtagRepository hashtagRepository;
private final GraphQLRepository graphQLRepository;
private final Hashtag hashtagModel;
private String nextMaxId;
@ -24,42 +25,31 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
public HashtagPostFetchService(final Hashtag hashtagModel, final boolean isLoggedIn) {
this.hashtagModel = hashtagModel;
this.isLoggedIn = isLoggedIn;
tagsService = isLoggedIn ? TagsService.getInstance() : null;
hashtagRepository = isLoggedIn ? HashtagRepository.Companion.getInstance() : null;
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
}
@Override
public void fetch(final FetchListener<List<Media>> fetchListener) {
final ServiceCallback<PostsFetchResponse> cb = new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextCursor();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
final Continuation<PostsFetchResponse> cb = CoroutineUtilsKt.getContinuation((result, t) -> {
if (t != null) {
if (fetchListener != null) {
fetchListener.onFailure(t);
}
return;
}
};
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
if (result == null) return;
nextMaxId = result.getNextCursor();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}, Dispatchers.getIO());
if (isLoggedIn) hashtagRepository.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else graphQLRepository.fetchHashtagPosts(
hashtagModel.getName().toLowerCase(),
nextMaxId,
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(postsFetchResponse);
}, Dispatchers.getIO())
cb
);
}

View File

@ -9,12 +9,13 @@ import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.CoroutineUtilsKt;
import awais.instagrabber.webservices.GraphQLRepository;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.LocationRepository;
import awais.instagrabber.webservices.ServiceCallback;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.Dispatchers;
public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationService locationService;
private final LocationRepository locationRepository;
private final GraphQLRepository graphQLRepository;
private final Location locationModel;
private String nextMaxId;
@ -24,42 +25,31 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
public LocationPostFetchService(final Location locationModel, final boolean isLoggedIn) {
this.locationModel = locationModel;
this.isLoggedIn = isLoggedIn;
locationService = isLoggedIn ? LocationService.getInstance() : null;
locationRepository = isLoggedIn ? LocationRepository.Companion.getInstance() : null;
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
}
@Override
public void fetch(final FetchListener<List<Media>> fetchListener) {
final ServiceCallback<PostsFetchResponse> cb = new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextCursor();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
final Continuation<PostsFetchResponse> cb = CoroutineUtilsKt.getContinuation((result, t) -> {
if (t != null) {
if (fetchListener != null) {
fetchListener.onFailure(t);
}
return;
}
};
if (isLoggedIn) locationService.fetchPosts(locationModel.getPk(), nextMaxId, cb);
if (result == null) return;
nextMaxId = result.getNextCursor();
moreAvailable = result.getHasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
}, Dispatchers.getIO());
if (isLoggedIn) locationRepository.fetchPosts(locationModel.getPk(), nextMaxId, cb);
else graphQLRepository.fetchLocationPosts(
locationModel.getPk(),
nextMaxId,
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(postsFetchResponse);
}, Dispatchers.getIO())
cb
);
}

View File

@ -48,7 +48,6 @@ import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.FollowingType;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.Media;
@ -61,8 +60,9 @@ import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.GraphQLRepository;
import awais.instagrabber.webservices.HashtagRepository;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.Dispatchers;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -80,7 +80,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private ActionMode actionMode;
// private StoriesRepository storiesRepository;
private boolean isLoggedIn;
private TagsService tagsService;
private HashtagRepository hashtagRepository;
private GraphQLRepository graphQLRepository;
// private boolean storiesFetching;
private Set<Media> selectedFeedModels;
@ -264,19 +264,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
}
};
private final ServiceCallback<Hashtag> cb = new ServiceCallback<Hashtag>() {
@Override
public void onSuccess(final Hashtag result) {
hashtagModel = result;
binding.swipeRefreshLayout.setRefreshing(false);
setHashtagDetails();
}
@Override
public void onFailure(final Throwable t) {
setHashtagDetails();
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -284,7 +271,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
fragmentActivity = (MainActivity) requireActivity();
final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
tagsService = isLoggedIn ? TagsService.getInstance() : null;
hashtagRepository = isLoggedIn ? HashtagRepository.Companion.getInstance() : null;
// storiesRepository = isLoggedIn ? StoriesRepository.Companion.getInstance() : null;
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
setHasOptionsMenu(true);
@ -349,19 +336,20 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
hashtag = fragmentArgs.getHashtag();
if (hashtag.charAt(0) == '#') hashtag = hashtag.substring(1);
fetchHashtagModel();
fetchHashtagModel(true);
}
private void fetchHashtagModel() {
private void fetchHashtagModel(final boolean init) {
binding.swipeRefreshLayout.setRefreshing(true);
if (isLoggedIn) tagsService.fetch(hashtag, cb);
else graphQLRepository.fetchTag(hashtag, CoroutineUtilsKt.getContinuation((hashtag1, throwable) -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
AppExecutors.INSTANCE.getMainThread().execute(() -> cb.onSuccess(hashtag1));
}, Dispatchers.getIO()));
final Continuation<Hashtag> cb = CoroutineUtilsKt.getContinuation((result, t) -> {
hashtagModel = result;
AppExecutors.INSTANCE.getMainThread().execute(() -> {
setHashtagDetails(init);
binding.swipeRefreshLayout.setRefreshing(false);
});
}, Dispatchers.getIO());
if (isLoggedIn) hashtagRepository.fetch(hashtag, cb);
else graphQLRepository.fetchTag(hashtag, cb);
}
private void setupPosts() {
@ -386,7 +374,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
// });
}
private void setHashtagDetails() {
private void setHashtagDetails(final boolean init) {
if (hashtagModel == null) {
try {
Toast.makeText(getContext(), R.string.error_loading_hashtag, Toast.LENGTH_SHORT).show();
@ -394,14 +382,16 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
} catch (Exception ignored) {}
return;
}
setTitle();
setupPosts();
if (init) {
setTitle();
setupPosts();
}
if (isLoggedIn) {
hashtagDetailsBinding.btnFollowTag.setVisibility(View.VISIBLE);
hashtagDetailsBinding.btnFollowTag.setText(hashtagModel.getFollowing() == FollowingType.FOLLOWING
hashtagDetailsBinding.btnFollowTag.setText(hashtagModel.getFollow()
? R.string.unfollow
: R.string.follow);
hashtagDetailsBinding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing() == FollowingType.FOLLOWING
hashtagDetailsBinding.btnFollowTag.setChipIconResource(hashtagModel.getFollow()
? R.drawable.ic_outline_person_add_disabled_24
: R.drawable.ic_outline_person_add_24);
hashtagDetailsBinding.btnFollowTag.setOnClickListener(v -> {
@ -411,29 +401,15 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
if (csrfToken != null && userId != 0) {
hashtagDetailsBinding.btnFollowTag.setClickable(false);
tagsService.changeFollow(
hashtagModel.getFollowing() == FollowingType.FOLLOWING ? "unfollow" : "follow",
hashtagRepository.changeFollow(
hashtagModel.getFollow() ? "unfollow" : "follow",
hashtag,
csrfToken,
userId,
deviceUuid,
new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow);
hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24);
}
@Override
public void onFailure(@NonNull final Throwable t) {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
CoroutineUtilsKt.getContinuation((result, t) -> {
hashtagDetailsBinding.btnFollowTag.setClickable(true);
if (t != null) {
Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage();
Snackbar.make(
@ -441,8 +417,17 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
message != null ? message : getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
});
if (result != true) {
Log.e(TAG, "onSuccess: result is false");
Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
fetchHashtagModel(false);
})
);
}
});
} else {
@ -451,6 +436,18 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE);
final Context context = getContext();
if (context == null) return;
final String postCount = String.valueOf(hashtagModel.getMediaCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(
R.plurals.main_posts_count_inline,
hashtagModel.getMediaCount() > 2000000000L ? 2000000000
: Long.valueOf(hashtagModel.getMediaCount()).intValue(),
postCount)
);
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
hashtagDetailsBinding.mainTagPostCount.setText(span);
hashtagDetailsBinding.mainTagPostCount.setVisibility(View.VISIBLE);
if (!init) return;
final FavoriteRepository favoriteRepository = FavoriteRepository.Companion.getInstance(context);
favoriteRepository.getFavorite(
hashtag,
@ -528,17 +525,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
)
);
hashtagDetailsBinding.mainHashtagImage.setImageURI("res:/" + R.drawable.ic_hashtag);
final String postCount = String.valueOf(hashtagModel.getMediaCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(
R.plurals.main_posts_count_inline,
hashtagModel.getMediaCount() > 2000000000L ? 2000000000
: Long.valueOf(hashtagModel.getMediaCount()).intValue(),
postCount)
);
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
hashtagDetailsBinding.mainTagPostCount.setText(span);
hashtagDetailsBinding.mainTagPostCount.setVisibility(View.VISIBLE);
// hashtagDetailsBinding.mainHashtagImage.setOnClickListener(v -> {
// if (!hasStories) return;
// // show stories

View File

@ -58,8 +58,8 @@ import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.GraphQLRepository;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.LocationRepository;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.Dispatchers;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -76,7 +76,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private Location locationModel;
private ActionMode actionMode;
private GraphQLRepository graphQLRepository;
private LocationService locationService;
private LocationRepository locationRepository;
private boolean isLoggedIn;
private Set<Media> selectedFeedModels;
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_LOCATION_POSTS_LAYOUT);
@ -257,19 +257,6 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
}
}
};
private final ServiceCallback<Location> cb = new ServiceCallback<Location>() {
@Override
public void onSuccess(final Location result) {
locationModel = result;
binding.swipeRefreshLayout.setRefreshing(false);
setupLocationDetails();
}
@Override
public void onFailure(final Throwable t) {
setupLocationDetails();
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -277,7 +264,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
fragmentActivity = (MainActivity) requireActivity();
final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
locationService = isLoggedIn ? LocationService.getInstance() : null;
locationRepository = isLoggedIn ? LocationRepository.Companion.getInstance() : null;
// storiesRepository = StoriesRepository.Companion.getInstance();
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
setHasOptionsMenu(true);
@ -372,17 +359,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private void fetchLocationModel() {
binding.swipeRefreshLayout.setRefreshing(true);
if (isLoggedIn) locationService.fetch(locationId, cb);
else graphQLRepository.fetchLocation(
locationId,
CoroutineUtilsKt.getContinuation((location, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
if (throwable != null) {
cb.onFailure(throwable);
return;
}
cb.onSuccess(location);
}))
);
final Continuation<Location> cb = CoroutineUtilsKt.getContinuation((result, t) -> {
locationModel = result;
AppExecutors.INSTANCE.getMainThread().execute(() -> {
setupLocationDetails();
binding.swipeRefreshLayout.setRefreshing(false);
});
}, Dispatchers.getIO());
if (isLoggedIn) locationRepository.fetch(locationId, cb);
else graphQLRepository.fetchLocation(locationId, cb);
}
private void setupLocationDetails() {

View File

@ -1,24 +0,0 @@
package awais.instagrabber.models.enums
import java.io.Serializable
import java.util.*
enum class FollowingType(val id: Int) : Serializable {
FOLLOWING(1),
NOT_FOLLOWING(0);
companion object {
private val map: MutableMap<Int, FollowingType> = mutableMapOf()
@JvmStatic
fun valueOf(id: Int): FollowingType? {
return map[id]
}
init {
for (type in values()) {
map[type.id] = type
}
}
}
}

View File

@ -0,0 +1,25 @@
package awais.instagrabber.repositories
import awais.instagrabber.repositories.responses.Hashtag
import awais.instagrabber.repositories.responses.TagFeedResponse
import retrofit2.Call
import retrofit2.http.*
interface HashtagService {
@GET("/api/v1/tags/{tag}/info/")
suspend fun fetch(@Path("tag") tag: String?): Hashtag?
@FormUrlEncoded
@POST("/api/v1/tags/{action}/{tag}/")
suspend fun changeFollow(
@FieldMap signedForm: Map<String?, String?>?,
@Path("action") action: String?,
@Path("tag") tag: String?
): String?
@GET("/api/v1/feed/tag/{tag}/")
suspend fun fetchPosts(
@Path("tag") tag: String?,
@QueryMap queryParams: Map<String?, String?>?
): TagFeedResponse?
}

View File

@ -1,19 +0,0 @@
package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.LocationFeedResponse;
import awais.instagrabber.repositories.responses.Place;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface LocationRepository {
@GET("/api/v1/locations/{location}/info/")
Call<Place> fetch(@Path("location") final long locationId);
@GET("/api/v1/feed/location/{location}/")
Call<LocationFeedResponse> fetchPosts(@Path("location") final long locationId,
@QueryMap Map<String, String> queryParams);
}

View File

@ -0,0 +1,19 @@
package awais.instagrabber.repositories
import retrofit2.http.GET
import awais.instagrabber.repositories.responses.Place
import awais.instagrabber.repositories.responses.LocationFeedResponse
import retrofit2.Call
import retrofit2.http.Path
import retrofit2.http.QueryMap
interface LocationService {
@GET("/api/v1/locations/{location}/info/")
suspend fun fetch(@Path("location") locationId: Long): Place?
@GET("/api/v1/feed/location/{location}/")
suspend fun fetchPosts(
@Path("location") locationId: Long,
@QueryMap queryParams: Map<String?, String?>?
): LocationFeedResponse?
}

View File

@ -1,29 +0,0 @@
package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.TagFeedResponse;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface TagsRepository {
@GET("/api/v1/tags/{tag}/info/")
Call<Hashtag> fetch(@Path("tag") final String tag);
@FormUrlEncoded
@POST("/api/v1/tags/{action}/{tag}/")
Call<String> changeFollow(@FieldMap final Map<String, String> signedForm,
@Path("action") String action,
@Path("tag") String tag);
@GET("/api/v1/feed/tag/{tag}/")
Call<TagFeedResponse> fetchPosts(@Path("tag") final String tag,
@QueryMap Map<String, String> queryParams);
}

View File

@ -1,12 +1,14 @@
package awais.instagrabber.repositories.responses
import awais.instagrabber.models.enums.FollowingType
import java.io.Serializable
data class Hashtag(
val id: String,
val name: String,
val mediaCount: Long,
val following: FollowingType?, // 0 false 1 true; not on search results
val following: Int?, // 0 false 1 true; not on search results
val searchResultSubtitle: String? // shows how many posts there are on search results
) : Serializable
) : Serializable {
val follow: Boolean
get() = following == 1
}

View File

@ -3,9 +3,9 @@ package awais.instagrabber.repositories.responses
data class LocationFeedResponse(
val numResults: Int,
val nextMaxId: String?,
val moreAvailable: Boolean?,
val moreAvailable: Boolean,
val mediaCount: Long?,
val status: String,
val items: List<Media>?,
val items: List<Media>,
val location: Location
)

View File

@ -262,7 +262,7 @@ open class GraphQLRepository(private val service: GraphQLService) {
body.getString(Constants.EXTRAS_ID),
body.getString("name"),
timelineMedia.getLong("count"),
if (body.optBoolean("is_following")) FollowingType.FOLLOWING else FollowingType.NOT_FOLLOWING,
if (body.optBoolean("is_following")) 1 else 0,
null
)
}

View File

@ -0,0 +1,64 @@
package awais.instagrabber.webservices
import android.util.Log
import awais.instagrabber.repositories.HashtagService
import awais.instagrabber.repositories.responses.Hashtag
import awais.instagrabber.repositories.responses.PostsFetchResponse
import awais.instagrabber.repositories.responses.TagFeedResponse
import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.utils.Utils
import awais.instagrabber.webservices.RetrofitFactory.retrofit
import com.google.common.collect.ImmutableMap
import org.json.JSONException
import org.json.JSONObject
import retrofit2.Call
import retrofit2.Callback
import java.util.*
open class HashtagRepository(private val repository: HashtagService) {
suspend fun fetch(tag: String): Hashtag? {
return repository.fetch(tag)
}
suspend fun changeFollow(
action: String,
tag: String,
csrfToken: String,
userId: Long,
deviceUuid: String
): Boolean {
val form: MutableMap<String, Any> = HashMap(3)
form["_csrftoken"] = csrfToken
form["_uid"] = userId
form["_uuid"] = deviceUuid
val signedForm = Utils.sign(form)
val body = repository.changeFollow(signedForm, action, tag) ?: return false
val jsonObject = JSONObject(body)
return jsonObject.optString("status") == "ok"
}
suspend fun fetchPosts(tag: String, maxId: String?): PostsFetchResponse? {
val builder = ImmutableMap.builder<String?, String?>()
if (!isEmpty(maxId)) {
builder.put("max_id", maxId)
}
val body = repository.fetchPosts(tag, builder.build()) ?: return null
return PostsFetchResponse(
body.items,
body.moreAvailable,
body.nextMaxId
)
}
companion object {
@Volatile
private var INSTANCE: HashtagRepository? = null
fun getInstance(): HashtagRepository {
return INSTANCE ?: synchronized(this) {
val service = RetrofitFactory.retrofit.create(HashtagService::class.java)
HashtagRepository(service).also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,46 @@
package awais.instagrabber.webservices
import awais.instagrabber.repositories.HashtagService
import awais.instagrabber.repositories.responses.Location
import awais.instagrabber.repositories.responses.LocationFeedResponse
import awais.instagrabber.repositories.responses.Place
import awais.instagrabber.repositories.responses.PostsFetchResponse
import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.repositories.LocationService
import awais.instagrabber.webservices.RetrofitFactory.retrofit
import com.google.common.collect.ImmutableMap
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
open class LocationRepository(private val repository: LocationService) {
suspend fun fetchPosts(locationId: Long, maxId: String): PostsFetchResponse? {
val builder = ImmutableMap.builder<String, String>()
if (!isEmpty(maxId)) {
builder.put("max_id", maxId)
}
val body = repository.fetchPosts(locationId, builder.build()) ?: return null
return PostsFetchResponse(
body.items,
body.moreAvailable,
body.nextMaxId
)
}
suspend fun fetch(locationId: Long): Location? {
val place = repository.fetch(locationId) ?: return null
return place.location
}
companion object {
@Volatile
private var INSTANCE: LocationRepository? = null
fun getInstance(): LocationRepository {
return INSTANCE ?: synchronized(this) {
val service = RetrofitFactory.retrofit.create(LocationService::class.java)
LocationRepository(service).also { INSTANCE = it }
}
}
}
}

View File

@ -1,92 +0,0 @@
package awais.instagrabber.webservices;
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import awais.instagrabber.repositories.LocationRepository;
import awais.instagrabber.repositories.responses.Location;
import awais.instagrabber.repositories.responses.LocationFeedResponse;
import awais.instagrabber.repositories.responses.Place;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class LocationService {
private static final String TAG = "LocationService";
private final LocationRepository repository;
private static LocationService instance;
private LocationService() {
repository = RetrofitFactory.INSTANCE
.getRetrofit()
.create(LocationRepository.class);
}
public static LocationService getInstance() {
if (instance == null) {
instance = new LocationService();
}
return instance;
}
public void fetchPosts(final long locationId,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
final Call<LocationFeedResponse> request = repository.fetchPosts(locationId, builder.build());
request.enqueue(new Callback<LocationFeedResponse>() {
@Override
public void onResponse(@NonNull final Call<LocationFeedResponse> call, @NonNull final Response<LocationFeedResponse> response) {
if (callback == null) return;
final LocationFeedResponse body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
final PostsFetchResponse postsFetchResponse = new PostsFetchResponse(
body.getItems(),
body.getMoreAvailable(),
body.getNextMaxId()
);
callback.onSuccess(postsFetchResponse);
}
@Override
public void onFailure(@NonNull final Call<LocationFeedResponse> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void fetch(@NonNull final long locationId,
final ServiceCallback<Location> callback) {
final Call<Place> request = repository.fetch(locationId);
request.enqueue(new Callback<Place>() {
@Override
public void onResponse(@NonNull final Call<Place> call, @NonNull final Response<Place> response) {
if (callback == null) {
return;
}
callback.onSuccess(response.body() == null ? null : response.body().getLocation());
}
@Override
public void onFailure(@NonNull final Call<Place> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
}

View File

@ -1,138 +0,0 @@
package awais.instagrabber.webservices;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.repositories.TagsRepository;
import awais.instagrabber.repositories.responses.Hashtag;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.TagFeedResponse;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class TagsService {
private static final String TAG = "TagsService";
private static TagsService instance;
private final TagsRepository repository;
private TagsService() {
repository = RetrofitFactory.INSTANCE
.getRetrofit()
.create(TagsRepository.class);
}
public static TagsService getInstance() {
if (instance == null) {
instance = new TagsService();
}
return instance;
}
public void fetch(@NonNull final String tag,
final ServiceCallback<Hashtag> callback) {
final Call<Hashtag> request = repository.fetch(tag);
request.enqueue(new Callback<Hashtag>() {
@Override
public void onResponse(@NonNull final Call<Hashtag> call, @NonNull final Response<Hashtag> response) {
if (callback == null) {
return;
}
callback.onSuccess(response.body());
}
@Override
public void onFailure(@NonNull final Call<Hashtag> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void changeFollow(@NonNull final String action,
@NonNull final String tag,
@NonNull final String csrfToken,
@NonNull final long userId,
@NonNull final String deviceUuid,
final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>(3);
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", deviceUuid);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> request = repository.changeFollow(signedForm, action, tag);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
callback.onFailure(new RuntimeException("body is null"));
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
Log.e(TAG, "onResponse: ", e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
callback.onFailure(t);
}
});
}
public void fetchPosts(@NonNull final String tag,
final String maxId,
final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
}
final Call<TagFeedResponse> request = repository.fetchPosts(tag, builder.build());
request.enqueue(new Callback<TagFeedResponse>() {
@Override
public void onResponse(@NonNull final Call<TagFeedResponse> call, @NonNull final Response<TagFeedResponse> response) {
if (callback == null) {
return;
}
final TagFeedResponse body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
callback.onSuccess(new PostsFetchResponse(
body.getItems(),
body.getMoreAvailable(),
body.getNextMaxId()
));
}
@Override
public void onFailure(@NonNull final Call<TagFeedResponse> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
}