Browse Source
convert search-related backend stuff to kotlin
renovate/androidx.fragment-fragment-ktx-1.x
convert search-related backend stuff to kotlin
renovate/androidx.fragment-fragment-ktx-1.x
Austin Huang
3 years ago
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
4 changed files with 281 additions and 366 deletions
-
21app/src/main/java/awais/instagrabber/repositories/SearchService.kt
-
533app/src/main/java/awais/instagrabber/viewmodels/SearchFragmentViewModel.kt
-
38app/src/main/java/awais/instagrabber/webservices/SearchRepository.kt
-
43app/src/main/java/awais/instagrabber/webservices/SearchService.java
@ -1,14 +1,15 @@ |
|||||
package awais.instagrabber.repositories; |
|
||||
|
package awais.instagrabber.repositories |
||||
|
|
||||
import java.util.Map; |
|
||||
|
import awais.instagrabber.repositories.responses.search.SearchResponse |
||||
|
import retrofit2.Call |
||||
|
import retrofit2.http.GET |
||||
|
import retrofit2.http.QueryMap |
||||
|
import retrofit2.http.Url |
||||
|
|
||||
import awais.instagrabber.repositories.responses.search.SearchResponse; |
|
||||
import retrofit2.Call; |
|
||||
import retrofit2.http.GET; |
|
||||
import retrofit2.http.QueryMap; |
|
||||
import retrofit2.http.Url; |
|
||||
|
|
||||
public interface SearchRepository { |
|
||||
|
interface SearchService { |
||||
@GET |
@GET |
||||
Call<SearchResponse> search(@Url String url, @QueryMap(encoded = true) Map<String, String> queryParams); |
|
||||
|
suspend fun search( |
||||
|
@Url url: String?, |
||||
|
@QueryMap(encoded = true) queryParams: Map<String?, String?>? |
||||
|
): SearchResponse |
||||
} |
} |
@ -1,365 +1,284 @@ |
|||||
package awais.instagrabber.viewmodels; |
|
||||
|
package awais.instagrabber.viewmodels |
||||
|
|
||||
import android.app.Application; |
|
||||
import android.util.Log; |
|
||||
|
import android.app.Application |
||||
|
import android.util.Log |
||||
|
import androidx.lifecycle.LiveData |
||||
|
import androidx.lifecycle.MutableLiveData |
||||
|
import androidx.lifecycle.Transformations |
||||
|
import androidx.lifecycle.viewModelScope |
||||
|
import awais.instagrabber.db.datasources.RecentSearchDataSource |
||||
|
import awais.instagrabber.db.entities.Favorite |
||||
|
import awais.instagrabber.db.entities.RecentSearch |
||||
|
import awais.instagrabber.db.entities.RecentSearch.Companion.fromSearchItem |
||||
|
import awais.instagrabber.db.repositories.FavoriteRepository |
||||
|
import awais.instagrabber.db.repositories.RecentSearchRepository |
||||
|
import awais.instagrabber.models.Resource |
||||
|
import awais.instagrabber.models.Resource.Companion.error |
||||
|
import awais.instagrabber.models.Resource.Companion.loading |
||||
|
import awais.instagrabber.models.Resource.Companion.success |
||||
|
import awais.instagrabber.models.enums.FavoriteType |
||||
|
import awais.instagrabber.repositories.responses.search.SearchItem |
||||
|
import awais.instagrabber.repositories.responses.search.SearchResponse |
||||
|
import awais.instagrabber.utils.* |
||||
|
import awais.instagrabber.utils.AppExecutors.mainThread |
||||
|
import awais.instagrabber.utils.TextUtils.isEmpty |
||||
|
import awais.instagrabber.webservices.SearchRepository |
||||
|
import com.google.common.collect.ImmutableList |
||||
|
import com.google.common.util.concurrent.FutureCallback |
||||
|
import com.google.common.util.concurrent.Futures |
||||
|
import com.google.common.util.concurrent.SettableFuture |
||||
|
import kotlinx.coroutines.Dispatchers |
||||
|
import kotlinx.coroutines.Dispatchers.IO |
||||
|
import kotlinx.coroutines.launch |
||||
|
import retrofit2.Call |
||||
|
import retrofit2.Callback |
||||
|
import retrofit2.Response |
||||
|
import java.util.* |
||||
|
import java.util.function.BiConsumer |
||||
|
import java.util.stream.Collectors |
||||
|
|
||||
import androidx.annotation.NonNull; |
|
||||
import androidx.annotation.Nullable; |
|
||||
import androidx.lifecycle.LiveData; |
|
||||
import androidx.lifecycle.MutableLiveData; |
|
||||
|
|
||||
import com.google.common.collect.ImmutableList; |
|
||||
import com.google.common.util.concurrent.FutureCallback; |
|
||||
import com.google.common.util.concurrent.Futures; |
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import com.google.common.util.concurrent.SettableFuture; |
|
||||
|
|
||||
import java.util.Collections; |
|
||||
import java.util.List; |
|
||||
import java.util.Objects; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
import awais.instagrabber.db.datasources.RecentSearchDataSource; |
|
||||
import awais.instagrabber.db.entities.Favorite; |
|
||||
import awais.instagrabber.db.entities.RecentSearch; |
|
||||
import awais.instagrabber.db.repositories.FavoriteRepository; |
|
||||
import awais.instagrabber.db.repositories.RecentSearchRepository; |
|
||||
import awais.instagrabber.models.Resource; |
|
||||
import awais.instagrabber.models.enums.FavoriteType; |
|
||||
import awais.instagrabber.repositories.responses.search.SearchItem; |
|
||||
import awais.instagrabber.repositories.responses.search.SearchResponse; |
|
||||
import awais.instagrabber.utils.AppExecutors; |
|
||||
import awais.instagrabber.utils.Constants; |
|
||||
import awais.instagrabber.utils.CookieUtils; |
|
||||
import awais.instagrabber.utils.CoroutineUtilsKt; |
|
||||
import awais.instagrabber.utils.Debouncer; |
|
||||
import awais.instagrabber.utils.TextUtils; |
|
||||
import awais.instagrabber.webservices.SearchService; |
|
||||
import kotlinx.coroutines.Dispatchers; |
|
||||
import retrofit2.Call; |
|
||||
import retrofit2.Callback; |
|
||||
import retrofit2.Response; |
|
||||
|
|
||||
import static androidx.lifecycle.Transformations.distinctUntilChanged; |
|
||||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|
||||
|
|
||||
public class SearchFragmentViewModel extends AppStateViewModel { |
|
||||
private static final String TAG = SearchFragmentViewModel.class.getSimpleName(); |
|
||||
private static final String QUERY = "query"; |
|
||||
|
|
||||
private final MutableLiveData<String> query = new MutableLiveData<>(); |
|
||||
private final MutableLiveData<Resource<List<SearchItem>>> topResults = new MutableLiveData<>(); |
|
||||
private final MutableLiveData<Resource<List<SearchItem>>> userResults = new MutableLiveData<>(); |
|
||||
private final MutableLiveData<Resource<List<SearchItem>>> hashtagResults = new MutableLiveData<>(); |
|
||||
private final MutableLiveData<Resource<List<SearchItem>>> locationResults = new MutableLiveData<>(); |
|
||||
|
|
||||
private final SearchService searchService; |
|
||||
private final Debouncer<String> searchDebouncer; |
|
||||
private final boolean isLoggedIn; |
|
||||
private final LiveData<String> distinctQuery; |
|
||||
private final RecentSearchRepository recentSearchRepository; |
|
||||
private final FavoriteRepository favoriteRepository; |
|
||||
|
|
||||
private String tempQuery; |
|
||||
|
|
||||
public SearchFragmentViewModel(@NonNull final Application application) { |
|
||||
super(application); |
|
||||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|
||||
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0; |
|
||||
final Debouncer.Callback<String> searchCallback = new Debouncer.Callback<String>() { |
|
||||
@Override |
|
||||
public void call(final String key) { |
|
||||
if (tempQuery == null) return; |
|
||||
query.postValue(tempQuery); |
|
||||
|
class SearchFragmentViewModel(application: Application) : AppStateViewModel(application) { |
||||
|
private val query = MutableLiveData<String>() |
||||
|
private val topResults = MutableLiveData<Resource<List<SearchItem>?>>() |
||||
|
private val userResults = MutableLiveData<Resource<List<SearchItem>?>>() |
||||
|
private val hashtagResults = MutableLiveData<Resource<List<SearchItem>?>>() |
||||
|
private val locationResults = MutableLiveData<Resource<List<SearchItem>?>>() |
||||
|
private val searchRepository: SearchRepository by lazy { SearchRepository.getInstance() } |
||||
|
private val searchCallback: Debouncer.Callback<String> = object : Debouncer.Callback<String> { |
||||
|
override fun call(key: String) { |
||||
|
if (tempQuery == null) return |
||||
|
query.postValue(tempQuery) |
||||
} |
} |
||||
|
|
||||
@Override |
|
||||
public void onError(final Throwable t) { |
|
||||
Log.e(TAG, "onError: ", t); |
|
||||
|
override fun onError(t: Throwable) { |
||||
|
Log.e(TAG, "onError: ", t) |
||||
} |
} |
||||
}; |
|
||||
searchDebouncer = new Debouncer<>(searchCallback, 500); |
|
||||
distinctQuery = distinctUntilChanged(query); |
|
||||
searchService = SearchService.getInstance(); |
|
||||
recentSearchRepository = RecentSearchRepository.getInstance(RecentSearchDataSource.getInstance(application)); |
|
||||
favoriteRepository = FavoriteRepository.Companion.getInstance(application); |
|
||||
} |
} |
||||
|
|
||||
public LiveData<String> getQuery() { |
|
||||
return distinctQuery; |
|
||||
|
private val searchDebouncer = Debouncer(searchCallback, 500) |
||||
|
private val cookie = Utils.settingsHelper.getString(Constants.COOKIE) |
||||
|
private val isLoggedIn = !isEmpty(cookie) && getUserIdFromCookie(cookie) != 0L |
||||
|
private val distinctQuery = Transformations.distinctUntilChanged(query) |
||||
|
private val recentSearchRepository: RecentSearchRepository by lazy { |
||||
|
RecentSearchRepository.getInstance(RecentSearchDataSource.getInstance(application)) |
||||
|
} |
||||
|
private val favoriteRepository: FavoriteRepository by lazy { FavoriteRepository.getInstance(application) } |
||||
|
private var tempQuery: String? = null |
||||
|
fun getQuery(): LiveData<String> { |
||||
|
return distinctQuery |
||||
} |
} |
||||
|
|
||||
public LiveData<Resource<List<SearchItem>>> getTopResults() { |
|
||||
return topResults; |
|
||||
|
fun getTopResults(): LiveData<Resource<List<SearchItem>?>> { |
||||
|
return topResults |
||||
} |
} |
||||
|
|
||||
public LiveData<Resource<List<SearchItem>>> getUserResults() { |
|
||||
return userResults; |
|
||||
|
fun getUserResults(): LiveData<Resource<List<SearchItem>?>> { |
||||
|
return userResults |
||||
} |
} |
||||
|
|
||||
public LiveData<Resource<List<SearchItem>>> getHashtagResults() { |
|
||||
return hashtagResults; |
|
||||
|
fun getHashtagResults(): LiveData<Resource<List<SearchItem>?>> { |
||||
|
return hashtagResults |
||||
} |
} |
||||
|
|
||||
public LiveData<Resource<List<SearchItem>>> getLocationResults() { |
|
||||
return locationResults; |
|
||||
|
fun getLocationResults(): LiveData<Resource<List<SearchItem>?>> { |
||||
|
return locationResults |
||||
} |
} |
||||
|
|
||||
public void submitQuery(@Nullable final String query) { |
|
||||
String localQuery = query; |
|
||||
|
fun submitQuery(query: String?) { |
||||
|
var localQuery = query |
||||
if (query == null) { |
if (query == null) { |
||||
localQuery = ""; |
|
||||
|
localQuery = "" |
||||
} |
} |
||||
if (tempQuery != null && Objects.equals(localQuery.toLowerCase(), tempQuery.toLowerCase())) return; |
|
||||
tempQuery = query; |
|
||||
if (TextUtils.isEmpty(query)) { |
|
||||
|
if (tempQuery != null && localQuery!!.lowercase(Locale.getDefault()) == tempQuery!!.lowercase(Locale.getDefault())) return |
||||
|
tempQuery = query |
||||
|
if (isEmpty(query)) { |
||||
// If empty immediately post it |
// If empty immediately post it |
||||
searchDebouncer.cancel(QUERY); |
|
||||
this.query.postValue(""); |
|
||||
return; |
|
||||
|
searchDebouncer.cancel(QUERY) |
||||
|
this.query.postValue("") |
||||
|
return |
||||
} |
} |
||||
searchDebouncer.call(QUERY); |
|
||||
|
searchDebouncer.call(QUERY) |
||||
} |
} |
||||
|
|
||||
public void search(@NonNull final String query, |
|
||||
@NonNull final FavoriteType type) { |
|
||||
final MutableLiveData<Resource<List<SearchItem>>> liveData = getLiveDataByType(type); |
|
||||
if (liveData == null) return; |
|
||||
if (TextUtils.isEmpty(query)) { |
|
||||
showRecentSearchesAndFavorites(type, liveData); |
|
||||
return; |
|
||||
} |
|
||||
if (query.equals("@") || query.equals("#")) return; |
|
||||
final String c; |
|
||||
switch (type) { |
|
||||
case TOP: |
|
||||
c = "blended"; |
|
||||
break; |
|
||||
case USER: |
|
||||
c = "user"; |
|
||||
break; |
|
||||
case HASHTAG: |
|
||||
c = "hashtag"; |
|
||||
break; |
|
||||
case LOCATION: |
|
||||
c = "place"; |
|
||||
break; |
|
||||
default: |
|
||||
return; |
|
||||
} |
|
||||
liveData.postValue(Resource.loading(null)); |
|
||||
final Call<SearchResponse> request = searchService.search(isLoggedIn, query, c); |
|
||||
request.enqueue(new Callback<SearchResponse>() { |
|
||||
@Override |
|
||||
public void onResponse(@NonNull final Call<SearchResponse> call, |
|
||||
@NonNull final Response<SearchResponse> response) { |
|
||||
if (!response.isSuccessful()) { |
|
||||
sendErrorResponse(type); |
|
||||
return; |
|
||||
} |
|
||||
final SearchResponse body = response.body(); |
|
||||
if (body == null) { |
|
||||
sendErrorResponse(type); |
|
||||
return; |
|
||||
} |
|
||||
parseResponse(body, type); |
|
||||
|
fun search( |
||||
|
query: String, |
||||
|
type: FavoriteType |
||||
|
) { |
||||
|
val liveData = getLiveDataByType(type) ?: return |
||||
|
if (isEmpty(query)) { |
||||
|
showRecentSearchesAndFavorites(type, liveData) |
||||
|
return |
||||
|
} |
||||
|
if (query == "@" || query == "#") return |
||||
|
val c: String |
||||
|
c = when (type) { |
||||
|
FavoriteType.TOP -> "blended" |
||||
|
FavoriteType.USER -> "user" |
||||
|
FavoriteType.HASHTAG -> "hashtag" |
||||
|
FavoriteType.LOCATION -> "place" |
||||
|
else -> return |
||||
|
} |
||||
|
liveData.postValue(loading<List<SearchItem>?>(null)) |
||||
|
viewModelScope.launch(Dispatchers.IO) { |
||||
|
try { |
||||
|
val response = searchRepository.search(isLoggedIn, query, c) |
||||
|
parseResponse(response, type) |
||||
|
} |
||||
|
catch (e: Exception) { |
||||
|
sendErrorResponse(type) |
||||
} |
} |
||||
|
|
||||
@Override |
|
||||
public void onFailure(@NonNull final Call<SearchResponse> call, |
|
||||
@NonNull final Throwable t) { |
|
||||
Log.e(TAG, "onFailure: ", t); |
|
||||
} |
} |
||||
}); |
|
||||
} |
} |
||||
|
|
||||
private void showRecentSearchesAndFavorites(@NonNull final FavoriteType type, |
|
||||
@NonNull final MutableLiveData<Resource<List<SearchItem>>> liveData) { |
|
||||
final SettableFuture<List<RecentSearch>> recentResultsFuture = SettableFuture.create(); |
|
||||
final SettableFuture<List<Favorite>> favoritesFuture = SettableFuture.create(); |
|
||||
recentSearchRepository.getAllRecentSearches( |
|
||||
CoroutineUtilsKt.getContinuation((recentSearches, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { |
|
||||
if (throwable != null) { |
|
||||
Log.e(TAG, "showRecentSearchesAndFavorites: ", throwable); |
|
||||
recentResultsFuture.set(Collections.emptyList()); |
|
||||
return; |
|
||||
} |
|
||||
if (type != FavoriteType.TOP) { |
|
||||
recentResultsFuture.set((List<RecentSearch>) recentSearches |
|
||||
.stream() |
|
||||
.filter(rs -> rs.getType() == type) |
|
||||
|
private fun showRecentSearchesAndFavorites( |
||||
|
type: FavoriteType, |
||||
|
liveData: MutableLiveData<Resource<List<SearchItem>?>> |
||||
|
) { |
||||
|
val recentResultsFuture = SettableFuture.create<List<RecentSearch>>() |
||||
|
val favoritesFuture = SettableFuture.create<List<Favorite>>() |
||||
|
viewModelScope.launch(Dispatchers.IO) { |
||||
|
try { |
||||
|
val recentSearches = recentSearchRepository.getAllRecentSearches() |
||||
|
recentResultsFuture.set( |
||||
|
if (type == FavoriteType.TOP) recentSearches |
||||
|
else recentSearches.stream() |
||||
|
.filter { (_, _, _, _, _, type1) -> type1 === type } |
||||
.collect(Collectors.toList()) |
.collect(Collectors.toList()) |
||||
); |
|
||||
return; |
|
||||
} |
|
||||
//noinspection unchecked |
|
||||
recentResultsFuture.set((List<RecentSearch>) recentSearches); |
|
||||
}), Dispatchers.getIO()) |
|
||||
); |
|
||||
favoriteRepository.getAllFavorites( |
|
||||
CoroutineUtilsKt.getContinuation((favorites, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { |
|
||||
if (throwable != null) { |
|
||||
favoritesFuture.set(Collections.emptyList()); |
|
||||
Log.e(TAG, "showRecentSearchesAndFavorites: ", throwable); |
|
||||
return; |
|
||||
} |
|
||||
if (type != FavoriteType.TOP) { |
|
||||
favoritesFuture.set((List<Favorite>) favorites |
|
||||
|
) |
||||
|
} |
||||
|
catch (e: Exception) { |
||||
|
recentResultsFuture.set(emptyList()) |
||||
|
} |
||||
|
try { |
||||
|
val favorites = favoriteRepository.getAllFavorites() |
||||
|
favoritesFuture.set( |
||||
|
if (type == FavoriteType.TOP) favorites |
||||
|
else favorites |
||||
.stream() |
.stream() |
||||
.filter(f -> f.getType() == type) |
|
||||
|
.filter { (_, _, type1) -> type1 === type } |
||||
.collect(Collectors.toList()) |
.collect(Collectors.toList()) |
||||
); |
|
||||
return; |
|
||||
} |
|
||||
//noinspection unchecked |
|
||||
favoritesFuture.set((List<Favorite>) favorites); |
|
||||
}), Dispatchers.getIO()) |
|
||||
); |
|
||||
//noinspection UnstableApiUsage |
|
||||
final ListenableFuture<List<List<?>>> listenableFuture = Futures.allAsList(recentResultsFuture, favoritesFuture); |
|
||||
Futures.addCallback(listenableFuture, new FutureCallback<List<List<?>>>() { |
|
||||
@Override |
|
||||
public void onSuccess(@Nullable final List<List<?>> result) { |
|
||||
if (!TextUtils.isEmpty(tempQuery)) return; // Make sure user has not entered anything before updating results |
|
||||
|
) |
||||
|
} |
||||
|
catch (e: Exception) { |
||||
|
favoritesFuture.set(emptyList()) |
||||
|
} |
||||
|
} |
||||
|
val listenableFuture = Futures.allAsList<List<*>>(recentResultsFuture, favoritesFuture) |
||||
|
Futures.addCallback(listenableFuture, object : FutureCallback<List<List<*>?>?> { |
||||
|
override fun onSuccess(result: List<List<*>?>?) { |
||||
|
if (!isEmpty(tempQuery)) return // Make sure user has not entered anything before updating results |
||||
if (result == null) { |
if (result == null) { |
||||
liveData.postValue(Resource.success(Collections.emptyList())); |
|
||||
return; |
|
||||
|
liveData.postValue(success(emptyList())) |
||||
|
return |
||||
} |
} |
||||
try { |
try { |
||||
//noinspection unchecked |
|
||||
liveData.postValue(Resource.success( |
|
||||
ImmutableList.<SearchItem>builder() |
|
||||
.addAll(SearchItem.fromRecentSearch((List<RecentSearch>) result.get(0))) |
|
||||
.addAll(SearchItem.fromFavorite((List<Favorite>) result.get(1))) |
|
||||
|
liveData.postValue( |
||||
|
success( |
||||
|
ImmutableList.builder<SearchItem>() |
||||
|
.addAll(SearchItem.fromRecentSearch(result[0] as List<RecentSearch?>?)) |
||||
|
.addAll(SearchItem.fromFavorite(result[1] as List<Favorite?>?)) |
||||
.build() |
.build() |
||||
)); |
|
||||
} catch (Exception e) { |
|
||||
Log.e(TAG, "onSuccess: ", e); |
|
||||
liveData.postValue(Resource.success(Collections.emptyList())); |
|
||||
|
) |
||||
|
) |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "onSuccess: ", e) |
||||
|
liveData.postValue(success(emptyList())) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
@Override |
|
||||
public void onFailure(@NonNull final Throwable t) { |
|
||||
if (!TextUtils.isEmpty(tempQuery)) return; |
|
||||
liveData.postValue(Resource.success(Collections.emptyList())); |
|
||||
Log.e(TAG, "onFailure: ", t); |
|
||||
|
override fun onFailure(t: Throwable) { |
||||
|
if (!isEmpty(tempQuery)) return |
||||
|
liveData.postValue(success(emptyList())) |
||||
|
Log.e(TAG, "onFailure: ", t) |
||||
} |
} |
||||
}, AppExecutors.INSTANCE.getMainThread()); |
|
||||
|
}, mainThread) |
||||
} |
} |
||||
|
|
||||
private void sendErrorResponse(@NonNull final FavoriteType type) { |
|
||||
final MutableLiveData<Resource<List<SearchItem>>> liveData = getLiveDataByType(type); |
|
||||
if (liveData == null) return; |
|
||||
liveData.postValue(Resource.error(null, Collections.emptyList())); |
|
||||
|
private fun sendErrorResponse(type: FavoriteType) { |
||||
|
val liveData = getLiveDataByType(type) ?: return |
||||
|
liveData.postValue(error(null, emptyList())) |
||||
} |
} |
||||
|
|
||||
private MutableLiveData<Resource<List<SearchItem>>> getLiveDataByType(@NonNull final FavoriteType type) { |
|
||||
final MutableLiveData<Resource<List<SearchItem>>> liveData; |
|
||||
switch (type) { |
|
||||
case TOP: |
|
||||
liveData = topResults; |
|
||||
break; |
|
||||
case USER: |
|
||||
liveData = userResults; |
|
||||
break; |
|
||||
case HASHTAG: |
|
||||
liveData = hashtagResults; |
|
||||
break; |
|
||||
case LOCATION: |
|
||||
liveData = locationResults; |
|
||||
break; |
|
||||
default: |
|
||||
return null; |
|
||||
} |
|
||||
return liveData; |
|
||||
|
private fun getLiveDataByType(type: FavoriteType): MutableLiveData<Resource<List<SearchItem>?>>? { |
||||
|
val liveData: MutableLiveData<Resource<List<SearchItem>?>> |
||||
|
liveData = when (type) { |
||||
|
FavoriteType.TOP -> topResults |
||||
|
FavoriteType.USER -> userResults |
||||
|
FavoriteType.HASHTAG -> hashtagResults |
||||
|
FavoriteType.LOCATION -> locationResults |
||||
|
else -> return null |
||||
|
} |
||||
|
return liveData |
||||
} |
} |
||||
|
|
||||
private void parseResponse(@NonNull final SearchResponse body, |
|
||||
@NonNull final FavoriteType type) { |
|
||||
final MutableLiveData<Resource<List<SearchItem>>> liveData = getLiveDataByType(type); |
|
||||
if (liveData == null) return; |
|
||||
|
private fun parseResponse( |
||||
|
body: SearchResponse, |
||||
|
type: FavoriteType |
||||
|
) { |
||||
|
val liveData = getLiveDataByType(type) ?: return |
||||
if (isLoggedIn) { |
if (isLoggedIn) { |
||||
if (body.getList() == null) { |
|
||||
liveData.postValue(Resource.success(Collections.emptyList())); |
|
||||
return; |
|
||||
|
if (body.list == null) { |
||||
|
liveData.postValue(success(emptyList())) |
||||
|
return |
||||
} |
} |
||||
if (type == FavoriteType.HASHTAG || type == FavoriteType.LOCATION) { |
|
||||
liveData.postValue(Resource.success(body.getList() |
|
||||
|
if (type === FavoriteType.HASHTAG || type === FavoriteType.LOCATION) { |
||||
|
liveData.postValue(success(body.list |
||||
.stream() |
.stream() |
||||
.filter(i -> i.getUser() == null) |
|
||||
.collect(Collectors.toList()))); |
|
||||
return; |
|
||||
|
.filter { i: SearchItem -> i.user == null } |
||||
|
.collect(Collectors.toList()))) |
||||
|
return |
||||
} |
} |
||||
liveData.postValue(Resource.success(body.getList())); |
|
||||
return; |
|
||||
|
liveData.postValue(success(body.list)) |
||||
|
return |
||||
} |
} |
||||
|
|
||||
// anonymous |
// anonymous |
||||
final List<SearchItem> list; |
|
||||
switch (type) { |
|
||||
case TOP: |
|
||||
list = ImmutableList |
|
||||
.<SearchItem>builder() |
|
||||
.addAll(body.getUsers() == null ? Collections.emptyList() : body.getUsers()) |
|
||||
.addAll(body.getHashtags() == null ? Collections.emptyList() : body.getHashtags()) |
|
||||
.addAll(body.getPlaces() == null ? Collections.emptyList() : body.getPlaces()) |
|
||||
.build(); |
|
||||
break; |
|
||||
case USER: |
|
||||
list = body.getUsers(); |
|
||||
break; |
|
||||
case HASHTAG: |
|
||||
list = body.getHashtags(); |
|
||||
break; |
|
||||
case LOCATION: |
|
||||
list = body.getPlaces(); |
|
||||
break; |
|
||||
default: |
|
||||
return; |
|
||||
} |
|
||||
liveData.postValue(Resource.success(list)); |
|
||||
|
val list: List<SearchItem>? |
||||
|
list = when (type) { |
||||
|
FavoriteType.TOP -> ImmutableList |
||||
|
.builder<SearchItem>() |
||||
|
.addAll(body.users ?: emptyList()) |
||||
|
.addAll(body.hashtags ?: emptyList()) |
||||
|
.addAll(body.places ?: emptyList()) |
||||
|
.build() |
||||
|
FavoriteType.USER -> body.users |
||||
|
FavoriteType.HASHTAG -> body.hashtags |
||||
|
FavoriteType.LOCATION -> body.places |
||||
|
else -> return |
||||
|
} |
||||
|
liveData.postValue(success(list)) |
||||
|
} |
||||
|
|
||||
|
fun saveToRecentSearches(searchItem: SearchItem?) { |
||||
|
if (searchItem == null) return |
||||
|
viewModelScope.launch(Dispatchers.IO) { |
||||
|
try { |
||||
|
val recentSearch = fromSearchItem(searchItem) |
||||
|
recentSearchRepository.insertOrUpdateRecentSearch(recentSearch!!) |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "saveToRecentSearches: ", e) |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
public void saveToRecentSearches(final SearchItem searchItem) { |
|
||||
if (searchItem == null) return; |
|
||||
|
fun deleteRecentSearch(searchItem: SearchItem?): LiveData<Resource<Any?>>? { |
||||
|
if (searchItem == null || !searchItem.isRecent) return null |
||||
|
val (_, igId, _, _, _, type) = fromSearchItem(searchItem) ?: return null |
||||
|
val data = MutableLiveData<Resource<Any?>>() |
||||
|
data.postValue(loading(null)) |
||||
|
viewModelScope.launch(Dispatchers.IO) { |
||||
try { |
try { |
||||
final RecentSearch recentSearch = RecentSearch.fromSearchItem(searchItem); |
|
||||
if (recentSearch == null) return; |
|
||||
recentSearchRepository.insertOrUpdateRecentSearch( |
|
||||
recentSearch, |
|
||||
CoroutineUtilsKt.getContinuation((unit, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { |
|
||||
if (throwable != null) { |
|
||||
Log.e(TAG, "saveToRecentSearches: ", throwable); |
|
||||
// return; |
|
||||
} |
|
||||
// Log.d(TAG, "onSuccess: inserted recent: " + recentSearch); |
|
||||
}), Dispatchers.getIO()) |
|
||||
); |
|
||||
} catch (Exception e) { |
|
||||
Log.e(TAG, "saveToRecentSearches: ", e); |
|
||||
|
recentSearchRepository.deleteRecentSearchByIgIdAndType(igId, type) |
||||
|
data.postValue(success(Any())) |
||||
|
} |
||||
|
catch (e: Exception) { |
||||
|
data.postValue(error(e.message, null)) |
||||
|
} |
||||
} |
} |
||||
|
return data |
||||
} |
} |
||||
|
|
||||
@Nullable |
|
||||
public LiveData<Resource<Object>> deleteRecentSearch(final SearchItem searchItem) { |
|
||||
if (searchItem == null || !searchItem.isRecent()) return null; |
|
||||
final RecentSearch recentSearch = RecentSearch.fromSearchItem(searchItem); |
|
||||
if (recentSearch == null) return null; |
|
||||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|
||||
data.postValue(Resource.loading(null)); |
|
||||
recentSearchRepository.deleteRecentSearchByIgIdAndType( |
|
||||
recentSearch.getIgId(), |
|
||||
recentSearch.getType(), |
|
||||
CoroutineUtilsKt.getContinuation((unit, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { |
|
||||
if (throwable != null) { |
|
||||
Log.e(TAG, "deleteRecentSearch: ", throwable); |
|
||||
data.postValue(Resource.error("Error deleting recent item", null)); |
|
||||
return; |
|
||||
} |
|
||||
data.postValue(Resource.success(new Object())); |
|
||||
}), Dispatchers.getIO()) |
|
||||
); |
|
||||
return data; |
|
||||
|
companion object { |
||||
|
private val TAG = SearchFragmentViewModel::class.java.simpleName |
||||
|
private const val QUERY = "query" |
||||
} |
} |
||||
} |
} |
@ -0,0 +1,38 @@ |
|||||
|
package awais.instagrabber.webservices |
||||
|
|
||||
|
import awais.instagrabber.repositories.SearchService |
||||
|
import awais.instagrabber.repositories.responses.search.SearchResponse |
||||
|
import awais.instagrabber.webservices.RetrofitFactory.retrofitWeb |
||||
|
import com.google.common.collect.ImmutableMap |
||||
|
import retrofit2.Call |
||||
|
|
||||
|
class SearchRepository(private val service: SearchService) { |
||||
|
suspend fun search( |
||||
|
isLoggedIn: Boolean, |
||||
|
query: String, |
||||
|
context: String |
||||
|
): SearchResponse { |
||||
|
val builder = ImmutableMap.builder<String, String>() |
||||
|
builder.put("query", query) |
||||
|
// context is one of: "blended", "user", "place", "hashtag" |
||||
|
// note that "place" and "hashtag" can contain ONE user result, who knows why |
||||
|
builder.put("context", context) |
||||
|
builder.put("count", "50") |
||||
|
return service.search( |
||||
|
if (isLoggedIn) "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/" else "https://www.instagram.com/web/search/topsearch/", |
||||
|
builder.build() |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
companion object { |
||||
|
@Volatile |
||||
|
private var INSTANCE: SearchRepository? = null |
||||
|
|
||||
|
fun getInstance(): SearchRepository { |
||||
|
return INSTANCE ?: synchronized(this) { |
||||
|
val service: SearchService = RetrofitFactory.retrofit.create(SearchService::class.java) |
||||
|
SearchRepository(service).also { INSTANCE = it } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,43 +0,0 @@ |
|||||
package awais.instagrabber.webservices; |
|
||||
|
|
||||
import com.google.common.collect.ImmutableMap; |
|
||||
|
|
||||
import awais.instagrabber.repositories.SearchRepository; |
|
||||
import awais.instagrabber.repositories.responses.search.SearchResponse; |
|
||||
import retrofit2.Call; |
|
||||
|
|
||||
public class SearchService { |
|
||||
private static final String TAG = "LocationService"; |
|
||||
|
|
||||
private final SearchRepository repository; |
|
||||
|
|
||||
private static SearchService instance; |
|
||||
|
|
||||
private SearchService() { |
|
||||
repository = RetrofitFactory.INSTANCE |
|
||||
.getRetrofitWeb() |
|
||||
.create(SearchRepository.class); |
|
||||
} |
|
||||
|
|
||||
public static SearchService getInstance() { |
|
||||
if (instance == null) { |
|
||||
instance = new SearchService(); |
|
||||
} |
|
||||
return instance; |
|
||||
} |
|
||||
|
|
||||
public Call<SearchResponse> search(final boolean isLoggedIn, |
|
||||
final String query, |
|
||||
final String context) { |
|
||||
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
|
||||
builder.put("query", query); |
|
||||
// context is one of: "blended", "user", "place", "hashtag" |
|
||||
// note that "place" and "hashtag" can contain ONE user result, who knows why |
|
||||
builder.put("context", context); |
|
||||
builder.put("count", "50"); |
|
||||
return repository.search(isLoggedIn |
|
||||
? "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/" |
|
||||
: "https://www.instagram.com/web/search/topsearch/", |
|
||||
builder.build()); |
|
||||
} |
|
||||
} |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue