diff options
author | Alex Ning <chineseperson5@gmail.com> | 2019-02-23 02:12:26 +0000 |
---|---|---|
committer | Alex Ning <chineseperson5@gmail.com> | 2019-02-23 02:12:26 +0000 |
commit | 3fa6969b750cb0effa7b134e8b7a198f36692637 (patch) | |
tree | 63e7692eb46180c1d5b74f63ad29bf4852f23885 /app | |
parent | ce4773230ab31e84b10b53793331ec6575ce553d (diff) | |
download | infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.tar infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.tar.gz infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.tar.bz2 infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.tar.lz infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.tar.xz infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.tar.zst infinity-for-reddit-3fa6969b750cb0effa7b134e8b7a198f36692637.zip |
Add a feature: Search for users. Fixed calling callback.onResult twice when loading user's posts.
Diffstat (limited to 'app')
22 files changed, 1026 insertions, 116 deletions
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CheckIsSubscribedToSubredditAsyncTask.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CheckIsSubscribedToSubredditAsyncTask.java new file mode 100644 index 00000000..d1cf3cf1 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CheckIsSubscribedToSubredditAsyncTask.java @@ -0,0 +1,42 @@ +package ml.docilealligator.infinityforreddit; + +import android.os.AsyncTask; + +import SubscribedSubredditDatabase.SubscribedSubredditDao; +import SubscribedSubredditDatabase.SubscribedSubredditData; + +class CheckIsSubscribedToSubredditAsyncTask extends AsyncTask<Void, Void, Void> { + + private SubscribedSubredditDao subscribedSubredditDao; + private String subredditName; + private SubscribedSubredditData subscribedSubredditData; + private CheckIsSubscribedToSubredditListener checkIsSubscribedToSubredditListener; + + interface CheckIsSubscribedToSubredditListener { + void isSubscribed(); + void isNotSubscribed(); + } + + CheckIsSubscribedToSubredditAsyncTask(SubscribedSubredditDao subscribedSubredditDao, String subredditName, + CheckIsSubscribedToSubredditListener checkIsSubscribedToSubredditListener) { + this.subscribedSubredditDao = subscribedSubredditDao; + this.subredditName =subredditName; + this.checkIsSubscribedToSubredditListener = checkIsSubscribedToSubredditListener; + } + + @Override + protected Void doInBackground(Void... voids) { + subscribedSubredditData = subscribedSubredditDao.getSubscribedSubreddit(subredditName); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + if(subscribedSubredditData != null) { + checkIsSubscribedToSubredditListener.isSubscribed(); + } else { + checkIsSubscribedToSubredditListener.isNotSubscribed(); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubredditData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubredditData.java index f3f1536b..b7c5a73e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubredditData.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubredditData.java @@ -1,8 +1,11 @@ package ml.docilealligator.infinityforreddit; -import androidx.annotation.NonNull; import android.util.Log; +import java.util.ArrayList; + +import SubredditDatabase.SubredditData; +import androidx.annotation.NonNull; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -10,10 +13,15 @@ import retrofit2.Retrofit; class FetchSubredditData { interface FetchSubredditDataListener { - void onFetchSubredditDataSuccess(String response); + void onFetchSubredditDataSuccess(SubredditData subredditData, int nCurrentOnlineSubscribers); void onFetchSubredditDataFail(); } + interface FetchSubredditListingDataListener { + void onFetchSubredditListingDataSuccess(ArrayList<SubredditData> subredditData, String after); + void onFetchSubredditListingDataFail(); + } + static void fetchSubredditData(Retrofit retrofit, String subredditName, final FetchSubredditDataListener fetchSubredditDataListener) { RedditAPI api = retrofit.create(RedditAPI.class); @@ -22,7 +30,18 @@ class FetchSubredditData { @Override public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) { if(response.isSuccessful()) { - fetchSubredditDataListener.onFetchSubredditDataSuccess(response.body()); + ParseSubredditData.parseSubredditData(response.body(), new ParseSubredditData.ParseSubredditDataListener() { + @Override + public void onParseSubredditDataSuccess(SubredditData subredditData, int nCurrentOnlineSubscribers) { + fetchSubredditDataListener.onFetchSubredditDataSuccess(subredditData, nCurrentOnlineSubscribers); + } + + @Override + public void onParseSubredditDataFail() { + Log.i("parse failed", "true"); + fetchSubredditDataListener.onFetchSubredditDataFail(); + } + }); } else { Log.i("call failed", response.message()); fetchSubredditDataListener.onFetchSubredditDataFail(); @@ -36,4 +55,38 @@ class FetchSubredditData { } }); } + + static void fetchSubredditListingData(Retrofit retrofit, String query, String after, final FetchSubredditListingDataListener fetchSubredditListingDataListener) { + RedditAPI api = retrofit.create(RedditAPI.class); + + Call<String> subredditDataCall = api.searchSubreddits(query, after); + subredditDataCall.enqueue(new Callback<String>() { + @Override + public void onResponse(@NonNull Call<String> call, @NonNull Response<String> response) { + if(response.isSuccessful()) { + ParseSubredditData.parseSubredditListingData(response.body(), new ParseSubredditData.ParseSubredditListingDataListener() { + @Override + public void onParseSubredditListingDataSuccess(ArrayList<SubredditData> subredditData, String after) { + fetchSubredditListingDataListener.onFetchSubredditListingDataSuccess(subredditData, after); + } + + @Override + public void onParseSubredditListingDataFail() { + Log.i("parse failed", "true"); + fetchSubredditListingDataListener.onFetchSubredditListingDataFail(); + } + }); + } else { + Log.i("call failed", response.message()); + fetchSubredditListingDataListener.onFetchSubredditListingDataFail(); + } + } + + @Override + public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) { + Log.i("call failed", t.getMessage()); + fetchSubredditListingDataListener.onFetchSubredditListingDataFail(); + } + }); + } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/InsertSubscribedThingsAsyncTask.java b/app/src/main/java/ml/docilealligator/infinityforreddit/InsertSubscribedThingsAsyncTask.java new file mode 100644 index 00000000..c5b517c2 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/InsertSubscribedThingsAsyncTask.java @@ -0,0 +1,72 @@ +package ml.docilealligator.infinityforreddit; + +import android.os.AsyncTask; + +import java.util.List; + +import SubredditDatabase.SubredditDao; +import SubredditDatabase.SubredditData; +import SubscribedSubredditDatabase.SubscribedSubredditDao; +import SubscribedSubredditDatabase.SubscribedSubredditData; +import SubscribedUserDatabase.SubscribedUserDao; +import SubscribedUserDatabase.SubscribedUserData; + +class InsertSubscribedThingsAsyncTask extends AsyncTask<Void, Void, Void> { + + interface InsertSubscribedThingListener { + void insertSuccess(); + } + + private SubscribedSubredditDao mSubscribedSubredditDao; + private SubscribedUserDao mUserDao; + private SubredditDao mSubredditDao; + private List<SubscribedSubredditData> subscribedSubredditData; + private List<SubscribedUserData> subscribedUserData; + private List<SubredditData> subredditData; + private InsertSubscribedThingListener insertSubscribedThingListener; + + InsertSubscribedThingsAsyncTask(SubscribedSubredditDao subscribedSubredditDao, + SubscribedUserDao userDao, + SubredditDao subredditDao, + List<SubscribedSubredditData> subscribedSubredditData, + List<SubscribedUserData> subscribedUserData, + List<SubredditData> subredditData, + InsertSubscribedThingListener insertSubscribedThingListener) { + + mSubscribedSubredditDao = subscribedSubredditDao; + mUserDao = userDao; + mSubredditDao = subredditDao; + + this.subscribedSubredditData = subscribedSubredditData; + this.subscribedUserData = subscribedUserData; + this.subredditData = subredditData; + this.insertSubscribedThingListener = insertSubscribedThingListener; + } + + @Override + protected Void doInBackground(final Void... params) { + if(subscribedSubredditData != null) { + for (SubscribedSubredditData s : subscribedSubredditData) { + mSubscribedSubredditDao.insert(s); + } + } + + if(subscribedUserData != null) { + for (SubscribedUserData s : subscribedUserData) { + mUserDao.insert(s); + } + } + + if(subredditData != null) { + for (SubredditData s : subredditData) { + mSubredditDao.insert(s); + } + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + insertSubscribedThingListener.insertSuccess(); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java index 46730021..7448daf9 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java @@ -3,7 +3,6 @@ package ml.docilealligator.infinityforreddit; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; @@ -17,19 +16,15 @@ import com.bumptech.glide.RequestManager; import com.ferfalk.simplesearchview.SimpleSearchView; import java.util.ArrayList; -import java.util.List; import javax.inject.Inject; import javax.inject.Named; -import SubredditDatabase.SubredditDao; import SubredditDatabase.SubredditData; import SubredditDatabase.SubredditRoomDatabase; -import SubscribedSubredditDatabase.SubscribedSubredditDao; import SubscribedSubredditDatabase.SubscribedSubredditData; import SubscribedSubredditDatabase.SubscribedSubredditRoomDatabase; import SubscribedSubredditDatabase.SubscribedSubredditViewModel; -import SubscribedUserDatabase.SubscribedUserDao; import SubscribedUserDatabase.SubscribedUserData; import SubscribedUserDatabase.SubscribedUserRoomDatabase; import SubscribedUserDatabase.SubscribedUserViewModel; @@ -295,9 +290,9 @@ public class MainActivity extends AppCompatActivity { ArrayList<SubscribedUserData> subscribedUserData, ArrayList<SubredditData> subredditData) { new InsertSubscribedThingsAsyncTask( - SubscribedSubredditRoomDatabase.getDatabase(MainActivity.this), - SubscribedUserRoomDatabase.getDatabase(MainActivity.this), - SubredditRoomDatabase.getDatabase(MainActivity.this), + SubscribedSubredditRoomDatabase.getDatabase(MainActivity.this).subscribedSubredditDao(), + SubscribedUserRoomDatabase.getDatabase(MainActivity.this).subscribedUserDao(), + SubredditRoomDatabase.getDatabase(MainActivity.this).subredditDao(), subscribedSubredditData, subscribedUserData, subredditData, @@ -391,54 +386,4 @@ public class MainActivity extends AppCompatActivity { glide.load(mBannerImageUrl).into(mBannerImageView); } } - - private static class InsertSubscribedThingsAsyncTask extends AsyncTask<Void, Void, Void> { - - interface InsertSubscribedThingListener { - void insertSuccess(); - } - - private final SubscribedSubredditDao mSubscribedSubredditDao; - private final SubscribedUserDao mUserDao; - private final SubredditDao mSubredditDao; - private List<SubscribedSubredditData> subscribedSubredditData; - private List<SubscribedUserData> subscribedUserData; - private List<SubredditData> subredditData; - private InsertSubscribedThingListener insertSubscribedThingListener; - - InsertSubscribedThingsAsyncTask(SubscribedSubredditRoomDatabase subscribedSubredditDb, - SubscribedUserRoomDatabase userDb, - SubredditRoomDatabase subredditDb, - List<SubscribedSubredditData> subscribedSubredditData, - List<SubscribedUserData> subscribedUserData, - List<SubredditData> subredditData, - InsertSubscribedThingListener insertSubscribedThingListener) { - mSubscribedSubredditDao = subscribedSubredditDb.subscribedSubredditDao(); - mUserDao = userDb.subscribedUserDao(); - mSubredditDao = subredditDb.subredditDao(); - this.subscribedSubredditData = subscribedSubredditData; - this.subscribedUserData = subscribedUserData; - this.subredditData = subredditData; - this.insertSubscribedThingListener = insertSubscribedThingListener; - } - - @Override - protected Void doInBackground(final Void... params) { - for (SubscribedSubredditData s : subscribedSubredditData) { - mSubscribedSubredditDao.insert(s); - } - for (SubscribedUserData s : subscribedUserData) { - mUserDao.insert(s); - } - for (SubredditData s : subredditData) { - mSubredditDao.insert(s); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - insertSubscribedThingListener.insertSuccess(); - } - } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkComponent.java index 747dead0..05aae8af 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkComponent.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/NetworkComponent.java @@ -9,6 +9,7 @@ import dagger.Component; interface NetworkComponent { void inject(MainActivity mainActivity); void inject(PostFragment postFragment); + void inject(SubredditListingFragment subredditListingFragment); void inject(ViewPostDetailActivity viewPostDetailActivity); void inject(ViewSubredditDetailActivity viewSubredditDetailActivity); void inject(ViewUserDetailActivity viewUserDetailActivity); diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java index cc7a0326..e12da13f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubredditData.java @@ -3,9 +3,12 @@ package ml.docilealligator.infinityforreddit; import android.os.AsyncTask; import android.util.Log; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; + import SubredditDatabase.SubredditData; class ParseSubredditData { @@ -14,10 +17,19 @@ class ParseSubredditData { void onParseSubredditDataFail(); } + interface ParseSubredditListingDataListener { + void onParseSubredditListingDataSuccess(ArrayList<SubredditData> subredditData, String after); + void onParseSubredditListingDataFail(); + } + static void parseSubredditData(String response, ParseSubredditDataListener parseSubredditDataListener) { new ParseSubredditDataAsyncTask(response, parseSubredditDataListener).execute(); } + static void parseSubredditListingData(String response, ParseSubredditListingDataListener parseSubredditListingDataListener) { + new ParseSubredditListingDataAsyncTask(response, parseSubredditListingDataListener).execute(); + } + private static class ParseSubredditDataAsyncTask extends AsyncTask<Void, Void, Void> { private JSONObject jsonResponse; private boolean parseFailed; @@ -31,7 +43,7 @@ class ParseSubredditData { jsonResponse = new JSONObject(response); parseFailed = false; } catch (JSONException e) { - Log.i("comment json error", e.getMessage()); + Log.i("subreddit json error", e.getMessage()); parseSubredditDataListener.onParseSubredditDataFail(); } } @@ -40,7 +52,9 @@ class ParseSubredditData { protected Void doInBackground(Void... voids) { try { JSONObject data = jsonResponse.getJSONObject(JSONUtils.DATA_KEY); - String id = data.getString(JSONUtils.NAME_KEY); + mNCurrentOnlineSubscribers = data.getInt(JSONUtils.ACTIVE_USER_COUNT_KEY); + subredditData = parseSubredditData(data); + /*String id = data.getString(JSONUtils.NAME_KEY); String subredditFullName = data.getString(JSONUtils.DISPLAY_NAME); String description = data.getString(JSONUtils.PUBLIC_DESCRIPTION_KEY).trim(); @@ -67,7 +81,7 @@ class ParseSubredditData { int nSubscribers = data.getInt(JSONUtils.SUBSCRIBERS_KEY); int nCurrentOnlineSubscribers = data.getInt(JSONUtils.ACTIVE_USER_COUNT_KEY); subredditData = new SubredditData(id, subredditFullName, iconUrl, bannerImageUrl, description, nSubscribers); - mNCurrentOnlineSubscribers = nCurrentOnlineSubscribers; + mNCurrentOnlineSubscribers = nCurrentOnlineSubscribers;*/ } catch (JSONException e) { parseFailed = true; Log.i("parse", "SubredditData error"); @@ -85,4 +99,88 @@ class ParseSubredditData { } } } + + private static class ParseSubredditListingDataAsyncTask extends AsyncTask<Void, Void, Void> { + private JSONObject jsonResponse; + private boolean parseFailed; + private ParseSubredditListingDataListener parseSubredditListingDataListener; + private ArrayList<SubredditData> subredditListingData; + private String after; + + ParseSubredditListingDataAsyncTask(String response, ParseSubredditListingDataListener parseSubredditListingDataListener){ + this.parseSubredditListingDataListener = parseSubredditListingDataListener; + try { + jsonResponse = new JSONObject(response); + parseFailed = false; + subredditListingData = new ArrayList<>(); + } catch (JSONException e) { + Log.i("subreddit json error", e.getMessage()); + parseFailed = true; + parseSubredditListingDataListener.onParseSubredditListingDataFail(); + } + } + + @Override + protected Void doInBackground(Void... voids) { + try { + if(!parseFailed) { + JSONArray children = jsonResponse.getJSONObject(JSONUtils.DATA_KEY) + .getJSONArray(JSONUtils.CHILDREN_KEY); + for(int i = 0; i < children.length(); i++) { + JSONObject data = children.getJSONObject(i).getJSONObject(JSONUtils.DATA_KEY); + SubredditData subredditData = parseSubredditData(data); + subredditListingData.add(subredditData); + } + after = jsonResponse.getJSONObject(JSONUtils.DATA_KEY).getString(JSONUtils.AFTER_KEY); + } + } catch (JSONException e) { + parseFailed = true; + Log.i("parse", "SubredditDataListing error"); + parseSubredditListingDataListener.onParseSubredditListingDataFail(); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + if(!parseFailed) { + parseSubredditListingDataListener.onParseSubredditListingDataSuccess(subredditListingData, after); + } else { + parseSubredditListingDataListener.onParseSubredditListingDataFail(); + } + } + } + + private static SubredditData parseSubredditData(JSONObject subredditDataJsonObject) throws JSONException { + String id = subredditDataJsonObject.getString(JSONUtils.NAME_KEY); + String subredditFullName = subredditDataJsonObject.getString(JSONUtils.DISPLAY_NAME); + String description = subredditDataJsonObject.getString(JSONUtils.PUBLIC_DESCRIPTION_KEY).trim(); + + String bannerImageUrl; + if(subredditDataJsonObject.isNull(JSONUtils.BANNER_BACKGROUND_IMAGE_KEY)) { + bannerImageUrl = ""; + } else { + bannerImageUrl = subredditDataJsonObject.getString(JSONUtils.BANNER_BACKGROUND_IMAGE_KEY); + } + if(bannerImageUrl.equals("") && !subredditDataJsonObject.isNull(JSONUtils.BANNER_IMG_KEY)) { + bannerImageUrl= subredditDataJsonObject.getString(JSONUtils.BANNER_IMG_KEY); + } + + String iconUrl; + if(subredditDataJsonObject.isNull(JSONUtils.COMMUNITY_ICON_KEY)) { + iconUrl = ""; + } else { + iconUrl = subredditDataJsonObject.getString(JSONUtils.COMMUNITY_ICON_KEY); + } + if(iconUrl.equals("") && !subredditDataJsonObject.isNull(JSONUtils.ICON_IMG_KEY)) { + iconUrl = subredditDataJsonObject.getString(JSONUtils.ICON_IMG_KEY); + } + + int nSubscribers = 0; + if(!subredditDataJsonObject.isNull(JSONUtils.SUBSCRIBERS_KEY)) { + nSubscribers = subredditDataJsonObject.getInt(JSONUtils.SUBSCRIBERS_KEY); + } + + return new SubredditData(id, subredditFullName, iconUrl, bannerImageUrl, description, nSubscribers); + } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java index c30991b3..7461bd21 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostDataSource.java @@ -115,8 +115,10 @@ class PostDataSource extends PageKeyedDataSource<String, Post> { break; case TYPE_USER: loadUserPostsAfter(params, callback); + break; case TYPE_SEARCH: loadSearchPostsAfter(params, callback); + break; } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java index 657919ed..2a6330cb 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostRecyclerViewAdapter.java @@ -1,21 +1,11 @@ package ml.docilealligator.infinityforreddit; -import androidx.paging.PagedListAdapter; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.ColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.browser.customtabs.CustomTabsIntent; -import com.google.android.material.card.MaterialCardView; -import com.google.android.material.chip.Chip; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.DiffUtil; -import androidx.cardview.widget.CardView; -import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -36,12 +26,22 @@ import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.target.Target; +import com.google.android.material.card.MaterialCardView; +import com.google.android.material.chip.Chip; import CustomView.AspectRatioGifImageView; import SubredditDatabase.SubredditDao; import SubredditDatabase.SubredditRoomDatabase; import User.UserDao; import User.UserRoomDatabase; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.browser.customtabs.CustomTabsIntent; +import androidx.cardview.widget.CardView; +import androidx.core.content.ContextCompat; +import androidx.paging.PagedListAdapter; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import jp.wasabeef.glide.transformations.BlurTransformation; @@ -600,12 +600,14 @@ class PostRecyclerViewAdapter extends PagedListAdapter<Post, RecyclerView.ViewHo } class ErrorViewHolder extends RecyclerView.ViewHolder { - @BindView(R.id.relative_layout_footer_progress_bar_item) RelativeLayout relativeLayout; - @BindView(R.id.retry_button_footer_progress_bar_item) Button retryButton; + @BindView(R.id.relative_layout_footer_error_item) RelativeLayout relativeLayout; + @BindView(R.id.error_text_view_footer_error_item) TextView errorTextView; + @BindView(R.id.retry_button_footer_error_item) Button retryButton; ErrorViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); + errorTextView.setText(R.string.load_posts_failed); retryButton.setOnClickListener(view -> retryLoadingMoreCallback.retryLoadingMore()); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java index 93d51f25..ab622d09 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PostViewModel.java @@ -1,15 +1,15 @@ package ml.docilealligator.infinityforreddit; +import java.util.Locale; + +import androidx.annotation.NonNull; +import androidx.arch.core.util.Function; import androidx.lifecycle.LiveData; import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; import androidx.paging.LivePagedListBuilder; import androidx.paging.PagedList; -import androidx.annotation.NonNull; - -import java.util.Locale; - import retrofit2.Retrofit; public class PostViewModel extends ViewModel { @@ -23,9 +23,9 @@ public class PostViewModel extends ViewModel { postDataSourceFactory = new PostDataSourceFactory(retrofit, accessToken, locale, postType, onPostFetchedCallback); initialLoadingState = Transformations.switchMap(postDataSourceFactory.getPostDataSourceLiveData(), - dataSource -> dataSource.getInitialLoadStateLiveData()); + (Function<PostDataSource, LiveData<NetworkState>>) PostDataSource::getInitialLoadStateLiveData); paginationNetworkState = Transformations.switchMap(postDataSourceFactory.getPostDataSourceLiveData(), - dataSource -> dataSource.getPaginationNetworkStateLiveData()); + (Function<PostDataSource, LiveData<NetworkState>>) PostDataSource::getPaginationNetworkStateLiveData); PagedList.Config pagedListConfig = (new PagedList.Config.Builder()) .setEnablePlaceholders(false) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java index 1a1c9b37..e467eb0a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditAPI.java @@ -58,8 +58,7 @@ public interface RedditAPI { Call<String> getInfo(@Path("subredditNamePrefixed") String subredditNamePrefixed, @Query("id") String id); @GET("subreddits/search.json?raw_json=1&include_over_18=on") - Call<String> searchSubreddits(@Query("q") String subredditName, @Query("after") String after, - @HeaderMap Map<String, String> headers); + Call<String> searchSubreddits(@Query("q") String subredditName, @Query("after") String after); @GET("profiles/search.json?raw_json=1") Call<String> searchProfiles(@Query("q") String profileName, @Query("after") String after, diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java index a0e08f12..dd72822e 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SearchActivity.java @@ -70,10 +70,9 @@ public class SearchActivity extends AppCompatActivity { return mFragment; } case 1: { - PostFragment mFragment = new PostFragment(); + SubredditListingFragment mFragment = new SubredditListingFragment(); Bundle bundle = new Bundle(); - bundle.putInt(PostFragment.POST_TYPE_KEY, PostDataSource.TYPE_FRONT_PAGE); - bundle.putString(PostFragment.NAME_KEY, mQuery); + bundle.putString(SubredditListingFragment.QUERY_KEY, mQuery); mFragment.setArguments(bundle); return mFragment; } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingDataSource.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingDataSource.java new file mode 100644 index 00000000..80e024d6 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingDataSource.java @@ -0,0 +1,107 @@ +package ml.docilealligator.infinityforreddit; + +import java.util.ArrayList; + +import SubredditDatabase.SubredditData; +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.PageKeyedDataSource; +import retrofit2.Retrofit; + +public class SubredditListingDataSource extends PageKeyedDataSource<String, SubredditData> { + interface OnSubredditListingDataFetchedCallback { + void hasSubreddit(); + void noSubreddit(); + } + private Retrofit retrofit; + private String query; + private OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback; + + private MutableLiveData<NetworkState> paginationNetworkStateLiveData; + private MutableLiveData<NetworkState> initialLoadStateLiveData; + + private LoadInitialParams<String> initialParams; + private LoadInitialCallback<String, SubredditData> initialCallback; + private LoadParams<String> params; + private LoadCallback<String, SubredditData> callback; + + SubredditListingDataSource(Retrofit retrofit, String query, + OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback) { + this.retrofit = retrofit; + this.query = query; + this.onSubredditListingDataFetchedCallback = onSubredditListingDataFetchedCallback; + paginationNetworkStateLiveData = new MutableLiveData(); + initialLoadStateLiveData = new MutableLiveData(); + } + + MutableLiveData getPaginationNetworkStateLiveData() { + return paginationNetworkStateLiveData; + } + + MutableLiveData getInitialLoadStateLiveData() { + return initialLoadStateLiveData; + } + + @Override + public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, SubredditData> callback) { + initialParams = params; + initialCallback = callback; + + initialLoadStateLiveData.postValue(NetworkState.LOADING); + + FetchSubredditData.fetchSubredditListingData(retrofit, query, null, new FetchSubredditData.FetchSubredditListingDataListener() { + @Override + public void onFetchSubredditListingDataSuccess(ArrayList<SubredditData> subredditData, String after) { + if(subredditData.size() == 0) { + onSubredditListingDataFetchedCallback.noSubreddit(); + } else { + onSubredditListingDataFetchedCallback.hasSubreddit(); + } + + callback.onResult(subredditData, null, after); + initialLoadStateLiveData.postValue(NetworkState.LOADED); + } + + @Override + public void onFetchSubredditListingDataFail() { + initialLoadStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error retrieving subreddit list")); + } + }); + } + + @Override + public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, SubredditData> callback) { + + } + + @Override + public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, SubredditData> callback) { + this.params = params; + this.callback = callback; + + if(params.key.equals("null")) { + return; + } + + FetchSubredditData.fetchSubredditListingData(retrofit, query, params.key, new FetchSubredditData.FetchSubredditListingDataListener() { + @Override + public void onFetchSubredditListingDataSuccess(ArrayList<SubredditData> subredditData, String after) { + callback.onResult(subredditData, after); + paginationNetworkStateLiveData.postValue(NetworkState.LOADED); + } + + @Override + public void onFetchSubredditListingDataFail() { + paginationNetworkStateLiveData.postValue(new NetworkState(NetworkState.Status.FAILED, "Error retrieving subreddit list")); + } + }); + } + + void retry() { + loadInitial(initialParams, initialCallback); + } + + void retryLoadingMore() { + loadAfter(params, callback); + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingDataSourceFactory.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingDataSourceFactory.java new file mode 100644 index 00000000..168fadbd --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingDataSourceFactory.java @@ -0,0 +1,40 @@ +package ml.docilealligator.infinityforreddit; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; +import androidx.paging.DataSource; +import retrofit2.Retrofit; + +public class SubredditListingDataSourceFactory extends DataSource.Factory { + private Retrofit retrofit; + private String query; + private SubredditListingDataSource.OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback; + + private SubredditListingDataSource subredditListingDataSource; + private MutableLiveData<SubredditListingDataSource> subredditListingDataSourceMutableLiveData; + + SubredditListingDataSourceFactory(Retrofit retrofit, String query, + SubredditListingDataSource.OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback) { + this.retrofit = retrofit; + this.query = query; + this.onSubredditListingDataFetchedCallback = onSubredditListingDataFetchedCallback; + subredditListingDataSourceMutableLiveData = new MutableLiveData<>(); + } + + @NonNull + @Override + public DataSource create() { + SubredditListingDataSource subredditListingDataSource = new SubredditListingDataSource(retrofit, + query, onSubredditListingDataFetchedCallback); + subredditListingDataSourceMutableLiveData.postValue(subredditListingDataSource); + return subredditListingDataSource; + } + + public MutableLiveData<SubredditListingDataSource> getSubredditListingDataSourceMutableLiveData() { + return subredditListingDataSourceMutableLiveData; + } + + SubredditListingDataSource getSubredditListingDataSource() { + return subredditListingDataSource; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingFragment.java new file mode 100644 index 00000000..f591c4f9 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingFragment.java @@ -0,0 +1,141 @@ +package ml.docilealligator.infinityforreddit; + + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar; + +import javax.inject.Inject; +import javax.inject.Named; + +import SubscribedSubredditDatabase.SubscribedSubredditRoomDatabase; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import retrofit2.Retrofit; + + +/** + * A simple {@link Fragment} subclass. + */ +public class SubredditListingFragment extends Fragment { + + static final String QUERY_KEY = "QK"; + + @BindView(R.id.coordinator_layout_subreddit_listing_fragment) CoordinatorLayout mCoordinatorLayout; + @BindView(R.id.recycler_view_subreddit_listing_fragment) RecyclerView mSubredditListingRecyclerView; + @BindView(R.id.progress_bar_subreddit_listing_fragment) CircleProgressBar mProgressBar; + @BindView(R.id.fetch_subreddit_listing_info_linear_layout_subreddit_listing_fragment) LinearLayout mFetchSubredditListingInfoLinearLayout; + @BindView(R.id.fetch_subreddit_listing_info_image_view_subreddit_listing_fragment) ImageView mFetchSubredditListingInfoImageView; + @BindView(R.id.fetch_subreddit_listing_info_text_view_subreddit_listing_fragment) TextView mFetchSubredditListingInfoTextView; + + private LinearLayoutManager mLinearLayoutManager; + + private String mQuery; + + private SubredditListingRecyclerViewAdapter mAdapter; + + SubredditListingViewModel mSubredditListingViewModel; + + @Inject @Named("auth_info") + SharedPreferences mAuthInfoSharedPreferences; + + @Inject @Named("no_oauth") + Retrofit mRetrofit; + + @Inject @Named("oauth") + Retrofit mOauthRetrofit; + + public SubredditListingFragment() { + // Required empty public constructor + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + View rootView = inflater.inflate(R.layout.fragment_subreddit_listing, container, false); + + ((Infinity) getActivity().getApplication()).getmNetworkComponent().inject(this); + + ButterKnife.bind(this, rootView); + + mLinearLayoutManager = new LinearLayoutManager(getActivity()); + mSubredditListingRecyclerView.setLayoutManager(mLinearLayoutManager); + + mQuery = getArguments().getString(QUERY_KEY); + + String accessToken = getActivity().getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE) + .getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, ""); + + SubredditListingViewModel.Factory factory = new SubredditListingViewModel.Factory(mRetrofit, mQuery, + new SubredditListingDataSource.OnSubredditListingDataFetchedCallback() { + @Override + public void hasSubreddit() { + mFetchSubredditListingInfoLinearLayout.setVisibility(View.GONE); + } + + @Override + public void noSubreddit() { + mFetchSubredditListingInfoLinearLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //Do nothing + } + }); + showErrorView(R.string.no_subreddits); + } + }); + + mAdapter = new SubredditListingRecyclerViewAdapter(getActivity(), mOauthRetrofit, mRetrofit, + mAuthInfoSharedPreferences, + SubscribedSubredditRoomDatabase.getDatabase(getContext()).subscribedSubredditDao(), + () -> mSubredditListingViewModel.retryLoadingMore()); + + mSubredditListingRecyclerView.setAdapter(mAdapter); + + mSubredditListingViewModel = ViewModelProviders.of(this, factory).get(SubredditListingViewModel.class); + mSubredditListingViewModel.getSubreddits().observe(this, subredditData -> mAdapter.submitList(subredditData)); + + mSubredditListingViewModel.getInitialLoadingState().observe(this, networkState -> { + if(networkState.getStatus().equals(NetworkState.Status.SUCCESS)) { + mProgressBar.setVisibility(View.GONE); + } else if(networkState.getStatus().equals(NetworkState.Status.FAILED)) { + mFetchSubredditListingInfoLinearLayout.setOnClickListener(view -> mSubredditListingViewModel.retry()); + showErrorView(R.string.load_posts_error); + } else { + mFetchSubredditListingInfoLinearLayout.setVisibility(View.GONE); + mProgressBar.setVisibility(View.VISIBLE); + } + }); + + mSubredditListingViewModel.getPaginationNetworkState().observe(this, networkState -> { + mAdapter.setNetworkState(networkState); + }); + + return rootView; + } + + private void showErrorView(int stringResId) { + mProgressBar.setVisibility(View.GONE); + if(getActivity() != null && isAdded()) { + mFetchSubredditListingInfoLinearLayout.setVisibility(View.VISIBLE); + mFetchSubredditListingInfoTextView.setText(stringResId); + Glide.with(this).load(R.drawable.load_post_error_indicator).into(mFetchSubredditListingInfoImageView); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java new file mode 100644 index 00000000..56735c5e --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingRecyclerViewAdapter.java @@ -0,0 +1,233 @@ +package ml.docilealligator.infinityforreddit; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.RequestOptions; + +import SubredditDatabase.SubredditData; +import SubscribedSubredditDatabase.SubscribedSubredditDao; +import androidx.annotation.NonNull; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.paging.PagedListAdapter; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; +import butterknife.BindView; +import butterknife.ButterKnife; +import jp.wasabeef.glide.transformations.RoundedCornersTransformation; +import pl.droidsonroids.gif.GifImageView; +import retrofit2.Retrofit; + +public class SubredditListingRecyclerViewAdapter extends PagedListAdapter<SubredditData, RecyclerView.ViewHolder> { + interface RetryLoadingMoreCallback { + void retryLoadingMore(); + } + + private RequestManager glide; + + private static final int VIEW_TYPE_DATA = 0; + private static final int VIEW_TYPE_ERROR = 1; + private static final int VIEW_TYPE_LOADING = 2; + + private Context context; + private Retrofit oauthRetrofit; + private Retrofit retrofit; + private SharedPreferences authInfoSharedPreferences; + private SubscribedSubredditDao subscribedSubredditDao; + + private NetworkState networkState; + private RetryLoadingMoreCallback retryLoadingMoreCallback; + + SubredditListingRecyclerViewAdapter(Context context, Retrofit oauthRetrofit, Retrofit retrofit, + SharedPreferences authInfoSharedPreferences, + SubscribedSubredditDao subscribedSubredditDao, + RetryLoadingMoreCallback retryLoadingMoreCallback) { + super(DIFF_CALLBACK); + this.context = context; + this.oauthRetrofit = oauthRetrofit; + this.retrofit = retrofit; + this.authInfoSharedPreferences = authInfoSharedPreferences; + this.subscribedSubredditDao = subscribedSubredditDao; + this.retryLoadingMoreCallback = retryLoadingMoreCallback; + glide = Glide.with(context.getApplicationContext()); + } + + static final DiffUtil.ItemCallback<SubredditData> DIFF_CALLBACK = new DiffUtil.ItemCallback<SubredditData>() { + @Override + public boolean areItemsTheSame(@NonNull SubredditData oldItem, @NonNull SubredditData newItem) { + return oldItem.getId().equals(newItem.getId()); + } + + @Override + public boolean areContentsTheSame(@NonNull SubredditData oldItem, @NonNull SubredditData newItem) { + return true; + } + }; + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if(viewType == VIEW_TYPE_DATA) { + ConstraintLayout constraintLayout = (ConstraintLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_subreddit_listing, parent, false); + return new DataViewHolder(constraintLayout); + } else if(viewType == VIEW_TYPE_ERROR) { + RelativeLayout relativeLayout = (RelativeLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_error, parent, false); + return new ErrorViewHolder(relativeLayout); + } else { + RelativeLayout relativeLayout = (RelativeLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_loading, parent, false); + return new LoadingViewHolder(relativeLayout); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if(holder instanceof DataViewHolder) { + SubredditData subredditData = getItem(position); + ((DataViewHolder) holder).constraintLayout.setOnClickListener(view -> { + Intent intent = new Intent(context, ViewSubredditDetailActivity.class); + intent.putExtra(ViewSubredditDetailActivity.EXTRA_SUBREDDIT_NAME_KEY, subredditData.getName()); + context.startActivity(intent); + }); + + if(subredditData.getIconUrl() != null) { + glide.load(subredditData.getIconUrl()) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) + .error(glide.load(R.drawable.subreddit_default_icon)) + .into(((DataViewHolder) holder).iconGifImageView); + } else { + glide.load(R.drawable.subreddit_default_icon) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) + .into(((DataViewHolder) holder).iconGifImageView); + } + + ((DataViewHolder) holder).subredditNameTextView.setText(subredditData.getName()); + + new CheckIsSubscribedToSubredditAsyncTask(subscribedSubredditDao, subredditData.getName(), + new CheckIsSubscribedToSubredditAsyncTask.CheckIsSubscribedToSubredditListener() { + @Override + public void isSubscribed() { + ((DataViewHolder) holder).subscribeButton.setVisibility(View.GONE); + } + + @Override + public void isNotSubscribed() { + ((DataViewHolder) holder).subscribeButton.setVisibility(View.VISIBLE); + ((DataViewHolder) holder).subscribeButton.setOnClickListener(view -> { + SubredditSubscription.subscribeToSubreddit(oauthRetrofit, retrofit, + authInfoSharedPreferences, subredditData.getName(), subscribedSubredditDao, + new SubredditSubscription.SubredditSubscriptionListener() { + @Override + public void onSubredditSubscriptionSuccess() { + ((DataViewHolder) holder).subscribeButton.setVisibility(View.GONE); + Toast.makeText(context, R.string.subscribed, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSubredditSubscriptionFail() { + Toast.makeText(context, R.string.subscribe_failed, Toast.LENGTH_SHORT).show(); + } + }); + }); + } + }).execute(); + } + } + + @Override + public int getItemViewType(int position) { + // Reached at the end + if (hasExtraRow() && position == getItemCount() - 1) { + if (networkState.getStatus() == NetworkState.Status.LOADING) { + return VIEW_TYPE_LOADING; + } else { + return VIEW_TYPE_ERROR; + } + } else { + return VIEW_TYPE_DATA; + } + } + + @Override + public int getItemCount() { + if(hasExtraRow()) { + return super.getItemCount() + 1; + } + return super.getItemCount(); + } + + private boolean hasExtraRow() { + return networkState != null && networkState.getStatus() != NetworkState.Status.SUCCESS; + } + + void setNetworkState(NetworkState newNetworkState) { + NetworkState previousState = this.networkState; + boolean previousExtraRow = hasExtraRow(); + this.networkState = newNetworkState; + boolean newExtraRow = hasExtraRow(); + if (previousExtraRow != newExtraRow) { + if (previousExtraRow) { + notifyItemRemoved(super.getItemCount()); + } else { + notifyItemInserted(super.getItemCount()); + } + } else if (newExtraRow && !previousState.equals(newNetworkState)) { + notifyItemChanged(getItemCount() - 1); + } + } + + class DataViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.constraint_layout_item_subreddit_listing) ConstraintLayout constraintLayout; + @BindView(R.id.subreddit_icon_gif_image_view_item_subreddit_listing) GifImageView iconGifImageView; + @BindView(R.id.subreddit_name_text_view_item_subreddit_listing) TextView subredditNameTextView; + @BindView(R.id.subscribe_image_view_item_subreddit_listing) ImageView subscribeButton; + + DataViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + class ErrorViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.relative_layout_footer_error_item) RelativeLayout relativeLayout; + @BindView(R.id.error_text_view_footer_error_item) TextView errorTextView; + @BindView(R.id.retry_button_footer_error_item) Button retryButton; + + ErrorViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + retryButton.setOnClickListener(view -> retryLoadingMoreCallback.retryLoadingMore()); + errorTextView.setText(R.string.load_comment_failed); + } + } + + class LoadingViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.progress_bar_footer_progress_bar_item) ProgressBar progressBar; + + LoadingViewHolder(@NonNull View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + } + } + + @Override + public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { + if(holder instanceof DataViewHolder) { + glide.clear(((DataViewHolder) holder).iconGifImageView); + ((DataViewHolder) holder).subscribeButton.setVisibility(View.GONE); + } + } + + +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingViewModel.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingViewModel.java new file mode 100644 index 00000000..1ebb2dc3 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditListingViewModel.java @@ -0,0 +1,79 @@ +package ml.docilealligator.infinityforreddit; + +import SubredditDatabase.SubredditData; +import androidx.annotation.NonNull; +import androidx.arch.core.util.Function; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Transformations; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import androidx.paging.LivePagedListBuilder; +import androidx.paging.PagedList; +import retrofit2.Retrofit; + +public class SubredditListingViewModel extends ViewModel { + private SubredditListingDataSourceFactory subredditListingDataSourceFactory; + private LiveData<NetworkState> paginationNetworkState; + private LiveData<NetworkState> initialLoadingState; + private LiveData<PagedList<SubredditData>> subreddits; + + SubredditListingViewModel(Retrofit retrofit, String query, + SubredditListingDataSource.OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback) { + subredditListingDataSourceFactory = new SubredditListingDataSourceFactory(retrofit, query, onSubredditListingDataFetchedCallback); + + initialLoadingState = Transformations.switchMap(subredditListingDataSourceFactory.getSubredditListingDataSourceMutableLiveData(), + (Function<SubredditListingDataSource, LiveData<NetworkState>>) SubredditListingDataSource::getInitialLoadStateLiveData); + paginationNetworkState = Transformations.switchMap(subredditListingDataSourceFactory.getSubredditListingDataSourceMutableLiveData(), + (Function<SubredditListingDataSource, LiveData<NetworkState>>) SubredditListingDataSource::getPaginationNetworkStateLiveData); + PagedList.Config pagedListConfig = + (new PagedList.Config.Builder()) + .setEnablePlaceholders(false) + .setPageSize(25) + .build(); + + subreddits = (new LivePagedListBuilder(subredditListingDataSourceFactory, pagedListConfig)).build(); + } + + LiveData<PagedList<SubredditData>> getSubreddits() { + return subreddits; + } + + LiveData<NetworkState> getPaginationNetworkState() { + return paginationNetworkState; + } + + LiveData<NetworkState> getInitialLoadingState() { + return initialLoadingState; + } + + void refresh() { + subredditListingDataSourceFactory.getSubredditListingDataSource().invalidate(); + } + + void retry() { + subredditListingDataSourceFactory.getSubredditListingDataSource().retry(); + } + + void retryLoadingMore() { + subredditListingDataSourceFactory.getSubredditListingDataSource().retryLoadingMore(); + } + + public static class Factory extends ViewModelProvider.NewInstanceFactory { + private Retrofit retrofit; + private String query; + private SubredditListingDataSource.OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback; + + public Factory(Retrofit retrofit, String query, + SubredditListingDataSource.OnSubredditListingDataFetchedCallback onSubredditListingDataFetchedCallback) { + this.retrofit = retrofit; + this.query = query; + this.onSubredditListingDataFetchedCallback = onSubredditListingDataFetchedCallback; + } + + @NonNull + @Override + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + return (T) new SubredditListingViewModel(retrofit, query, onSubredditListingDataFetchedCallback); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditSubscription.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditSubscription.java index ceb73e3b..9ede0db5 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditSubscription.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SubredditSubscription.java @@ -2,7 +2,6 @@ package ml.docilealligator.infinityforreddit; import android.content.SharedPreferences; import android.os.AsyncTask; -import androidx.annotation.NonNull; import android.util.Log; import java.util.HashMap; @@ -11,6 +10,7 @@ import java.util.Map; import SubredditDatabase.SubredditData; import SubscribedSubredditDatabase.SubscribedSubredditDao; import SubscribedSubredditDatabase.SubscribedSubredditData; +import androidx.annotation.NonNull; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Retrofit; @@ -55,8 +55,10 @@ class SubredditSubscription { if(action.equals("sub")) { FetchSubredditData.fetchSubredditData(retrofit, subredditName, new FetchSubredditData.FetchSubredditDataListener() { @Override - public void onFetchSubredditDataSuccess(String response) { - ParseSubredditData.parseSubredditData(response, new ParseSubredditData.ParseSubredditDataListener() { + public void onFetchSubredditDataSuccess(SubredditData subredditData, int nCurrentOnlineSubscribers) { + new UpdateSubscriptionAsyncTask(subscribedSubredditDao, + subredditData, true).execute(); + /*ParseSubredditData.parseSubredditData(response, new ParseSubredditData.ParseSubredditDataListener() { @Override public void onParseSubredditDataSuccess(SubredditData subredditData, int nCurrentOnlineSubscribers) { new UpdateSubscriptionAsyncTask(subscribedSubredditDao, @@ -67,7 +69,7 @@ class SubredditSubscription { public void onParseSubredditDataFail() { } - }); + });*/ } @Override diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewSubredditDetailActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewSubredditDetailActivity.java index 831a22ff..b4127233 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewSubredditDetailActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewSubredditDetailActivity.java @@ -1,18 +1,9 @@ package ml.docilealligator.infinityforreddit; -import androidx.lifecycle.ViewModelProviders; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; -import com.google.android.material.chip.Chip; -import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.appbar.CollapsingToolbarLayout; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import com.google.android.material.snackbar.Snackbar; -import androidx.fragment.app.Fragment; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -23,6 +14,10 @@ import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.RequestManager; import com.bumptech.glide.request.RequestOptions; +import com.google.android.material.appbar.AppBarLayout; +import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.chip.Chip; +import com.google.android.material.snackbar.Snackbar; import javax.inject.Inject; import javax.inject.Named; @@ -32,8 +27,12 @@ import SubredditDatabase.SubredditData; import SubredditDatabase.SubredditRoomDatabase; import SubredditDatabase.SubredditViewModel; import SubscribedSubredditDatabase.SubscribedSubredditDao; -import SubscribedSubredditDatabase.SubscribedSubredditData; import SubscribedSubredditDatabase.SubscribedSubredditRoomDatabase; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProviders; import butterknife.BindView; import butterknife.ButterKnife; import jp.wasabeef.glide.transformations.RoundedCornersTransformation; @@ -244,8 +243,12 @@ public class ViewSubredditDetailActivity extends AppCompatActivity { FetchSubredditData.fetchSubredditData(mRetrofit, subredditName, new FetchSubredditData.FetchSubredditDataListener() { @Override - public void onFetchSubredditDataSuccess(String response) { - ParseSubredditData.parseSubredditData(response, new ParseSubredditData.ParseSubredditDataListener() { + public void onFetchSubredditDataSuccess(SubredditData subredditData, int nCurrentOnlineSubscribers) { + new InsertSubredditDataAsyncTask(SubredditRoomDatabase.getDatabase(ViewSubredditDetailActivity.this), subredditData) + .execute(); + String nOnlineSubscribers = getString(R.string.online_subscribers_number_detail, nCurrentOnlineSubscribers); + nOnlineSubscribersTextView.setText(nOnlineSubscribers); + /*ParseSubredditData.parseSubredditData(response, new ParseSubredditData.ParseSubredditDataListener() { @Override public void onParseSubredditDataSuccess(SubredditData subredditData, int nCurrentOnlineSubscribers) { new InsertSubredditDataAsyncTask(SubredditRoomDatabase.getDatabase(ViewSubredditDetailActivity.this), subredditData) @@ -258,7 +261,7 @@ public class ViewSubredditDetailActivity extends AppCompatActivity { public void onParseSubredditDataFail() { makeSnackbar(R.string.cannot_fetch_subreddit_info); } - }); + });*/ } @Override @@ -329,7 +332,7 @@ public class ViewSubredditDetailActivity extends AppCompatActivity { } } - private static class CheckIsSubscribedToSubredditAsyncTask extends AsyncTask<Void, Void, Void> { + /*private static class CheckIsSubscribedToSubredditAsyncTask extends AsyncTask<Void, Void, Void> { private SubscribedSubredditDao subscribedSubredditDao; private String subredditName; @@ -363,5 +366,5 @@ public class ViewSubredditDetailActivity extends AppCompatActivity { checkIsSubscribedToSubredditListener.isNotSubscribed(); } } - } + }*/ } diff --git a/app/src/main/res/layout/fragment_subreddit_listing.xml b/app/src/main/res/layout/fragment_subreddit_listing.xml new file mode 100644 index 00000000..d6fba93a --- /dev/null +++ b/app/src/main/res/layout/fragment_subreddit_listing.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/coordinator_layout_subreddit_listing_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + tools:context=".SubredditListingFragment"> + + <com.lsjwzh.widget.materialloadingprogressbar.CircleProgressBar + android:id="@+id/progress_bar_subreddit_listing_fragment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + app:mlpb_progress_stoke_width="3dp" + app:mlpb_progress_color="@color/colorAccent" + android:layout_gravity="center_horizontal"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recycler_view_subreddit_listing_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <LinearLayout + android:id="@+id/fetch_subreddit_listing_info_linear_layout_subreddit_listing_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + android:visibility="gone"> + + <ImageView + android:id="@+id/fetch_subreddit_listing_info_image_view_subreddit_listing_fragment" + android:layout_width="150dp" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/fetch_subreddit_listing_info_text_view_subreddit_listing_fragment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:gravity="center" /> + + </LinearLayout> + +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_footer_error.xml b/app/src/main/res/layout/item_footer_error.xml index bd653604..e1d3c14b 100644 --- a/app/src/main/res/layout/item_footer_error.xml +++ b/app/src/main/res/layout/item_footer_error.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/relative_layout_footer_progress_bar_item" + android:id="@+id/relative_layout_footer_error_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="16dp" @@ -9,16 +9,16 @@ android:layout_marginBottom="16dp"> <TextView + android:id="@+id/error_text_view_footer_error_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_centerVertical="true" - android:layout_toStartOf="@id/retry_button_footer_progress_bar_item" - android:text="@string/load_data_failed" + android:layout_toStartOf="@id/retry_button_footer_error_item" android:textSize="18sp" /> <Button - android:id="@+id/retry_button_footer_progress_bar_item" + android:id="@+id/retry_button_footer_error_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" diff --git a/app/src/main/res/layout/item_subreddit_listing.xml b/app/src/main/res/layout/item_subreddit_listing.xml new file mode 100644 index 00000000..10d5055a --- /dev/null +++ b/app/src/main/res/layout/item_subreddit_listing.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/constraint_layout_item_subreddit_listing" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:clickable="true" + android:focusable="true" + android:background="?attr/selectableItemBackground"> + + <pl.droidsonroids.gif.GifImageView + android:id="@+id/subreddit_icon_gif_image_view_item_subreddit_listing" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_gravity="center_vertical" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent"/> + + <TextView + android:id="@+id/subreddit_name_text_view_item_subreddit_listing" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginStart="32dp" + android:textColor="@android:color/black" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@+id/subreddit_icon_gif_image_view_item_subreddit_listing" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/subscribe_image_view_item_subreddit_listing" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + + android:src="@drawable/baseline_add_white_24" + android:tint="@color/colorPrimary" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:clickable="true" + android:focusable="true" + android:background="?attr/selectableItemBackground" + android:visibility="gone"/> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8058872..d286b264 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,8 +14,9 @@ <string name="tap_to_retry">Error loading image. Tap to retry.</string> <string name="load_posts_error">Error loading posts.\nTap to retry.</string> <string name="no_posts">No posts here.</string> + <string name="no_subreddits">No subreddits found.</string> - <string name="load_data_failed">Cannot load posts</string> + <string name="load_posts_failed">Error loading posts</string> <string name="load_comment_failed">Error loading comments</string> <string name="load_more_comment_failed">Error loading more comments</string> <string name="retry">Retry</string> @@ -56,9 +57,5 @@ <string name="app_label">Infinity</string> <string name="search_hint">Search anything</string> - <string name="title_activity_search">Search</string> - <string name="section_format">Hello World from section: %1$d</string> - <string name="tab_text_1">Tab 1</string> - <string name="tab_text_2">Tab 2</string> - <string name="tab_text_3">Tab 3</string> + </resources> |