Browse Source
Add ViewModel to post view to maintain state. Update some ui
renovate/org.robolectric-robolectric-4.x
Add ViewModel to post view to maintain state. Update some ui
renovate/org.robolectric-robolectric-4.x
Ammar Githam
4 years ago
13 changed files with 990 additions and 511 deletions
-
10app/src/main/java/awais/instagrabber/asyncs/FeedPostFetchService.java
-
2app/src/main/java/awais/instagrabber/customviews/ProfilePicView.java
-
5app/src/main/java/awais/instagrabber/customviews/SharedElementTransitionDialogFragment.java
-
92app/src/main/java/awais/instagrabber/dialogs/EditTextDialogFragment.java
-
3app/src/main/java/awais/instagrabber/dialogs/MultiOptionDialogFragment.java
-
860app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
-
273app/src/main/java/awais/instagrabber/viewmodels/PostViewV2ViewModel.java
-
10app/src/main/res/drawable/ic_more_vert_24.xml
-
10app/src/main/res/drawable/ic_round_location_on_24.xml
-
2app/src/main/res/drawable/shape_oval_light.xml
-
223app/src/main/res/layout/dialog_post_view.xml
-
6app/src/main/res/menu/post_view_menu.xml
-
5app/src/main/res/values/strings.xml
@ -0,0 +1,92 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.annotation.StringRes; |
|||
import androidx.appcompat.widget.AppCompatEditText; |
|||
import androidx.fragment.app.DialogFragment; |
|||
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
|
|||
public class EditTextDialogFragment extends DialogFragment { |
|||
|
|||
private Context context; |
|||
private EditTextDialogFragmentCallback callback; |
|||
|
|||
public static EditTextDialogFragment newInstance(@StringRes final int title, |
|||
@StringRes final int positiveText, |
|||
@StringRes final int negativeText, |
|||
@Nullable final String initialText) { |
|||
Bundle args = new Bundle(); |
|||
args.putInt("title", title); |
|||
args.putInt("positive", positiveText); |
|||
args.putInt("negative", negativeText); |
|||
args.putString("initial", initialText); |
|||
EditTextDialogFragment fragment = new EditTextDialogFragment(); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
public EditTextDialogFragment() {} |
|||
|
|||
@Override |
|||
public void onAttach(@NonNull final Context context) { |
|||
super.onAttach(context); |
|||
try { |
|||
callback = (EditTextDialogFragmentCallback) getParentFragment(); |
|||
} catch (ClassCastException e) { |
|||
throw new ClassCastException("Calling fragment must implement EditTextDialogFragmentCallback interface"); |
|||
} |
|||
this.context = context; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { |
|||
final Bundle arguments = getArguments(); |
|||
int title = -1; |
|||
int positiveButtonText = R.string.ok; |
|||
int negativeButtonText = R.string.cancel; |
|||
String initialText = null; |
|||
if (arguments != null) { |
|||
title = arguments.getInt("title", -1); |
|||
positiveButtonText = arguments.getInt("positive", R.string.ok); |
|||
negativeButtonText = arguments.getInt("negative", R.string.cancel); |
|||
initialText = arguments.getString("initial", null); |
|||
} |
|||
final AppCompatEditText input = new AppCompatEditText(context); |
|||
if (!TextUtils.isEmpty(initialText)) { |
|||
input.setText(initialText); |
|||
} |
|||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context) |
|||
.setView(input) |
|||
.setPositiveButton(positiveButtonText, (d, w) -> { |
|||
final String string = input.getText() != null ? input.getText().toString() : ""; |
|||
if (callback != null) { |
|||
callback.onPositiveButtonClicked(string); |
|||
} |
|||
}) |
|||
.setNegativeButton(negativeButtonText, (dialog, which) -> { |
|||
if (callback != null) { |
|||
callback.onNegativeButtonClicked(); |
|||
} |
|||
}); |
|||
if (title > 0) { |
|||
builder.setTitle(title); |
|||
} |
|||
return builder.create(); |
|||
} |
|||
|
|||
public interface EditTextDialogFragmentCallback { |
|||
void onPositiveButtonClicked(String text); |
|||
|
|||
void onNegativeButtonClicked(); |
|||
} |
|||
} |
860
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,273 @@ |
|||
package awais.instagrabber.viewmodels; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.lifecycle.LiveData; |
|||
import androidx.lifecycle.MutableLiveData; |
|||
import androidx.lifecycle.ViewModel; |
|||
|
|||
import com.google.common.collect.ImmutableList; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.models.Resource; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.repositories.responses.Caption; |
|||
import awais.instagrabber.repositories.responses.Location; |
|||
import awais.instagrabber.repositories.responses.Media; |
|||
import awais.instagrabber.repositories.responses.User; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.webservices.MediaService; |
|||
import awais.instagrabber.webservices.ServiceCallback; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class PostViewV2ViewModel extends ViewModel { |
|||
private static final String TAG = PostViewV2ViewModel.class.getSimpleName(); |
|||
|
|||
private final MutableLiveData<User> user = new MutableLiveData<>(); |
|||
private final MutableLiveData<Caption> caption = new MutableLiveData<>(); |
|||
private final MutableLiveData<Location> location = new MutableLiveData<>(); |
|||
private final MutableLiveData<String> date = new MutableLiveData<>(); |
|||
private final MutableLiveData<Long> likeCount = new MutableLiveData<>(0L); |
|||
private final MutableLiveData<Long> commentCount = new MutableLiveData<>(0L); |
|||
private final MutableLiveData<MediaItemType> type = new MutableLiveData<>(); |
|||
private final MutableLiveData<Boolean> liked = new MutableLiveData<>(false); |
|||
private final MutableLiveData<Boolean> saved = new MutableLiveData<>(false); |
|||
private final MutableLiveData<List<Integer>> options = new MutableLiveData<>(new ArrayList<>()); |
|||
private final MediaService mediaService; |
|||
private final long viewerId; |
|||
private final String csrfToken; |
|||
private final boolean isLoggedIn; |
|||
|
|||
private Media media; |
|||
|
|||
public PostViewV2ViewModel() { |
|||
mediaService = MediaService.getInstance(); |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
viewerId = CookieUtils.getUserIdFromCookie(cookie); |
|||
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); |
|||
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; |
|||
} |
|||
|
|||
public void setMedia(final Media media) { |
|||
this.media = media; |
|||
user.postValue(media.getUser()); |
|||
caption.postValue(media.getCaption()); |
|||
location.postValue(media.getLocation()); |
|||
date.postValue(media.getDate()); |
|||
likeCount.postValue(media.getLikeCount()); |
|||
commentCount.postValue(media.getCommentCount()); |
|||
type.postValue(media.getMediaType()); |
|||
liked.postValue(media.hasLiked()); |
|||
saved.postValue(media.hasViewerSaved()); |
|||
initOptions(); |
|||
} |
|||
|
|||
private void initOptions() { |
|||
final ImmutableList.Builder<Integer> builder = ImmutableList.builder(); |
|||
if (isLoggedIn && media.getUser().getPk() == viewerId) { |
|||
builder.add(R.id.edit_caption); |
|||
} |
|||
options.postValue(builder.build()); |
|||
} |
|||
|
|||
public Media getMedia() { |
|||
return media; |
|||
} |
|||
|
|||
public boolean isLoggedIn() { |
|||
return isLoggedIn; |
|||
} |
|||
|
|||
public LiveData<User> getUser() { |
|||
return user; |
|||
} |
|||
|
|||
public LiveData<Caption> getCaption() { |
|||
return caption; |
|||
} |
|||
|
|||
public LiveData<Location> getLocation() { |
|||
return location; |
|||
} |
|||
|
|||
public LiveData<String> getDate() { |
|||
return date; |
|||
} |
|||
|
|||
public LiveData<Long> getLikeCount() { |
|||
return likeCount; |
|||
} |
|||
|
|||
public LiveData<Long> getCommentCount() { |
|||
return commentCount; |
|||
} |
|||
|
|||
public LiveData<MediaItemType> getType() { |
|||
return type; |
|||
} |
|||
|
|||
public LiveData<Boolean> getLiked() { |
|||
return liked; |
|||
} |
|||
|
|||
public LiveData<Boolean> getSaved() { |
|||
return saved; |
|||
} |
|||
|
|||
public LiveData<List<Integer>> getOptions() { |
|||
return options; |
|||
} |
|||
|
|||
@NonNull |
|||
public LiveData<Resource<Object>> toggleLike() { |
|||
if (media.hasLiked()) { |
|||
return unlike(); |
|||
} |
|||
return like(); |
|||
} |
|||
|
|||
public LiveData<Resource<Object>> like() { |
|||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
mediaService.like(media.getPk(), viewerId, csrfToken, getLikeUnlikeCallback(data)); |
|||
return data; |
|||
} |
|||
|
|||
public LiveData<Resource<Object>> unlike() { |
|||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
mediaService.unlike(media.getPk(), viewerId, csrfToken, getLikeUnlikeCallback(data)); |
|||
return data; |
|||
} |
|||
|
|||
@NonNull |
|||
private ServiceCallback<Boolean> getLikeUnlikeCallback(final MutableLiveData<Resource<Object>> data) { |
|||
return new ServiceCallback<Boolean>() { |
|||
@Override |
|||
public void onSuccess(final Boolean result) { |
|||
if (!result) { |
|||
data.postValue(Resource.error("", null)); |
|||
return; |
|||
} |
|||
data.postValue(Resource.success(true)); |
|||
final long currentLikesCount = media.getLikeCount(); |
|||
final long updatedCount; |
|||
if (!media.hasLiked()) { |
|||
updatedCount = currentLikesCount + 1; |
|||
media.setHasLiked(true); |
|||
} else { |
|||
updatedCount = currentLikesCount - 1; |
|||
media.setHasLiked(false); |
|||
} |
|||
media.setLikeCount(updatedCount); |
|||
likeCount.postValue(updatedCount); |
|||
liked.postValue(media.hasLiked()); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
data.postValue(Resource.error(t.getMessage(), null)); |
|||
Log.e(TAG, "Error during like/unlike", t); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
@NonNull |
|||
public LiveData<Resource<Object>> toggleSave() { |
|||
if (!media.hasViewerSaved()) { |
|||
return save(); |
|||
} |
|||
return unsave(); |
|||
} |
|||
|
|||
public LiveData<Resource<Object>> save() { |
|||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
mediaService.save(media.getPk(), viewerId, csrfToken, getSaveUnsaveCallback(data)); |
|||
return data; |
|||
} |
|||
|
|||
public LiveData<Resource<Object>> unsave() { |
|||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
mediaService.unsave(media.getPk(), viewerId, csrfToken, getSaveUnsaveCallback(data)); |
|||
return data; |
|||
} |
|||
|
|||
@NonNull |
|||
private ServiceCallback<Boolean> getSaveUnsaveCallback(final MutableLiveData<Resource<Object>> data) { |
|||
return new ServiceCallback<Boolean>() { |
|||
@Override |
|||
public void onSuccess(final Boolean result) { |
|||
if (!result) { |
|||
data.postValue(Resource.error("", null)); |
|||
return; |
|||
} |
|||
data.postValue(Resource.success(true)); |
|||
media.setHasViewerSaved(!media.hasViewerSaved()); |
|||
saved.postValue(media.hasViewerSaved()); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
data.postValue(Resource.error(t.getMessage(), null)); |
|||
Log.e(TAG, "Error during save/unsave", t); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public LiveData<Resource<Object>> updateCaption(final String caption) { |
|||
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
mediaService.editCaption(media.getPk(), viewerId, caption, csrfToken, new ServiceCallback<Boolean>() { |
|||
@Override |
|||
public void onSuccess(final Boolean result) { |
|||
if (result) { |
|||
data.postValue(Resource.success("")); |
|||
media.setPostCaption(caption); |
|||
PostViewV2ViewModel.this.caption.postValue(media.getCaption()); |
|||
return; |
|||
} |
|||
data.postValue(Resource.error("", null)); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
Log.e(TAG, "Error editing caption", t); |
|||
data.postValue(Resource.error(t.getMessage(), null)); |
|||
} |
|||
}); |
|||
return data; |
|||
} |
|||
|
|||
public LiveData<Resource<String>> translateCaption() { |
|||
final MutableLiveData<Resource<String>> data = new MutableLiveData<>(); |
|||
data.postValue(Resource.loading(null)); |
|||
final Caption value = caption.getValue(); |
|||
if (value == null) return data; |
|||
mediaService.translate(String.valueOf(value.getPk()), "1", new ServiceCallback<String>() { |
|||
@Override |
|||
public void onSuccess(final String result) { |
|||
if (TextUtils.isEmpty(result)) { |
|||
data.postValue(Resource.error("", null)); |
|||
return; |
|||
} |
|||
data.postValue(Resource.success(result)); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(final Throwable t) { |
|||
Log.e(TAG, "Error translating comment", t); |
|||
data.postValue(Resource.error(t.getMessage(), null)); |
|||
} |
|||
}); |
|||
return data; |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> |
|||
</vector> |
@ -0,0 +1,10 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="24dp" |
|||
android:height="24dp" |
|||
android:viewportWidth="24" |
|||
android:viewportHeight="24" |
|||
android:tint="?attr/colorControlNormal"> |
|||
<path |
|||
android:fillColor="@android:color/white" |
|||
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,4.17 4.42,9.92 6.24,12.11 0.4,0.48 1.13,0.48 1.53,0C14.58,18.92 19,13.17 19,9c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/> |
|||
</vector> |
@ -1,5 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:shape="oval"> |
|||
<solid android:color="@color/white" /> |
|||
<solid android:color="@color/black" /> |
|||
</shape> |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<item |
|||
android:id="@+id/edit_caption" |
|||
android:title="@string/edit_caption" /> |
|||
</menu> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue