add suggested users feature

This commit is contained in:
Austin Huang 2020-12-30 13:26:16 -05:00
parent 3242fdc7ef
commit 975654961f
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
14 changed files with 176 additions and 24 deletions

View File

@ -53,18 +53,22 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
case RESPONDED_STORY:
subtext = model.getText();
break;
case AYML:
subtext = model.getPostId();
break;
}
binding.tvSubComment.setText(model.getType() == NotificationType.AYML ? model.getText() : subtext);
if (text == -1 && subtext != null) {
binding.tvComment.setText(subtext);
binding.tvSubComment.setVisibility(View.GONE);
binding.tvComment.setVisibility(TextUtils.isEmpty(subtext) ? View.GONE : View.VISIBLE);
binding.tvSubComment.setVisibility(model.getType() == NotificationType.AYML ? View.VISIBLE : View.GONE);
}
else if (text != -1) {
binding.tvComment.setText(text);
binding.tvSubComment.setText(subtext);
binding.tvSubComment.setVisibility(subtext == null ? View.GONE : View.VISIBLE);
}
if (model.getType() != NotificationType.REQUEST) {
if (model.getType() != NotificationType.REQUEST && model.getType() != NotificationType.AYML) {
binding.tvDate.setText(model.getDateTime());
}

View File

@ -26,6 +26,8 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.NotificationsAdapter;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
@ -46,6 +48,7 @@ import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.NotificationViewModel;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.NewsService;
import awais.instagrabber.webservices.ServiceCallback;
import static awais.instagrabber.utils.Utils.settingsHelper;
@ -59,8 +62,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
private NotificationViewModel notificationViewModel;
private FriendshipService friendshipService;
private MediaService mediaService;
private String userId;
private String csrfToken;
private NewsService newsService;
private String userId, csrfToken, type;
private Context context;
private final OnNotificationClickListener clickListener = new OnNotificationClickListener() {
@ -96,7 +99,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
public void onNotificationClick(final NotificationModel model) {
if (model == null) return;
final String username = model.getUsername();
if (model.getType() == NotificationType.FOLLOW) {
if (model.getType() == NotificationType.FOLLOW || model.getType() == NotificationType.AYML) {
openProfile(username);
}
else {
@ -242,6 +245,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
}
private void init() {
final NotificationsViewerFragmentArgs fragmentArgs = NotificationsViewerFragmentArgs.fromBundle(getArguments());
type = fragmentArgs.getType();
final Context context = getContext();
CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE));
binding.swipeRefreshLayout.setOnRefreshListener(this);
@ -256,10 +261,30 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
@Override
public void onRefresh() {
binding.swipeRefreshLayout.setRefreshing(true);
new NotificationsFetcher(true, notificationModels -> {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
switch (type) {
case "notif":
new NotificationsFetcher(true, notificationModels -> {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
case "ayml":
newsService = NewsService.getInstance();
newsService.fetchSuggestions(csrfToken, new ServiceCallback<List<NotificationModel>>() {
@Override
public void onSuccess(final List<NotificationModel> notificationModels) {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
break;
}
}
private void openProfile(final String username) {

View File

@ -134,7 +134,13 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
screen.addPreference(getDivider(context));
if (isLoggedIn) {
screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment);
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif");
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml");
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> {

View File

@ -15,8 +15,10 @@ public enum NotificationType implements Serializable {
COMMENT_LIKE("13"),
TAGGED_COMMENT("14"),
RESPONDED_STORY("213"),
// efr
REQUEST("REQUEST");
// efr - random value
REQUEST("REQUEST"),
// ayml - random value
AYML("AYML");
private final String itemType;
private static final Map<String, NotificationType> map = new HashMap<>();

View File

@ -21,4 +21,9 @@ public interface NewsRepository {
@Headers("User-Agent: " + Constants.I_USER_AGENT)
@GET("/api/v1/news/inbox/")
Call<String> appInbox(@Query(value = "mark_as_seen", encoded = true) boolean markAsSeen);
@FormUrlEncoded
@Headers("User-Agent: " + Constants.I_USER_AGENT)
@POST("/api/v1/discover/ayml/")
Call<String> getAyml(@FieldMap final Map<String, String> form);
}

View File

@ -12,14 +12,15 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.repositories.NewsRepository;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NetworkUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@ -89,7 +90,6 @@ public class NewsService extends BaseService {
public void fetchWebInbox(final boolean markAsSeen,
final ServiceCallback<List<NotificationModel>> callback) {
final List<NotificationModel> result = new ArrayList<>();
final Call<String> request = repository.webInbox();
request.enqueue(new Callback<String>() {
@Override
@ -100,6 +100,7 @@ public class NewsService extends BaseService {
return;
}
try {
final List<NotificationModel> result = new ArrayList<>();
final JSONObject page = new JSONObject(body)
.getJSONObject("graphql")
.getJSONObject("user");
@ -168,7 +169,7 @@ public class NewsService extends BaseService {
final String type = itemJson.getString("story_type");
final NotificationType notificationType = NotificationType.valueOfType(type);
if (notificationType == null) {
Log.d("austin_debug", "unhandled news type: "+itemJson);
if (BuildConfig.DEBUG) Log.d("austin_debug", "unhandled news type: "+itemJson);
return null;
}
final JSONObject data = itemJson.getJSONObject("args");
@ -194,4 +195,67 @@ public class NewsService extends BaseService {
}
return result;
}
public void fetchSuggestions(final String csrfToken,
final ServiceCallback<List<NotificationModel>> callback) {
final Map<String, String> form = new HashMap<>();
form.put("_uuid", UUID.randomUUID().toString());
form.put("_csrftoken", csrfToken);
form.put("phone_id", UUID.randomUUID().toString());
form.put("device_id", UUID.randomUUID().toString());
form.put("module", "discover_people");
form.put("paginate", "false");
final Call<String> request = repository.getAyml(form);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
try {
final List<NotificationModel> result = new ArrayList<>();
final JSONObject jsonObject = new JSONObject(body);
final JSONArray oldStories = jsonObject.getJSONObject("suggested_users").getJSONArray("suggestions"),
newStories = jsonObject.getJSONObject("new_suggested_users").getJSONArray("suggestions");
for (int j = 0; j < newStories.length(); ++j) {
final NotificationModel newsItem = parseAymlItem(newStories.getJSONObject(j));
if (newsItem != null) result.add(newsItem);
}
for (int i = 0; i < oldStories.length(); ++i) {
final NotificationModel newsItem = parseAymlItem(oldStories.getJSONObject(i));
if (newsItem != null) result.add(newsItem);
}
callback.onSuccess(result);
} catch (JSONException e) {
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
callback.onFailure(t);
// Log.e(TAG, "onFailure: ", t);
}
});
}
private NotificationModel parseAymlItem(final JSONObject itemJson) throws JSONException {
if (itemJson == null) return null;
final JSONObject data = itemJson.getJSONObject("user");
return new NotificationModel(
itemJson.getString("uuid"),
itemJson.getString("social_context"),
0L,
data.getString("pk"),
data.getString("username"),
data.getString("profile_pic_url"),
data.getString("full_name"), // just borrowing this field
null,
NotificationType.AYML);
}
}

View File

@ -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="M11,14H9c0,-4.97 4.03,-9 9,-9v2C14.13,7 11,10.13 11,14zM18,11V9c-2.76,0 -5,2.24 -5,5h2C15,12.34 16.34,11 18,11zM7,4c0,-1.11 -0.89,-2 -2,-2S3,2.89 3,4s0.89,2 2,2S7,5.11 7,4zM11.45,4.5h-2C9.21,5.92 7.99,7 6.5,7h-3C2.67,7 2,7.67 2,8.5V11h6V8.74C9.86,8.15 11.25,6.51 11.45,4.5zM19,17c1.11,0 2,-0.89 2,-2s-0.89,-2 -2,-2s-2,0.89 -2,2S17.89,17 19,17zM20.5,18h-3c-1.49,0 -2.71,-1.08 -2.95,-2.5h-2c0.2,2.01 1.59,3.65 3.45,4.24V22h6v-2.5C22,18.67 21.33,18 20.5,18z"/>
</vector>

View File

@ -42,7 +42,12 @@
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph" />
app:destination="@id/notification_viewer_nav_graph">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<include app:graph="@navigation/comments_nav_graph" />

View File

@ -76,7 +76,12 @@
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph" />
app:destination="@id/notification_viewer_nav_graph">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<fragment
android:id="@+id/discoverFragment"

View File

@ -76,7 +76,12 @@
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph" />
app:destination="@id/notification_viewer_nav_graph">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<include app:graph="@navigation/story_list_nav_graph" />

View File

@ -50,7 +50,12 @@
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph" />
app:destination="@id/notification_viewer_nav_graph">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<fragment
android:id="@+id/morePreferencesFragment"

View File

@ -9,11 +9,21 @@
android:id="@+id/notificationsViewer"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer" />
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</fragment>
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notificationsViewer" />
app:destination="@id/notificationsViewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<include app:graph="@navigation/comments_nav_graph" />

View File

@ -77,7 +77,12 @@
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph" />
app:destination="@id/notification_viewer_nav_graph">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
</action>
<fragment
android:id="@+id/profileFragment"

View File

@ -232,7 +232,8 @@
<string name="crash_descr">Oops.. the app crashed, but don\'t worry you can send error report to the developer to help him fix the issue. (:</string>
<string name="action_notif">Activity</string>
<string name="action_archive">Story archive</string>
<string name="license" translatable="false">Copyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.</string>
<string name="action_ayml">Suggested users</string>
<string name="license" translatable="false">Copyright (C) 2019-2020 AWAiS\nCopyright (C) 2020-2021 Austin Huang &amp; Ammar Githam\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.</string>
<string name="liability" translatable="false">This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</string>
<string name="select_picture">Select Picture</string>
<string name="uploading">Uploading…</string>