View user details in ViewUserDetailActivity. Follow or unfollow user is not properly implemented right now. Change users and subscribed_users databases' schemes. Press Profile in navigation drawer to view my reddit info. Press the username in the post to view that account's info.

This commit is contained in:
Alex Ning 2019-01-11 11:33:32 +08:00
parent f0b149ce82
commit e48bb565a5
29 changed files with 704 additions and 120 deletions

View File

@ -50,8 +50,8 @@
android:theme="@style/AppTheme.NoActionBarWithTranslucentStatusBar" />
<activity
android:name=".ViewUserDetailActivity"
android:label="@string/title_activity_view_user_detail"
android:theme="@style/AppTheme.NoActionBar" />
android:parentActivityName=".MainActivity"
android:theme="@style/AppTheme.NoActionBarWithTranslucentStatusBar" />
</application>
</manifest>

View File

@ -14,7 +14,7 @@ public interface SubredditDao {
@Query("DELETE FROM subreddits")
void deleteAllSubreddits();
@Query("SELECT * from subreddits WHERE name = :namePrefixed")
@Query("SELECT * from subreddits WHERE name = :namePrefixed LIMIT 1")
LiveData<SubredditData> getSubredditLiveDataByName(String namePrefixed);
@Query("SELECT * from subreddits WHERE name = :namePrefixed LIMIT 1")

View File

@ -20,14 +20,14 @@ public class SubredditRepository {
}
public void insert(SubredditData subredditData) {
new SubredditRepository.insertAsyncTask(mSubredditDao).execute(subredditData);
new InsertAsyncTask(mSubredditDao).execute(subredditData);
}
private static class insertAsyncTask extends AsyncTask<SubredditData, Void, Void> {
private static class InsertAsyncTask extends AsyncTask<SubredditData, Void, Void> {
private SubredditDao mAsyncTaskDao;
insertAsyncTask(SubredditDao dao) {
InsertAsyncTask(SubredditDao dao) {
mAsyncTaskDao = dao;
}

View File

@ -11,7 +11,7 @@ public class SubredditViewModel extends AndroidViewModel {
private SubredditRepository mSubredditRepository;
private LiveData<SubredditData> mSubredditLiveData;
SubredditViewModel(Application application, String id) {
public SubredditViewModel(Application application, String id) {
super(application);
mSubredditRepository = new SubredditRepository(application, id);
mSubredditLiveData = mSubredditRepository.getSubredditLiveData();

View File

@ -16,6 +16,12 @@ public interface SubscribedUserDao {
@Query("DELETE FROM subscribed_users")
void deleteAllSubscribedUsers();
@Query("SELECT * from subscribed_users ORDER BY name COLLATE NOCASE ASC")
@Query("SELECT * FROM subscribed_users ORDER BY name COLLATE NOCASE ASC")
LiveData<List<SubscribedUserData>> getAllSubscribedUsers();
@Query("SELECT * FROM subscribed_users WHERE name = :userName LIMIT 1")
SubscribedUserData getSubscribedUser(String userName);
@Query("DELETE FROM subscribed_users WHERE name = :userName")
void deleteSubscribedUser(String userName);
}

View File

@ -9,24 +9,17 @@ import android.support.annotation.NonNull;
public class SubscribedUserData {
@PrimaryKey
@NonNull
@ColumnInfo(name = "id")
private String id;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "icon")
private String iconUrl;
public SubscribedUserData(@NonNull String id, String name, String iconUrl) {
this.id = id;
public SubscribedUserData(@NonNull String name, String iconUrl) {
this.name = name;
this.iconUrl = iconUrl;
}
@NonNull
public String getId() {
return id;
}
public String getName() {
return name;
}

View File

@ -1,5 +1,6 @@
package User;
import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.OnConflictStrategy;
@ -8,11 +9,14 @@ import android.arch.persistence.room.Query;
@Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(User user);
void insert(UserData userData);
@Query("DELETE FROM users")
void deleteAllUsers();
@Query("SELECT * FROM users WHERE user_name = :userName LIMIT 1")
User getUserData(String userName);
@Query("SELECT * FROM users WHERE name = :userName LIMIT 1")
LiveData<UserData> getUserLiveData(String userName);
@Query("SELECT * FROM users WHERE name = :userName LIMIT 1")
UserData getUserData(String userName);
}

View File

@ -2,17 +2,12 @@ package User;
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;
import android.support.annotation.NonNull;
import SubscribedUserDatabase.SubscribedUserData;
@Entity(tableName = "users")
public class User {
@PrimaryKey
@NonNull
@ColumnInfo(name = "user_name")
private String userName;
@ColumnInfo(name = "icon")
private String icon;
public class UserData extends SubscribedUserData {
@ColumnInfo(name = "banner")
private String banner;
@ColumnInfo(name = "karma")
@ -22,24 +17,14 @@ public class User {
@ColumnInfo(name = "is_friend")
private boolean isFriend;
User(@NonNull String userName, String icon, String banner, int karma, boolean isGold, boolean isFriend) {
this.userName = userName;
this.icon = icon;
public UserData(@NonNull String name, String iconUrl, String banner, int karma, boolean isGold, boolean isFriend) {
super(name, iconUrl);
this.banner = banner;
this.karma = karma;
this.isGold = isGold;
this.isFriend = isFriend;
}
@NonNull
public String getUserName() {
return userName;
}
public String getIcon() {
return icon;
}
public String getBanner() {
return banner;
}

View File

@ -0,0 +1,39 @@
package User;
import android.app.Application;
import android.arch.lifecycle.LiveData;
import android.os.AsyncTask;
public class UserRepository {
private UserDao mUserDao;
private LiveData<UserData> mUserLiveData;
UserRepository(Application application, String userName) {
mUserDao = UserRoomDatabase.getDatabase(application).userDao();
mUserLiveData = mUserDao.getUserLiveData(userName);
}
LiveData<UserData> getUserLiveData() {
return mUserLiveData;
}
public void insert(UserData userData) {
new InsertAsyncTask(mUserDao).execute(userData);
}
private static class InsertAsyncTask extends AsyncTask<UserData, Void, Void> {
private UserDao mAsyncTaskDao;
InsertAsyncTask(UserDao dao) {
mAsyncTaskDao = dao;
}
@Override
protected Void doInBackground(final UserData... params) {
mAsyncTaskDao.insert(params[0]);
return null;
}
}
}

View File

@ -5,7 +5,7 @@ import android.arch.persistence.room.Room;
import android.arch.persistence.room.RoomDatabase;
import android.content.Context;
@Database(entities = {User.class}, version = 1)
@Database(entities = {UserData.class}, version = 1)
public abstract class UserRoomDatabase extends RoomDatabase {
private static UserRoomDatabase INSTANCE;

View File

@ -0,0 +1,48 @@
package User;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.support.annotation.NonNull;
import SubredditDatabase.SubredditViewModel;
public class UserViewModel extends AndroidViewModel {
private UserRepository mSubredditRepository;
private LiveData<UserData> mUserLiveData;
public UserViewModel(Application application, String id) {
super(application);
mSubredditRepository = new UserRepository(application, id);
mUserLiveData = mSubredditRepository.getUserLiveData();
}
public LiveData<UserData> getUserLiveData() {
return mUserLiveData;
}
public void insert(UserData userData) {
mSubredditRepository.insert(userData);
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
@NonNull
private final Application mApplication;
private final String userName;
public Factory(@NonNull Application application, String userName) {
mApplication = application;
this.userName = userName;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
//noinspection unchecked
return (T) new UserViewModel(mApplication, userName);
}
}
}

View File

@ -1,16 +1,16 @@
package User;
package ml.docilealligator.infinityforreddit;
import android.support.annotation.NonNull;
import android.util.Log;
import ml.docilealligator.infinityforreddit.RedditAPI;
import User.UserData;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
public class FetchUserData {
public interface FetchUserDataListener {
void onFetchUserDataSuccess(User user);
void onFetchUserDataSuccess(UserData userData);
void onFetchUserDataFail();
}
@ -23,10 +23,10 @@ public class FetchUserData {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull retrofit2.Response<String> response) {
if(response.isSuccessful()) {
ParseUserData.parseMyInfo(response.body(), new ParseUserData.ParseUserDataListener() {
ParseUserData.parseUserData(response.body(), new ParseUserData.ParseUserDataListener() {
@Override
public void onParseUserDataSuccess(User user) {
fetchUserDataListener.onFetchUserDataSuccess(user);
public void onParseUserDataSuccess(UserData userData) {
fetchUserDataListener.onFetchUserDataSuccess(userData);
}
@Override

View File

@ -2,27 +2,27 @@ package ml.docilealligator.infinityforreddit;
import android.os.AsyncTask;
import User.User;
import User.UserData;
import User.UserDao;
public class InsertUserDataAsyncTask extends AsyncTask<Void, Void, Void> {
private UserDao userDao;
private User user;
private UserData userData;
private InsertUserDataCallback insertUserDataCallback;
public interface InsertUserDataCallback {
void insertSuccess();
}
public InsertUserDataAsyncTask(UserDao userDao, User user, InsertUserDataCallback insertUserDataCallback) {
public InsertUserDataAsyncTask(UserDao userDao, UserData userData, InsertUserDataCallback insertUserDataCallback) {
this.userDao = userDao;
this.user = user;
this.userData = userData;
this.insertUserDataCallback = insertUserDataCallback;
}
@Override
protected Void doInBackground(Void... voids) {
userDao.insert(user);
userDao.insert(userData);
return null;
}

View File

@ -2,8 +2,7 @@ package ml.docilealligator.infinityforreddit;
import android.os.AsyncTask;
import User.FetchUserData;
import User.User;
import User.UserData;
import User.UserDao;
import retrofit2.Retrofit;
@ -29,7 +28,7 @@ public class LoadUserDataAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
if(userDao.getUserData(userName) != null) {
iconImageUrl = userDao.getUserData(userName).getIcon();
iconImageUrl = userDao.getUserData(userName).getIconUrl();
hasUserInDb = true;
} else {
hasUserInDb = false;
@ -45,13 +44,8 @@ public class LoadUserDataAsyncTask extends AsyncTask<Void, Void, Void> {
} else {
FetchUserData.fetchUserData(retrofit, userName, new FetchUserData.FetchUserDataListener() {
@Override
public void onFetchUserDataSuccess(User user) {
new InsertUserDataAsyncTask(userDao, user, new InsertUserDataAsyncTask.InsertUserDataCallback() {
@Override
public void insertSuccess() {
loadUserDataAsyncTaskListener.loadUserDataSuccess(user.getIcon());
}
}).execute();
public void onFetchUserDataSuccess(UserData userData) {
new InsertUserDataAsyncTask(userDao, userData, () -> loadUserDataAsyncTaskListener.loadUserDataSuccess(userData.getIconUrl())).execute();
}
@Override

View File

@ -20,6 +20,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.bumptech.glide.Glide;
@ -61,6 +62,7 @@ public class MainActivity extends AppCompatActivity {
@BindView(R.id.subscriptions_label_main_activity) TextView subscriptionsLabelTextView;
@BindView(R.id.subscribed_user_recycler_view_main_activity) RecyclerView subscribedUserRecyclerView;
@BindView(R.id.following_label_main_activity) TextView followingLabelTextView;
@BindView(R.id.profile_linear_layout_main_activity) LinearLayout profileLinearLayout;
private TextView mNameTextView;
private TextView mKarmaTextView;
@ -153,13 +155,14 @@ public class MainActivity extends AppCompatActivity {
glide.load(mBannerImageUrl).into(mBannerImageView);
}
final SubscribedSubredditRecyclerViewAdapter subredditadapter = new SubscribedSubredditRecyclerViewAdapter(this,
new SubscribedSubredditRecyclerViewAdapter.OnItemClickListener() {
@Override
public void onClick() {
drawer.closeDrawers();
}
});
profileLinearLayout.setOnClickListener(view -> {
Intent intent = new Intent(MainActivity.this, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, mName);
startActivity(intent);
});
final SubscribedSubredditRecyclerViewAdapter subredditadapter =
new SubscribedSubredditRecyclerViewAdapter(this, drawer::closeDrawers);
subscribedSubredditRecyclerView.setAdapter(subredditadapter);
mSubscribedSubredditViewModel = ViewModelProviders.of(this).get(SubscribedSubredditViewModel.class);
@ -176,13 +179,8 @@ public class MainActivity extends AppCompatActivity {
}
});
final SubscribedUserRecyclerViewAdapter userAdapter = new SubscribedUserRecyclerViewAdapter(this,
new SubscribedUserRecyclerViewAdapter.OnItemClickListener() {
@Override
public void onClick() {
drawer.closeDrawers();
}
});
final SubscribedUserRecyclerViewAdapter userAdapter =
new SubscribedUserRecyclerViewAdapter(this, drawer::closeDrawers);
subscribedUserRecyclerView.setAdapter(userAdapter);
mSubscribedUserViewModel = ViewModelProviders.of(this).get(SubscribedUserViewModel.class);
mSubscribedUserViewModel.getAllSubscribedUsers().observe(this, new Observer<List<SubscribedUserData>>() {

View File

@ -11,4 +11,5 @@ interface NetworkComponent {
void inject(PostFragment postFragment);
void inject(ViewPostDetailActivity viewPostDetailActivity);
void inject(ViewSubredditDetailActivity viewSubredditDetailActivity);
void inject(ViewUserDetailActivity viewUserDetailActivity);
}

View File

@ -94,7 +94,7 @@ class ParseSubscribedThing {
if(data.getString(JSONUtils.SUBREDDIT_TYPE_KEY)
.equals(JSONUtils.SUBREDDIT_TYPE_VALUE_USER)) {
//It's a user
newSubscribedUserData.add(new SubscribedUserData(id, name.substring(2), iconUrl));
newSubscribedUserData.add(new SubscribedUserData(name.substring(2), iconUrl));
} else {
String subredditFullName = data.getString(JSONUtils.DISPLAY_NAME);
String description = data.getString(JSONUtils.PUBLIC_DESCRIPTION_KEY).trim();

View File

@ -1,4 +1,4 @@
package User;
package ml.docilealligator.infinityforreddit;
import android.os.AsyncTask;
import android.util.Log;
@ -6,15 +6,15 @@ import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import ml.docilealligator.infinityforreddit.JSONUtils;
import User.UserData;
public class ParseUserData {
interface ParseUserDataListener {
void onParseUserDataSuccess(User user);
void onParseUserDataSuccess(UserData userData);
void onParseUserDataFail();
}
static void parseMyInfo(String response, ParseUserDataListener parseUserDataListener) {
static void parseUserData(String response, ParseUserDataListener parseUserDataListener) {
new ParseUserDataAsyncTask(response, parseUserDataListener).execute();
}
@ -23,15 +23,16 @@ public class ParseUserData {
private ParseUserDataListener parseUserDataListener;
private boolean parseFailed;
private User user;
private UserData userData;
ParseUserDataAsyncTask(String response, ParseUserDataListener parseUserDataListener){
try {
Log.i("response", response);
jsonResponse = new JSONObject(response);
this.parseUserDataListener = parseUserDataListener;
parseFailed = false;
} catch (JSONException e) {
Log.i("user data json error", e.getMessage());
Log.i("userdata json error", e.getMessage());
parseUserDataListener.onParseUserDataFail();
}
}
@ -43,7 +44,7 @@ public class ParseUserData {
String userName = jsonResponse.getString(JSONUtils.NAME_KEY);
String iconImageUrl = jsonResponse.getString(JSONUtils.ICON_IMG_KEY);
String bannerImageUrl = "";
if(!jsonResponse.isNull(JSONUtils.SUBREDDIT_KEY)) {
if(jsonResponse.has(JSONUtils.SUBREDDIT_KEY) && !jsonResponse.isNull(JSONUtils.SUBREDDIT_KEY)) {
bannerImageUrl = jsonResponse.getJSONObject(JSONUtils.SUBREDDIT_KEY).getString(JSONUtils.BANNER_IMG_KEY);
}
int linkKarma = jsonResponse.getInt(JSONUtils.LINK_KARMA_KEY);
@ -52,7 +53,7 @@ public class ParseUserData {
boolean isGold = jsonResponse.getBoolean(JSONUtils.IS_GOLD_KEY);
boolean isFriend = jsonResponse.getBoolean(JSONUtils.IS_FRIEND_KEY);
user = new User(userName, iconImageUrl, bannerImageUrl, karma, isGold, isFriend);
userData = new UserData(userName, iconImageUrl, bannerImageUrl, karma, isGold, isFriend);
} catch (JSONException e) {
parseFailed = true;
Log.i("parse user data error", e.getMessage());
@ -63,7 +64,7 @@ public class ParseUserData {
@Override
protected void onPostExecute(Void aVoid) {
if(!parseFailed) {
parseUserDataListener.onParseUserDataSuccess(user);
parseUserDataListener.onParseUserDataSuccess(userData);
} else {
parseUserDataListener.onParseUserDataFail();
}

View File

@ -300,6 +300,7 @@ class PostRecyclerViewAdapter extends PagedListAdapter<Post, RecyclerView.ViewHo
if(canStartActivity) {
canStartActivity = false;
Intent intent = new Intent(mContext, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, post.getAuthor());
mContext.startActivity(intent);
}
});
@ -310,6 +311,7 @@ class PostRecyclerViewAdapter extends PagedListAdapter<Post, RecyclerView.ViewHo
if(canStartActivity) {
canStartActivity = false;
Intent intent = new Intent(mContext, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, post.getAuthor());
mContext.startActivity(intent);
}
});

View File

@ -3,11 +3,13 @@ package ml.docilealligator.infinityforreddit;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.DELETE;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.HeaderMap;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
@ -45,4 +47,10 @@ public interface RedditAPI {
@FormUrlEncoded
@POST("api/subscribe")
Call<String> subredditSubscription(@HeaderMap Map<String, String> headers, @FieldMap Map<String, String> params);
@PUT("/api/v1/me/friends/username")
Call<String> subscribeUser(@HeaderMap Map<String, String> headers);
@DELETE("/api/v1/me/friends/username")
Call<String> unsubscribeUser(@HeaderMap Map<String, String> headers);
}

View File

@ -30,7 +30,7 @@ public class RedditUtils {
static final String AUTHORIZATION_KEY = "Authorization";
static final String AUTHORIZATION_BASE = "bearer ";
static final String USER_AGENT_KEY = "User-Agent";
static final String USER_AGENT_KEY = "UserData-Agent";
static final String USER_AGENT = "";
static final String GRANT_TYPE_KEY = "grant_type";
@ -47,6 +47,7 @@ public class RedditUtils {
static final String ACTION_KEY = "action";
static final String SR_NAME_KEY = "sr_name";
static final String SR_KEY = "sr";
static Map<String, String> getHttpBasicAuthHeader() {
Map<String, String> params = new HashMap<>();

View File

@ -1,6 +1,7 @@
package ml.docilealligator.infinityforreddit;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
@ -27,6 +28,7 @@ import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
public class SubscribedUserRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<SubscribedUserData> mSubscribedUserData;
private Context mContext;
private RequestManager glide;
private OnItemClickListener mOnItemClickListener;
@ -35,6 +37,7 @@ public class SubscribedUserRecyclerViewAdapter extends RecyclerView.Adapter<Recy
}
SubscribedUserRecyclerViewAdapter(Context context, OnItemClickListener onItemClickListener) {
mContext = context;
glide = Glide.with(context.getApplicationContext());
mOnItemClickListener = onItemClickListener;
}
@ -47,13 +50,11 @@ public class SubscribedUserRecyclerViewAdapter extends RecyclerView.Adapter<Recy
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder viewHolder, int i) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Need to be implemented
mOnItemClickListener.onClick();
}
viewHolder.itemView.setOnClickListener(view -> {
Intent intent = new Intent(mContext, ViewUserDetailActivity.class);
intent.putExtra(ViewUserDetailActivity.EXTRA_USER_NAME_KEY, mSubscribedUserData.get(viewHolder.getAdapterPosition()).getName());
mContext.startActivity(intent);
mOnItemClickListener.onClick();
});
if(!mSubscribedUserData.get(i).getIconUrl().equals("")) {
glide.load(mSubscribedUserData.get(i).getIconUrl())

View File

@ -0,0 +1,117 @@
package ml.docilealligator.infinityforreddit;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import SubscribedUserDatabase.SubscribedUserDao;
import SubscribedUserDatabase.SubscribedUserData;
import User.UserData;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Retrofit;
class UserFollowing {
interface UserFollowingListener {
void onUserFollowingSuccess();
void onUserFollowingFail();
}
static void followUser(Retrofit oauthRetrofit, Retrofit retrofit,
SharedPreferences authInfoSharedPreferences, String userName,
SubscribedUserDao subscribedUserDao,
UserFollowingListener userFollowingListener) {
userFollowing(oauthRetrofit, retrofit, authInfoSharedPreferences, userName, "sub",
subscribedUserDao, userFollowingListener);
}
static void unfollowUser(Retrofit oauthRetrofit, Retrofit retrofit,
SharedPreferences authInfoSharedPreferences, String userName,
SubscribedUserDao subscribedUserDao,
UserFollowingListener userFollowingListener) {
userFollowing(oauthRetrofit, retrofit, authInfoSharedPreferences, userName, "unsub",
subscribedUserDao, userFollowingListener);
}
private static void userFollowing(Retrofit oauthRetrofit, Retrofit retrofit, SharedPreferences authInfoSharedPreferences,
String userName, String action, SubscribedUserDao subscribedUserDao,
UserFollowingListener userFollowingListener) {
RedditAPI api = oauthRetrofit.create(RedditAPI.class);
String accessToken = authInfoSharedPreferences.getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, "");
Map<String, String> params = new HashMap<>();
params.put(RedditUtils.ACTION_KEY, action);
params.put(RedditUtils.SR_KEY, "u/" + userName);
Call<String> subredditSubscriptionCall = api.subredditSubscription(RedditUtils.getOAuthHeader(accessToken), params);
subredditSubscriptionCall.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull Call<String> call, @NonNull retrofit2.Response<String> response) {
if(response.isSuccessful()) {
if(action.equals("sub")) {
FetchUserData.fetchUserData(retrofit, userName, new FetchUserData.FetchUserDataListener() {
@Override
public void onFetchUserDataSuccess(UserData userData) {
new UpdateSubscriptionAsyncTask(subscribedUserDao, userData, true).execute();
}
@Override
public void onFetchUserDataFail() {
}
});
} else {
new UpdateSubscriptionAsyncTask(subscribedUserDao, userName, false).execute();
}
userFollowingListener.onUserFollowingSuccess();
} else {
Log.i("call failed", response.message());
userFollowingListener.onUserFollowingFail();
}
}
@Override
public void onFailure(@NonNull Call<String> call, @NonNull Throwable t) {
Log.i("call failed", t.getMessage());
userFollowingListener.onUserFollowingFail();
}
});
}
private static class UpdateSubscriptionAsyncTask extends AsyncTask<Void, Void, Void> {
private SubscribedUserDao subscribedUserDao;
private String userName;
private SubscribedUserData subscribedUserData;
private boolean isSubscribing;
UpdateSubscriptionAsyncTask(SubscribedUserDao subscribedUserDao, String userName,
boolean isSubscribing) {
this.subscribedUserDao = subscribedUserDao;
this.userName = userName;
this.isSubscribing = isSubscribing;
}
UpdateSubscriptionAsyncTask(SubscribedUserDao subscribedUserDao, SubscribedUserData subscribedUserData,
boolean isSubscribing) {
this.subscribedUserDao = subscribedUserDao;
this.subscribedUserData = subscribedUserData;
this.isSubscribing = isSubscribing;
}
@Override
protected Void doInBackground(Void... voids) {
if(isSubscribing) {
subscribedUserDao.insert(subscribedUserData);
} else {
subscribedUserDao.deleteSubscribedUser(userName);;
}
return null;
}
}
}

View File

@ -22,7 +22,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
@ -104,11 +103,11 @@ public class ViewSubredditDetailActivity extends AppCompatActivity {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams();
params.topMargin = statusBarHeight;
final String subredditName = getIntent().getExtras().getString(EXTRA_SUBREDDIT_NAME_KEY);
String subredditName = getIntent().getExtras().getString(EXTRA_SUBREDDIT_NAME_KEY);
final String title = "r/" + subredditName;
final CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar_layout_view_subreddit_detail_activity);
final AppBarLayout appBarLayout = findViewById(R.id.app_bar_layout_view_subreddit_detail_activity);
String title = "r/" + subredditName;
CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar_layout_view_subreddit_detail_activity);
AppBarLayout appBarLayout = findViewById(R.id.app_bar_layout_view_subreddit_detail_activity);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
int previousVerticalOffset = 0;
@ -136,10 +135,10 @@ public class ViewSubredditDetailActivity extends AppCompatActivity {
});
subscribedSubredditDao = SubscribedSubredditRoomDatabase.getDatabase(this).subscribedSubredditDao();
glide = Glide.with(ViewSubredditDetailActivity.this);
glide = Glide.with(this);
SubredditViewModel.Factory factory = new SubredditViewModel.Factory(getApplication(), subredditName);
mSubredditViewModel = ViewModelProviders.of(this, factory).get(SubredditViewModel.class);
mSubredditViewModel = ViewModelProviders.of(this, new SubredditViewModel.Factory(getApplication(), subredditName))
.get(SubredditViewModel.class);
mSubredditViewModel.getSubredditLiveData().observe(this, subredditData -> {
if(subredditData != null) {
if(subredditData.getBannerUrl().equals("")) {
@ -279,7 +278,7 @@ public class ViewSubredditDetailActivity extends AppCompatActivity {
@Override
public void onParseSubredditDataFail() {
Toast.makeText(ViewSubredditDetailActivity.this, "Cannot fetch subreddit info", Toast.LENGTH_SHORT).show();
makeSnackbar(R.string.cannot_fetch_subreddit_info);
}
});
}

View File

@ -1,13 +1,78 @@
package ml.docilealligator.infinityforreddit;
import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.chip.Chip;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.DataSource;
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.felipecsl.gifimageview.library.GifImageView;
import javax.inject.Inject;
import javax.inject.Named;
import SubscribedUserDatabase.SubscribedUserDao;
import SubscribedUserDatabase.SubscribedUserData;
import SubscribedUserDatabase.SubscribedUserRoomDatabase;
import User.UserDao;
import User.UserData;
import User.UserRoomDatabase;
import User.UserViewModel;
import butterknife.BindView;
import butterknife.ButterKnife;
import jp.wasabeef.glide.transformations.RoundedCornersTransformation;
import retrofit2.Retrofit;
public class ViewUserDetailActivity extends AppCompatActivity {
static final String EXTRA_USER_NAME_KEY = "EUNK";
@BindView(R.id.coordinator_layout_view_user_detail_activity) CoordinatorLayout coordinatorLayout;
@BindView(R.id.banner_image_view_view_user_detail_activity) ImageView bannerImageView;
@BindView(R.id.icon_gif_image_view_view_user_detail_activity) GifImageView iconGifImageView;
@BindView(R.id.user_name_text_view_view_user_detail_activity) TextView userNameTextView;
@BindView(R.id.subscribe_user_chip_view_user_detail_activity) Chip subscribeUserChip;
@BindView(R.id.karma_text_view_view_user_detail_activity) TextView karmaTextView;
private SubscribedUserDao subscribedUserDao;
private RequestManager glide;
private UserViewModel userViewModel;
private boolean subscriptionReady = false;
@Inject
@Named("no_oauth")
Retrofit mRetrofit;
@Inject
@Named("oauth")
Retrofit mOauthRetrofit;
@Inject
@Named("auth_info")
SharedPreferences sharedPreferences;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -15,6 +80,254 @@ public class ViewUserDetailActivity extends AppCompatActivity {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ButterKnife.bind(this);
((Infinity) getApplication()).getmNetworkComponent().inject(this);
setSupportActionBar(toolbar);
//Get status bar height
int statusBarHeight = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) toolbar.getLayoutParams();
params.topMargin = statusBarHeight;
String userName = getIntent().getExtras().getString(EXTRA_USER_NAME_KEY);
String title = "u/" + userName;
CollapsingToolbarLayout collapsingToolbarLayout = findViewById(R.id.collapsing_toolbar_layout_view_user_detail_activity);
AppBarLayout appBarLayout = findViewById(R.id.app_bar_layout_view_user_detail_activity);
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
int previousVerticalOffset = 0;
int scrollRange = -1;
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if(scrollRange == -1) {
scrollRange = appBarLayout.getTotalScrollRange();
} else {
if(verticalOffset < previousVerticalOffset) {
//Scroll down
if(scrollRange - Math.abs(verticalOffset) <= toolbar.getHeight()) {
collapsingToolbarLayout.setTitle(title);
}
} else {
//Scroll up
if(scrollRange - Math.abs(verticalOffset) > toolbar.getHeight()) {
collapsingToolbarLayout.setTitle(" ");//carefull there should a space between double quote otherwise it wont work
}
}
previousVerticalOffset = verticalOffset;
}
}
});
subscribeUserChip.setOnClickListener(view -> {
if(subscriptionReady) {
subscriptionReady = false;
if(subscribeUserChip.getText().equals(getResources().getString(R.string.subscribe))) {
UserFollowing.followUser(mOauthRetrofit, mRetrofit, sharedPreferences,
userName, subscribedUserDao, new UserFollowing.UserFollowingListener() {
@Override
public void onUserFollowingSuccess() {
subscribeUserChip.setText(R.string.unfollow);
subscribeUserChip.setChipBackgroundColor(getResources().getColorStateList(R.color.colorAccent));
makeSnackbar(R.string.followed);
subscriptionReady = true;
}
@Override
public void onUserFollowingFail() {
makeSnackbar(R.string.follow_failed);
subscriptionReady = true;
}
});
} else {
UserFollowing.unfollowUser(mOauthRetrofit, mRetrofit, sharedPreferences,
userName, subscribedUserDao, new UserFollowing.UserFollowingListener() {
@Override
public void onUserFollowingSuccess() {
subscribeUserChip.setText(R.string.follow);
subscribeUserChip.setChipBackgroundColor(getResources().getColorStateList(R.color.colorPrimaryDark));
makeSnackbar(R.string.unfollowed);
subscriptionReady = true;
}
@Override
public void onUserFollowingFail() {
makeSnackbar(R.string.unfollow_failed);
subscriptionReady = true;
}
});
}
}
});
subscribedUserDao = SubscribedUserRoomDatabase.getDatabase(this).subscribedUserDao();
new CheckIsFollowingUserAsyncTask(subscribedUserDao, userName, new CheckIsFollowingUserAsyncTask.CheckIsFollowingUserListener() {
@Override
public void isSubscribed() {
subscribeUserChip.setText(R.string.unfollow);
subscribeUserChip.setChipBackgroundColor(getResources().getColorStateList(R.color.colorAccent));
subscriptionReady = true;
}
@Override
public void isNotSubscribed() {
subscribeUserChip.setText(R.string.follow);
subscribeUserChip.setChipBackgroundColor(getResources().getColorStateList(R.color.colorPrimaryDark));
subscriptionReady = true;
}
}).execute();
glide = Glide.with(this);
userViewModel = ViewModelProviders.of(this, new UserViewModel.Factory(getApplication(), userName))
.get(UserViewModel.class);
userViewModel.getUserLiveData().observe(this, userData -> {
if(userData != null) {
if(userData.getBanner().equals("")) {
bannerImageView.setOnClickListener(view -> {
//Do nothing since the user has no banner image
});
} else {
glide.load(userData.getBanner()).into(bannerImageView);
bannerImageView.setOnClickListener(view -> {
Intent intent = new Intent(this, ViewImageActivity.class);
intent.putExtra(ViewImageActivity.TITLE_KEY, title);
intent.putExtra(ViewImageActivity.IMAGE_URL_KEY, userData.getBanner());
intent.putExtra(ViewImageActivity.FILE_NAME_KEY, userName + "-banner");
startActivity(intent);
});
}
if(userData.getIconUrl().equals("")) {
glide.load(getDrawable(R.drawable.subreddit_default_icon))
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(216, 0)))
.into(iconGifImageView);
iconGifImageView.setOnClickListener(view -> {
//Do nothing since the user has no icon image
});
} else {
glide.load(userData.getIconUrl())
.apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(216, 0)))
.error(glide.load(R.drawable.subreddit_default_icon))
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
if(resource instanceof Animatable) {
//This is a gif
((Animatable) resource).start();
iconGifImageView.startAnimation();
}
return false;
}
})
.into(iconGifImageView);
iconGifImageView.setOnClickListener(view -> {
Intent intent = new Intent(this, ViewImageActivity.class);
intent.putExtra(ViewImageActivity.TITLE_KEY, title);
intent.putExtra(ViewImageActivity.IMAGE_URL_KEY, userData.getIconUrl());
intent.putExtra(ViewImageActivity.FILE_NAME_KEY, userName + "-icon");
startActivity(intent);
});
}
String userFullName = "u/" + userData.getName();
userNameTextView.setText(userFullName);
String karma = getString(R.string.karma_info, userData.getKarma());
karmaTextView.setText(karma);
}
});
FetchUserData.fetchUserData(mRetrofit, userName, new FetchUserData.FetchUserDataListener() {
@Override
public void onFetchUserDataSuccess(UserData userData) {
new InsertUserDataAsyncTask(UserRoomDatabase.getDatabase(ViewUserDetailActivity.this).userDao(), userData).execute();
}
@Override
public void onFetchUserDataFail() {
makeSnackbar(R.string.cannot_fetch_user_info);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return false;
}
private void makeSnackbar(int resId) {
Snackbar.make(coordinatorLayout, resId, Snackbar.LENGTH_SHORT).show();
}
private static class InsertUserDataAsyncTask extends AsyncTask<Void, Void, Void> {
private UserDao userDao;
private UserData subredditData;
InsertUserDataAsyncTask(UserDao userDao, UserData userData) {
this.userDao = userDao;
this.subredditData = userData;
}
@Override
protected Void doInBackground(final Void... params) {
userDao.insert(subredditData);
return null;
}
}
private static class CheckIsFollowingUserAsyncTask extends AsyncTask<Void, Void, Void> {
private SubscribedUserDao subscribedUserDao;
private String userName;
private SubscribedUserData subscribedUserData;
private CheckIsFollowingUserListener checkIsFollowingUserListener;
interface CheckIsFollowingUserListener {
void isSubscribed();
void isNotSubscribed();
}
CheckIsFollowingUserAsyncTask(SubscribedUserDao subscribedUserDao, String userName,
CheckIsFollowingUserListener checkIsFollowingUserListener) {
this.subscribedUserDao = subscribedUserDao;
this.userName = userName;
this.checkIsFollowingUserListener = checkIsFollowingUserListener;
}
@Override
protected Void doInBackground(Void... voids) {
subscribedUserData = subscribedUserDao.getSubscribedUser(userName);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if(subscribedUserData != null) {
checkIsFollowingUserListener.isSubscribed();
} else {
checkIsFollowingUserListener.isNotSubscribed();
}
}
}
}

View File

@ -35,6 +35,7 @@
layout="@layout/nav_header_main" />
<LinearLayout
android:id="@+id/profile_linear_layout_main_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"

View File

@ -4,33 +4,97 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:id="@+id/coordinator_layout_view_user_detail_activity"
tools:context=".ViewUserDetailActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:id="@+id/app_bar_layout_view_user_detail_activity"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:id="@+id/collapsing_toolbar_layout_view_user_detail_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:toolbarId="@+id/toolbar">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/banner_image_view_view_user_detail_activity"
android:layout_width="match_parent"
android:scaleType="centerCrop"
android:layout_height="180dp"
android:contentDescription="@string/content_description_banner_imageview" />
<com.felipecsl.gifimageview.library.GifImageView
android:id="@+id/icon_gif_image_view_view_user_detail_activity"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_marginTop="-36dp"
app:civ_border_color="@android:color/white"
app:civ_border_width="1dp"
android:layout_below="@id/banner_image_view_view_user_detail_activity"
android:layout_centerHorizontal="true"
android:elevation="4dp"
app:civ_circle_background_color="@android:color/white"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="36dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_below="@id/banner_image_view_view_user_detail_activity"
android:background="@android:color/white">
<TextView
android:id="@+id/user_name_text_view_view_user_detail_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="18sp"
android:textColor="@color/colorAccent"
android:layout_gravity="center_horizontal"/>
<TextView
android:id="@+id/karma_text_view_view_user_detail_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@android:color/black"
android:layout_gravity="center_horizontal"/>
<android.support.design.chip.Chip
android:id="@+id/subscribe_user_chip_view_user_detail_activity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:textColor="@android:color/white"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
</RelativeLayout>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:navigationIcon="?attr/homeAsUpIndicator"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_view_user_detail" />

View File

@ -26,6 +26,8 @@
<string name="subscriptions">Subscriptions</string>
<string name="subscribers_number_detail">Subscribers: %1$d</string>
<string name="online_subscribers_number_detail">Online: %1$d</string>
<string name="cannot_fetch_subreddit_info">Cannot fetch subreddit info</string>
<string name="cannot_fetch_user_info">Cannot fetch user info</string>
<string name="gilded">x%1$d</string>
<string name="title_activity_view_user_detail">ViewUserDetailActivity</string>
@ -49,15 +51,15 @@
"and use of imagery guide visual treatments. These elements do far more than please the "
"eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
"imagery, large scale typography, and intentional white space create a bold and graphic "
"interface that immerse the user in the experience.\n"
"An emphasis on user actions makes core functionality immediately apparent and provides "
"waypoints for the user.\n\n"
"interface that immerse the userData in the experience.\n"
"An emphasis on userData actions makes core functionality immediately apparent and provides "
"waypoints for the userData.\n\n"
"Motion provides meaning.\n\n"
"Motion respects and reinforces the user as the prime mover. Primary user actions are "
"Motion respects and reinforces the userData as the prime mover. Primary userData actions are "
"inflection points that initiate motion, transforming the whole design.\n"
"All action takes place in a single environment. Objects are presented to the user without "
"All action takes place in a single environment. Objects are presented to the userData without "
"breaking the continuity of experience even as they transform and reorganize.\n"
"Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
"Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
@ -96,7 +98,7 @@
"Responsive elevation and dynamic elevation offsets.\n\n"
"Some component types have responsive elevation, meaning they change elevation in response "
"to user input (e.g., normal, focused, and pressed) or system events. These elevation "
"to userData input (e.g., normal, focused, and pressed) or system events. These elevation "
"changes are consistently implemented using dynamic elevation offsets.\n"
"Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
"to the components resting state. They ensure that elevation changes are consistent "
@ -113,10 +115,10 @@
"whether on a per component basis or using the entire app layout.\n"
"On a component level, components can move or be removed before they cause interference. "
"For example, a floating action button (FAB) can disappear or move off screen before a "
"user picks up a card, or it can move if a snackbar appears.\n"
"userData picks up a card, or it can move if a snackbar appears.\n"
"On the layout level, design your app layout to minimize opportunities for interference. "
"For example, position the FAB to one side of stream of a cards so the FAB wont interfere "
"when a user tries to pick up one of cards.\n\n"
"when a userData tries to pick up one of cards.\n\n"
</string>
<string name="subscribe">Subscribe</string>
@ -126,5 +128,12 @@
<string name="unsubscribed">Unsubscribed</string>"
<string name="unsubscribe_failed">Unsubscribe Failed</string>
<string name="follow">Follow</string>
<string name="unfollow">Unfollow</string>
<string name="followed">Followed</string>
<string name="follow_failed">Following Failed</string>
<string name="unfollowed">Unfollowed</string>
<string name="unfollow_failed">Unfollowing Failed</string>
<string name="content_description_banner_imageview">Subreddit Banner Image</string>
</resources>

2
gradlew vendored
View File

@ -119,7 +119,7 @@ if $cygwin ; then
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
# Add a userData-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi