revamp notifications; close #796

This commit is contained in:
Austin Huang 2021-03-16 17:57:57 -04:00
parent 22a927e9e7
commit d65fbd4193
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
19 changed files with 91 additions and 228 deletions

View File

@ -709,7 +709,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
if (currentNavControllerLiveData == null) return;
final NavController navController = currentNavControllerLiveData.getValue();
if (navController == null) return;
navController.navigate(R.id.action_global_notificationsViewerFragment);
final Bundle bundle = new Bundle();
bundle.putString("type", "notif");
navController.navigate(R.id.action_global_notificationsViewerFragment, bundle);
}
private void bindActivityCheckerService() {

View File

@ -30,14 +30,10 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
case LIKE:
text = R.string.liked_notif;
break;
case COMMENT:
case COMMENT: // untested
text = R.string.comment_notif;
subtext = args.getText();
break;
case COMMENT_MENTION:
text = R.string.mention_notif;
subtext = args.getText();
break;
case TAGGED:
text = R.string.tagged_notif;
break;
@ -46,8 +42,8 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
break;
case REQUEST:
text = R.string.request_notif;
subtext = args.getText();
break;
case COMMENT_MENTION:
case COMMENT_LIKE:
case TAGGED_COMMENT:
case RESPONDED_STORY:
@ -70,7 +66,7 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
}
binding.tvDate.setVisibility(model.getType() == NotificationType.AYML ? View.GONE : View.VISIBLE);
if (model.getType() != NotificationType.REQUEST && model.getType() != NotificationType.AYML) {
if (model.getType() != NotificationType.AYML) {
binding.tvDate.setText(args.getDateTime());
}

View File

@ -1,66 +0,0 @@
package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Notification;
import awais.instagrabber.webservices.NewsService;
import awais.instagrabber.webservices.ServiceCallback;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notification>> {
private static final String TAG = "NotificationsFetcher";
private final FetchListener<List<Notification>> fetchListener;
private final NewsService newsService;
private final boolean markAsSeen;
private boolean fetchedWeb = false;
public NotificationsFetcher(final boolean markAsSeen,
final FetchListener<List<Notification>> fetchListener) {
this.markAsSeen = markAsSeen;
this.fetchListener = fetchListener;
newsService = NewsService.getInstance();
}
@Override
protected List<Notification> doInBackground(final Void... voids) {
List<Notification> notificationModels = new ArrayList<>();
newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<Notification>>() {
@Override
public void onSuccess(final List<Notification> result) {
if (result == null) return;
notificationModels.addAll(result);
if (fetchedWeb) {
fetchListener.onResult(notificationModels);
}
else {
fetchedWeb = true;
newsService.fetchWebInbox(this);
}
}
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
return notificationModels;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
}

View File

@ -31,10 +31,8 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.NotificationsAdapter;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
import awais.instagrabber.asyncs.NotificationsFetcher;
import awais.instagrabber.databinding.FragmentNotificationsViewerBinding;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.FriendshipChangeResponse;
@ -260,22 +258,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
switch (type) {
case "notif":
if (actionBar != null) actionBar.setTitle(R.string.action_notif);
new NotificationsFetcher(true, new FetchListener<List<Notification>>() {
@Override
public void onResult(final List<Notification> notificationModels) {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}
@Override
public void onFailure(Throwable t) {
try {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(Throwable e) {}
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
newsService.fetchAppInbox(true, cb);
break;
case "ayml":
if (actionBar != null) actionBar.setTitle(R.string.action_ayml);

View File

@ -470,8 +470,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
if (item.getItemId() == R.id.chaining) {
if (!isLoggedIn) return false;
final NavDirections navDirections = ProfileFragmentDirections.actionGlobalNotificationsViewerFragment("chaining", profileModel.getPk());
NavHostFragment.findNavController(this).navigate(navDirections);
final Bundle bundle = new Bundle();
bundle.putString("type", "chaining");
bundle.putLong("targetId", profileModel.getPk());
NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment, bundle);
return true;
}
if (item.getItemId() == R.id.mute_stories) {

View File

@ -137,14 +137,14 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
if (isLoggedIn) {
screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif", 0L);
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif");
navController.navigate(navDirections);
}
return true;
}));
screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml", 0L);
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml");
navController.navigate(navDirections);
}
return true;

View File

@ -5,23 +5,21 @@ import java.util.HashMap;
import java.util.Map;
public enum NotificationType implements Serializable {
// web
LIKE("GraphLikeAggregatedStory"),
FOLLOW("GraphFollowAggregatedStory"),
COMMENT("GraphCommentMediaStory"),
COMMENT_MENTION("GraphMentionStory"),
TAGGED("GraphUserTaggedStory"),
// app story_type
COMMENT_LIKE("13"),
TAGGED_COMMENT("14"),
RESPONDED_STORY("213"),
// efr - random value
REQUEST("REQUEST"),
// ayml - random value
AYML("AYML");
// story_type
LIKE(60),
FOLLOW(101),
COMMENT(12), // NOT TESTED
COMMENT_MENTION(66),
TAGGED(102), // NOT TESTED
COMMENT_LIKE(13),
TAGGED_COMMENT(14),
RESPONDED_STORY(213),
REQUEST(75),
// aymf - arbitrary, misspelled as ayml but eh
AYML(9999);
private final String itemType;
private static final Map<String, NotificationType> map = new HashMap<>();
private final int itemType;
private static final Map<Integer, NotificationType> map = new HashMap<>();
static {
for (NotificationType type : NotificationType.values()) {
@ -29,15 +27,15 @@ public enum NotificationType implements Serializable {
}
}
NotificationType(final String itemType) {
NotificationType(final int itemType) {
this.itemType = itemType;
}
public String getItemType() {
public int getItemType() {
return itemType;
}
public static NotificationType valueOfType(final String itemType) {
public static NotificationType valueOfType(final int itemType) {
return map.get(itemType);
}
}

View File

@ -14,17 +14,14 @@ import retrofit2.http.POST;
import retrofit2.http.Query;
public interface NewsRepository {
@GET("https://www.instagram.com/accounts/activity/?__a=1")
Call<String> webInbox(@Header("User-Agent") String userAgent);
@GET("/api/v1/news/inbox/")
Call<NewsInboxResponse> appInbox(@Header("User-Agent") String userAgent, @Query(value = "mark_as_seen", encoded = true) boolean markAsSeen);
Call<NewsInboxResponse> appInbox(@Query(value = "mark_as_seen", encoded = true) boolean markAsSeen,
@Header(value = "x-ig-app-id") String xIgAppId);
@FormUrlEncoded
@POST("/api/v1/discover/ayml/")
Call<AymlResponse> getAyml(@Header("User-Agent") String userAgent, @FieldMap final Map<String, String> form);
Call<AymlResponse> getAyml(@FieldMap final Map<String, String> form);
@GET("/api/v1/discover/chaining/")
Call<UserSearchResponse> getChaining(@Header("User-Agent") String userAgent, @Query(value = "target_id") long targetId);
Call<UserSearchResponse> getChaining(@Query(value = "target_id") long targetId);
}

View File

@ -4,11 +4,11 @@ import awais.instagrabber.models.enums.NotificationType;
public class Notification {
private final NotificationArgs args;
private final String storyType;
private final int storyType;
private final String pk;
public Notification(final NotificationArgs args,
final String storyType,
final int storyType,
final String pk) {
this.args = args;
this.storyType = storyType;

View File

@ -18,7 +18,10 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.repositories.responses.NotificationCounts;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.webservices.NewsService;
import awais.instagrabber.webservices.ServiceCallback;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -28,14 +31,13 @@ public class ActivityCheckerService extends Service {
private static final int DELAY_MILLIS = 60000;
private Handler handler;
// private OnTaskCompleteListener onTaskCompleteListener;
private NewsService newsService;
private ServiceCallback<NotificationCounts> cb;
private NotificationManagerCompat notificationManager;
private final IBinder binder = new LocalBinder();
private final Runnable runnable = () -> {
final String cookie = settingsHelper.getString(Constants.COOKIE);
// final GetActivityAsyncTask activityAsyncTask = new GetActivityAsyncTask(onTaskCompleteListener);
// activityAsyncTask.execute(cookie);
newsService.fetchActivityCounts(cb);
};
public class LocalBinder extends Binder {
@ -47,21 +49,25 @@ public class ActivityCheckerService extends Service {
@Override
public void onCreate() {
notificationManager = NotificationManagerCompat.from(getApplicationContext());
newsService = NewsService.getInstance();
handler = new Handler();
/*
onTaskCompleteListener = result -> {
// Log.d(TAG, "onTaskCompleteListener: result: " + result);
try {
if (result == null) return;
final String notification = getNotificationString(result);
if (notification == null) return;
final String notificationString = getString(R.string.activity_count_prefix) + " " + notification + ".";
showNotification(notificationString);
} finally {
handler.postDelayed(runnable, DELAY_MILLIS);
cb = new ServiceCallback<NotificationCounts>() {
@Override
public void onSuccess(final NotificationCounts result) {
try {
if (result == null) return;
final String notification = getNotificationString(result);
if (notification == null) return;
final String notificationString = getString(R.string.activity_count_prefix) + " " + notification + ".";
showNotification(notificationString);
} finally {
handler.postDelayed(runnable, DELAY_MILLIS);
}
}
@Override
public void onFailure(final Throwable t) {}
};
*/
}
@Override
@ -84,15 +90,20 @@ public class ActivityCheckerService extends Service {
handler.removeCallbacks(runnable);
}
/*
private String getNotificationString(final NotificationCounts result) {
final List<String> list = new ArrayList<>();
if (result.getRelationshipsCount() != 0) {
list.add(getString(R.string.activity_count_relationship, result.getRelationshipsCount()));
}
if (result.getRequestsCount() != 0) {
list.add(getString(R.string.activity_count_requests, result.getRequestsCount()));
}
if (result.getUserTagsCount() != 0) {
list.add(getString(R.string.activity_count_usertags, result.getUserTagsCount()));
}
if (result.getPOYCount() != 0) {
list.add(getString(R.string.activity_count_poy, result.getPOYCount()));
}
if (result.getCommentsCount() != 0) {
list.add(getString(R.string.activity_count_comments, result.getCommentsCount()));
}
@ -105,7 +116,6 @@ public class ActivityCheckerService extends Service {
if (list.isEmpty()) return null;
return TextUtils.join(", ", list);
}
*/
private void showNotification(final String notificationString) {
final Notification notification = new NotificationCompat.Builder(this, Constants.ACTIVITY_CHANNEL_ID)

View File

@ -113,4 +113,6 @@ public final class Constants {
public static final String DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id";
public static final String DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title";
public static final String X_IG_APP_ID = "936619743392459";
}

View File

@ -21,6 +21,7 @@ import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.repositories.NewsRepository;
import awais.instagrabber.repositories.responses.AymlResponse;
import awais.instagrabber.repositories.responses.AymlUser;
import awais.instagrabber.repositories.responses.NotificationCounts;
import awais.instagrabber.repositories.responses.UserSearchResponse;
import awais.instagrabber.repositories.responses.NewsInboxResponse;
import awais.instagrabber.repositories.responses.Notification;
@ -40,7 +41,6 @@ public class NewsService extends BaseService {
private final NewsRepository repository;
private static NewsService instance;
private static String browserUa, appUa;
private NewsService() {
final Retrofit retrofit = getRetrofitBuilder()
@ -53,14 +53,12 @@ public class NewsService extends BaseService {
if (instance == null) {
instance = new NewsService();
}
appUa = Utils.settingsHelper.getString(Constants.APP_UA);
browserUa = Utils.settingsHelper.getString(Constants.BROWSER_UA);
return instance;
}
public void fetchAppInbox(final boolean markAsSeen,
final ServiceCallback<List<Notification>> callback) {
final Call<NewsInboxResponse> request = repository.appInbox(appUa, markAsSeen);
final Call<NewsInboxResponse> request = repository.appInbox(markAsSeen, Constants.X_IG_APP_ID);
request.enqueue(new Callback<NewsInboxResponse>() {
@Override
public void onResponse(@NonNull final Call<NewsInboxResponse> call, @NonNull final Response<NewsInboxResponse> response) {
@ -83,91 +81,21 @@ public class NewsService extends BaseService {
});
}
public void fetchWebInbox(final ServiceCallback<List<Notification>> callback) {
final Call<String> request = repository.webInbox(browserUa);
request.enqueue(new Callback<String>() {
public void fetchActivityCounts(final ServiceCallback<NotificationCounts> callback) {
final Call<NewsInboxResponse> request = repository.appInbox(false, null);
request.enqueue(new Callback<NewsInboxResponse>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
public void onResponse(@NonNull final Call<NewsInboxResponse> call, @NonNull final Response<NewsInboxResponse> response) {
final NewsInboxResponse body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
try {
final List<Notification> result = new ArrayList<>();
final JSONObject page = new JSONObject(body)
.getJSONObject("graphql")
.getJSONObject("user");
final JSONObject ewaf = page.getJSONObject("activity_feed")
.optJSONObject("edge_web_activity_feed");
final JSONObject efr = page.optJSONObject("edge_follow_requests");
JSONObject data;
JSONArray media;
if (ewaf != null
&& (media = ewaf.optJSONArray("edges")) != null
&& media.length() > 0
&& media.optJSONObject(0).optJSONObject("node") != null) {
for (int i = 0; i < media.length(); ++i) {
data = media.optJSONObject(i).optJSONObject("node");
if (data == null) continue;
final String type = data.getString("__typename");
final NotificationType notificationType = NotificationType.valueOfType(type);
if (notificationType == null) continue;
final JSONObject user = data.getJSONObject("user");
result.add(new Notification(
new NotificationArgs(
data.optString("text"),
null,
user.getLong(Constants.EXTRAS_ID),
user.getString("profile_pic_url"),
data.isNull("media") ? null : Collections.singletonList(new NotificationImage(
data.getJSONObject("media").getString("id"),
data.getJSONObject("media").getString("thumbnail_src")
)),
data.getLong("timestamp"),
user.getString("username"),
null,
false
),
type,
data.getString(Constants.EXTRAS_ID)
));
}
}
if (efr != null
&& (media = efr.optJSONArray("edges")) != null
&& media.length() > 0
&& media.optJSONObject(0).optJSONObject("node") != null) {
for (int i = 0; i < media.length(); ++i) {
data = media.optJSONObject(i).optJSONObject("node");
if (data == null) continue;
result.add(new Notification(
new NotificationArgs(
null,
null,
data.getLong(Constants.EXTRAS_ID),
data.getString("profile_pic_url"),
null,
0L,
data.getString("username"),
data.optString("full_name"),
data.optBoolean("is_verified")
),
"REQUEST",
data.getString(Constants.EXTRAS_ID)
));
}
}
callback.onSuccess(result);
} catch (JSONException e) {
callback.onFailure(e);
}
callback.onSuccess(body.getCounts());
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
public void onFailure(@NonNull final Call<NewsInboxResponse> call, @NonNull final Throwable t) {
callback.onFailure(t);
// Log.e(TAG, "onFailure: ", t);
}
@ -184,7 +112,7 @@ public class NewsService extends BaseService {
form.put("device_id", UUID.randomUUID().toString());
form.put("module", "discover_people");
form.put("paginate", "false");
final Call<AymlResponse> request = repository.getAyml(appUa, form);
final Call<AymlResponse> request = repository.getAyml(form);
request.enqueue(new Callback<AymlResponse>() {
@Override
public void onResponse(@NonNull final Call<AymlResponse> call, @NonNull final Response<AymlResponse> response) {
@ -212,7 +140,7 @@ public class NewsService extends BaseService {
u.getFullName(),
u.isVerified()
),
"AYML",
9999,
i.getUuid()
);
})
@ -229,7 +157,7 @@ public class NewsService extends BaseService {
}
public void fetchChaining(final long targetId, final ServiceCallback<List<Notification>> callback) {
final Call<UserSearchResponse> request = repository.getChaining(appUa, targetId);
final Call<UserSearchResponse> request = repository.getChaining(targetId);
request.enqueue(new Callback<UserSearchResponse>() {
@Override
public void onResponse(@NonNull final Call<UserSearchResponse> call, @NonNull final Response<UserSearchResponse> response) {
@ -253,7 +181,7 @@ public class NewsService extends BaseService {
u.getFullName(),
u.isVerified()
),
"AYML",
9999,
u.getProfilePicId() // placeholder
);
})

View File

@ -43,6 +43,7 @@
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</action>

View File

@ -91,6 +91,7 @@
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</action>

View File

@ -91,6 +91,7 @@
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</action>

View File

@ -56,6 +56,7 @@
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</action>

View File

@ -16,6 +16,7 @@
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_notificationsViewerFragment_to_storyViewerFragment"
@ -29,6 +30,10 @@
android:name="type"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</action>
<include app:graph="@navigation/comments_nav_graph" />

View File

@ -82,6 +82,7 @@
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</action>

View File

@ -256,7 +256,6 @@
<string name="liked_notif">Liked your post</string>
<string name="comment_notif">Commented on your post:</string>
<string name="follow_notif">Started following you</string>
<string name="mention_notif">Mentioned you:</string>
<string name="tagged_notif">Tagged you in a post</string>
<string name="request_notif">Requested following you</string>
<string name="request_approve">Approve request</string>
@ -282,6 +281,8 @@
<string name="activity_count_commentlikes">%d comment likes</string>
<string name="activity_count_usertags">%d usertags</string>
<string name="activity_count_likes">%d likes</string>
<string name="activity_count_poy">%d photos of you</string>
<string name="activity_count_requests">%d follow requests</string>
<string name="activity_notloggedin">You logged out before clicking this notification?!</string>
<string name="feed">Feed</string>
<string name="profile">Profile</string>