diff --git a/app/build.gradle b/app/build.gradle
index 222f72ba..ec1da5b4 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -79,6 +79,7 @@ dependencies {
implementation "androidx.preference:preference:1.1.1"
implementation "androidx.work:work-runtime:2.5.0"
implementation 'androidx.palette:palette:1.0.0'
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation 'com.google.guava:guava:27.0.1-android'
@@ -89,7 +90,7 @@ dependencies {
annotationProcessor "androidx.room:room-compiler:$room_version"
// CameraX
- def camerax_version = "1.0.0-alpha02"
+ def camerax_version = "1.1.0-alpha02"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:1.0.0-alpha22"
diff --git a/app/schemas/awais.instagrabber.db.AppDatabase/5.json b/app/schemas/awais.instagrabber.db.AppDatabase/5.json
new file mode 100644
index 00000000..a60a7ea8
--- /dev/null
+++ b/app/schemas/awais.instagrabber.db.AppDatabase/5.json
@@ -0,0 +1,161 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "0b38e12b76bb081ec837191c5ef5b54e",
+ "entities": [
+ {
+ "tableName": "accounts",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cookie",
+ "columnName": "cookie",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profilePic",
+ "columnName": "profile_pic",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "favorites",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "query",
+ "columnName": "query_text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "display_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "picUrl",
+ "columnName": "pic_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "dateAdded",
+ "columnName": "date_added",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "dm_last_notified",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "threadId",
+ "columnName": "thread_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastNotifiedMsgTs",
+ "columnName": "last_notified_msg_ts",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastNotifiedAt",
+ "columnName": "last_notified_at",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_dm_last_notified_thread_id",
+ "unique": true,
+ "columnNames": [
+ "thread_id"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0b38e12b76bb081ec837191c5ef5b54e')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ba525c14..403ffd78 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,7 +13,6 @@
android:name="android.hardware.camera.any"
android:required="false" />
-
-
-
+
+
@@ -147,6 +146,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java
index 341fe21c..6abc076e 100644
--- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java
+++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java
@@ -38,6 +38,7 @@ import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
@@ -62,12 +63,15 @@ import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
+import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
import awais.instagrabber.fragments.main.FeedFragment;
+import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.services.ActivityCheckerService;
+import awais.instagrabber.services.DMSyncAlarmReceiver;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
@@ -159,6 +163,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
EmojiVariantManager.getInstance();
});
initEmojiCompat();
+ initDmService();
+ }
+
+ private void initDmService() {
+ if (!isLoggedIn) return;
+ final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
+ if (!enabled) return;
+ DMSyncAlarmReceiver.setAlarm(this);
}
@Override
@@ -247,15 +259,22 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
private void createNotificationChannels() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
- notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
- Constants.DOWNLOAD_CHANNEL_NAME,
- NotificationManager.IMPORTANCE_DEFAULT));
- notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
- Constants.ACTIVITY_CHANNEL_NAME,
- NotificationManager.IMPORTANCE_DEFAULT));
- }
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+ final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
+ notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
+ Constants.DOWNLOAD_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
+ Constants.ACTIVITY_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ notificationManager.createNotificationChannel(new NotificationChannel(Constants.DM_UNREAD_CHANNEL_ID,
+ Constants.DM_UNREAD_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ final NotificationChannel silentNotificationChannel = new NotificationChannel(Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
+ Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_LOW);
+ silentNotificationChannel.setSound(null, null);
+ notificationManager.createNotificationChannel(silentNotificationChannel);
}
private void setupSuggestions() {
@@ -521,6 +540,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
showActivityView();
return;
}
+ if (Constants.ACTION_SHOW_DM_THREAD.equals(action)) {
+ showThread(intent);
+ return;
+ }
if (Intent.ACTION_SEND.equals(action) && type != null) {
if (type.equals("text/plain")) {
handleUrl(intent.getStringExtra(Intent.EXTRA_TEXT));
@@ -534,6 +557,58 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
}
+ private void showThread(@NonNull final Intent intent) {
+ final String threadId = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID);
+ final String threadTitle = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE);
+ navigateToThread(threadId, threadTitle);
+ }
+
+ public void navigateToThread(final String threadId, final String threadTitle) {
+ if (threadId == null || threadTitle == null) return;
+ currentNavControllerLiveData.observe(this, new Observer() {
+ @Override
+ public void onChanged(final NavController navController) {
+ if (navController == null) return;
+ if (navController.getGraph().getId() != R.id.direct_messages_nav_graph) return;
+ try {
+ final NavDestination currentDestination = navController.getCurrentDestination();
+ if (currentDestination != null && currentDestination.getId() == R.id.directMessagesInboxFragment) {
+ // if we are already on the inbox page, navigate to the thread
+ // need handler.post() to wait for the fragment manager to be ready to navigate
+ new Handler().post(() -> {
+ final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
+ .actionInboxToThread(threadId, threadTitle);
+ navController.navigate(action);
+ });
+ return;
+ }
+ // add a destination change listener to navigate to thread once we are on the inbox page
+ navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
+ @Override
+ public void onDestinationChanged(@NonNull final NavController controller,
+ @NonNull final NavDestination destination,
+ @Nullable final Bundle arguments) {
+ if (destination.getId() == R.id.directMessagesInboxFragment) {
+ final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
+ .actionInboxToThread(threadId, threadTitle);
+ controller.navigate(action);
+ controller.removeOnDestinationChangedListener(this);
+ }
+ }
+ });
+ // pop back stack until we reach the inbox page
+ navController.popBackStack(R.id.directMessagesInboxFragment, false);
+ } finally {
+ currentNavControllerLiveData.removeObserver(this);
+ }
+ }
+ });
+ final int selectedItemId = binding.bottomNavView.getSelectedItemId();
+ if (selectedItemId != R.navigation.direct_messages_nav_graph) {
+ setBottomNavSelectedItem(R.navigation.direct_messages_nav_graph);
+ }
+ }
+
private void handleUrl(final String url) {
if (url == null) return;
// Log.d(TAG, url);
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
index df820e8b..41465b38 100644
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
@@ -12,26 +12,17 @@ import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
-import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
-import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
-import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
-import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
-import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary;
-import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.TextUtils;
public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
@@ -133,218 +124,17 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
if (directStory != null && !directStory.getItems().isEmpty()) {
final DirectItem item = directStory.getItems().get(0);
final MediaItemType mediaType = item.getVisualMedia().getMedia().getMediaType();
- final String username = getUsername(thread.getUsers(), item.getUserId(), viewerId, resources);
- final String subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
+ final String username = DMUtils.getUsername(thread.getUsers(), item.getUserId(), viewerId, resources);
+ final String subtitle = DMUtils.getMediaSpecificSubtitle(username, resources, mediaType);
binding.subtitle.setText(subtitle);
return;
}
final DirectItem item = thread.getFirstDirectItem();
if (item == null) return;
- final long senderId = item.getUserId();
- final DirectItemType itemType = item.getItemType();
- String subtitle = null;
- final String username = getUsername(thread.getUsers(), senderId, viewerId, resources);
- String message = "";
- if (itemType == null) {
- message = resources.getString(R.string.dms_inbox_raven_message_unknown);
- } else {
- switch (itemType) {
- case TEXT:
- message = item.getText();
- break;
- case LIKE:
- message = item.getLike();
- break;
- case LINK:
- message = item.getLink().getText();
- break;
- case PLACEHOLDER:
- message = item.getPlaceholder().getMessage();
- break;
- case MEDIA_SHARE:
- subtitle = resources.getString(R.string.dms_inbox_shared_post, username != null ? username : "", item.getMediaShare().getUser().getUsername());
- break;
- case ANIMATED_MEDIA:
- subtitle = resources.getString(R.string.dms_inbox_shared_gif, username != null ? username : "");
- break;
- case PROFILE:
- subtitle = resources.getString(R.string.dms_inbox_shared_profile, username != null ? username : "", item.getProfile().getUsername());
- break;
- case LOCATION:
- subtitle = resources.getString(R.string.dms_inbox_shared_location, username != null ? username : "", item.getLocation().getName());
- break;
- case MEDIA: {
- final MediaItemType mediaType = item.getMedia().getMediaType();
- subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
- break;
- }
- case STORY_SHARE: {
- final String reelType = item.getStoryShare().getReelType();
- if (reelType == null) {
- subtitle = item.getStoryShare().getTitle();
- } else {
- final int format = reelType.equals("highlight_reel")
- ? R.string.dms_inbox_shared_highlight
- : R.string.dms_inbox_shared_story;
- subtitle = resources.getString(format, username != null ? username : "",
- item.getStoryShare().getMedia().getUser().getUsername());
- }
- break;
- }
- case VOICE_MEDIA:
- subtitle = resources.getString(R.string.dms_inbox_shared_voice, username != null ? username : "");
- break;
- case ACTION_LOG:
- subtitle = item.getActionLog().getDescription();
- break;
- case VIDEO_CALL_EVENT:
- subtitle = item.getVideoCallEvent().getDescription();
- break;
- case CLIP:
- subtitle = resources.getString(R.string.dms_inbox_shared_clip, username != null ? username : "",
- item.getClip().getClip().getUser().getUsername());
- break;
- case FELIX_SHARE:
- subtitle = resources.getString(R.string.dms_inbox_shared_igtv, username != null ? username : "",
- item.getFelixShare().getVideo().getUser().getUsername());
- break;
- case RAVEN_MEDIA:
- subtitle = getRavenMediaSubtitle(item, resources, username);
- break;
- case REEL_SHARE:
- final DirectItemReelShare reelShare = item.getReelShare();
- if (reelShare == null) {
- subtitle = "";
- break;
- }
- final String reelType = reelShare.getType();
- switch (reelType) {
- case "reply":
- if (viewerId == item.getUserId()) {
- subtitle = resources.getString(R.string.dms_inbox_replied_story_outgoing, reelShare.getText());
- } else {
- subtitle = resources.getString(R.string.dms_inbox_replied_story_incoming, username != null ? username : "", reelShare.getText());
- }
- break;
- case "mention":
- if (viewerId == item.getUserId()) {
- // You mentioned the other person
- final long mentionedUserId = item.getReelShare().getMentionedUserId();
- final String otherUsername = getUsername(thread.getUsers(), mentionedUserId, viewerId, resources);
- subtitle = resources.getString(R.string.dms_inbox_mentioned_story_outgoing, otherUsername);
- } else {
- // They mentioned you
- subtitle = resources.getString(R.string.dms_inbox_mentioned_story_incoming, username != null ? username : "");
- }
- break;
- case "reaction":
- if (viewerId == item.getUserId()) {
- subtitle = resources.getString(R.string.dms_inbox_reacted_story_outgoing, reelShare.getText());
- } else {
- subtitle = resources.getString(R.string.dms_inbox_reacted_story_incoming, username != null ? username : "", reelShare.getText());
- }
- break;
- default:
- subtitle = "";
- break;
- }
- break;
- default:
- message = resources.getString(R.string.dms_inbox_raven_message_unknown);
- }
- }
- if (subtitle == null) {
- if (thread.getUsers().size() > 1
- || (thread.getUsers().size() == 1 && senderId == viewerId)) {
- subtitle = String.format("%s: %s", username != null ? username : "", message);
- } else {
- subtitle = message;
- }
- }
+ final String subtitle = DMUtils.getMessageString(thread, resources, viewerId, item);
binding.subtitle.setText(subtitle != null ? subtitle : "");
}
- private String getMediaSpecificSubtitle(final String username, final Resources resources, final MediaItemType mediaType) {
- final String userSharedAnImage = resources.getString(R.string.dms_inbox_shared_image, username != null ? username : "");
- final String userSharedAVideo = resources.getString(R.string.dms_inbox_shared_video, username != null ? username : "");
- final String userSentAMessage = resources.getString(R.string.dms_inbox_shared_message, username != null ? username : "");
- String subtitle;
- switch (mediaType) {
- case MEDIA_TYPE_IMAGE:
- subtitle = userSharedAnImage;
- break;
- case MEDIA_TYPE_VIDEO:
- subtitle = userSharedAVideo;
- break;
- default:
- subtitle = userSentAMessage;
- break;
- }
- return subtitle;
- }
-
- private String getRavenMediaSubtitle(final DirectItem item,
- final Resources resources,
- final String username) {
- String subtitle = "↗ ";
- final DirectItemVisualMedia visualMedia = item.getVisualMedia();
- final RavenExpiringMediaActionSummary summary = visualMedia.getExpiringMediaActionSummary();
- if (summary != null) {
- final RavenExpiringMediaActionSummary.ActionType expiringMediaType = summary.getType();
- int textRes = 0;
- switch (expiringMediaType) {
- case DELIVERED:
- textRes = R.string.dms_inbox_raven_media_delivered;
- break;
- case SENT:
- textRes = R.string.dms_inbox_raven_media_sent;
- break;
- case OPENED:
- textRes = R.string.dms_inbox_raven_media_opened;
- break;
- case REPLAYED:
- textRes = R.string.dms_inbox_raven_media_replayed;
- break;
- case SENDING:
- textRes = R.string.dms_inbox_raven_media_sending;
- break;
- case BLOCKED:
- textRes = R.string.dms_inbox_raven_media_blocked;
- break;
- case SUGGESTED:
- textRes = R.string.dms_inbox_raven_media_suggested;
- break;
- case SCREENSHOT:
- textRes = R.string.dms_inbox_raven_media_screenshot;
- break;
- case CANNOT_DELIVER:
- textRes = R.string.dms_inbox_raven_media_cant_deliver;
- break;
- }
- if (textRes > 0) {
- subtitle += itemView.getContext().getString(textRes);
- }
- return subtitle;
- }
- final MediaItemType mediaType = visualMedia.getMedia().getMediaType();
- subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
- return subtitle;
- }
-
- private String getUsername(final List users,
- final long userId,
- final long viewerId,
- final Resources resources) {
- if (userId == viewerId) {
- return resources.getString(R.string.you);
- }
- final Optional senderOptional = users.stream()
- .filter(Objects::nonNull)
- .filter(user -> user.getPk() == userId)
- .findFirst();
- return senderOptional.map(User::getUsername).orElse(null);
- }
-
private void setDateTime(@NonNull final DirectItem item) {
final long timestamp = item.getTimestamp() / 1000;
final String dateTimeString = TextUtils.getRelativeDateTimeString(itemView.getContext(), timestamp);
@@ -352,19 +142,7 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
}
private void setReadState(@NonNull final DirectThread thread) {
- final boolean read;
- if (thread.getDirectStory() != null) {
- read = false;
- } else {
- final DirectItem item = thread.getFirstDirectItem();
- if (item.getUserId() == thread.getViewerId()) {
- // if last item was sent by user, then it is read (even though we have auto read unchecked?)
- read = true;
- } else {
- final Map lastSeenAtMap = thread.getLastSeenAt();
- read = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()));
- }
- }
+ final boolean read = DMUtils.isRead(thread);
binding.unread.setVisibility(read ? View.GONE : View.VISIBLE);
binding.threadTitle.setTypeface(binding.threadTitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);
binding.subtitle.setTypeface(binding.subtitle.getTypeface(), read ? Typeface.NORMAL : Typeface.BOLD);
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
index d194d718..8271c38c 100644
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
@@ -45,6 +45,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiR
import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.DeepLinkParser;
import awais.instagrabber.utils.ResponseBodyUtils;
@@ -196,9 +197,9 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
if (item.isPending()) {
binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
} else {
- final boolean read = ResponseBodyUtils.isRead(item,
- thread.getLastSeenAt(),
- userIds
+ final boolean read = DMUtils.isRead(item,
+ thread.getLastSeenAt(),
+ userIds
);
binding.deliveryStatus.setImageResource(R.drawable.ic_check_all_24);
ImageViewCompat.setImageTintList(
@@ -324,8 +325,8 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
final String repliedToUsername = user != null ? user.getUsername() : "";
if (item.getUserId() == currentUser.getPk()) {
return thread.isGroup()
- ? resources.getString(R.string.replied_you_group, repliedToUsername)
- : resources.getString(R.string.replied_you);
+ ? resources.getString(R.string.replied_you_group, repliedToUsername)
+ : resources.getString(R.string.replied_you);
}
if (repliedToUserId == currentUser.getPk()) {
return resources.getString(R.string.replied_to_you);
diff --git a/app/src/main/java/awais/instagrabber/db/AppDatabase.java b/app/src/main/java/awais/instagrabber/db/AppDatabase.java
index 11547601..63e2ce43 100644
--- a/app/src/main/java/awais/instagrabber/db/AppDatabase.java
+++ b/app/src/main/java/awais/instagrabber/db/AppDatabase.java
@@ -20,14 +20,16 @@ import java.util.Date;
import java.util.List;
import awais.instagrabber.db.dao.AccountDao;
+import awais.instagrabber.db.dao.DMLastNotifiedDao;
import awais.instagrabber.db.dao.FavoriteDao;
import awais.instagrabber.db.entities.Account;
+import awais.instagrabber.db.entities.DMLastNotified;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Utils;
-@Database(entities = {Account.class, Favorite.class},
- version = 4)
+@Database(entities = {Account.class, Favorite.class, DMLastNotified.class},
+ version = 5)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = AppDatabase.class.getSimpleName();
@@ -38,12 +40,14 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract FavoriteDao favoriteDao();
+ public abstract DMLastNotifiedDao dmLastNotifiedDao();
+
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
- .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
+ .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.build();
}
}
@@ -140,6 +144,18 @@ public abstract class AppDatabase extends RoomDatabase {
}
};
+ static final Migration MIGRATION_4_5 = new Migration(4, 5) {
+ @Override
+ public void migrate(@NonNull final SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE IF NOT EXISTS `dm_last_notified` (" +
+ "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+ "`thread_id` TEXT, " +
+ "`last_notified_msg_ts` INTEGER, " +
+ "`last_notified_at` INTEGER)");
+ database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `dm_last_notified` (`thread_id`)");
+ }
+ };
+
@NonNull
private static List backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
// check if old favorites table had the column query_display
diff --git a/app/src/main/java/awais/instagrabber/db/Converters.java b/app/src/main/java/awais/instagrabber/db/Converters.java
index 50252755..de10e3fc 100644
--- a/app/src/main/java/awais/instagrabber/db/Converters.java
+++ b/app/src/main/java/awais/instagrabber/db/Converters.java
@@ -2,6 +2,10 @@ package awais.instagrabber.db;
import androidx.room.TypeConverter;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.Date;
import awais.instagrabber.models.enums.FavoriteType;
@@ -30,4 +34,16 @@ public class Converters {
public static String favoriteTypeToString(FavoriteType favoriteType) {
return favoriteType == null ? null : favoriteType.toString();
}
+
+ @TypeConverter
+ public static LocalDateTime fromTimestampToLocalDateTime(Long value) {
+ if (value == null) return null;
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.systemDefault());
+ }
+
+ @TypeConverter
+ public static Long localDateTimeToTimestamp(LocalDateTime localDateTime) {
+ if (localDateTime == null) return null;
+ return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ }
}
diff --git a/app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java b/app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java
new file mode 100644
index 00000000..ddd6968c
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java
@@ -0,0 +1,34 @@
+package awais.instagrabber.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import java.util.List;
+
+import awais.instagrabber.db.entities.DMLastNotified;
+
+@Dao
+public interface DMLastNotifiedDao {
+
+ @Query("SELECT * FROM dm_last_notified")
+ List getAllDMDmLastNotified();
+
+ @Query("SELECT * FROM dm_last_notified WHERE thread_id = :threadId")
+ DMLastNotified findDMLastNotifiedByThreadId(String threadId);
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ List insertDMLastNotified(DMLastNotified... dmLastNotified);
+
+ @Update
+ void updateDMLastNotified(DMLastNotified... dmLastNotified);
+
+ @Delete
+ void deleteDMLastNotified(DMLastNotified... dmLastNotified);
+
+ @Query("DELETE from dm_last_notified")
+ void deleteAllDMLastNotified();
+}
diff --git a/app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java
new file mode 100644
index 00000000..70a9a171
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java
@@ -0,0 +1,70 @@
+package awais.instagrabber.db.datasources;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import awais.instagrabber.db.AppDatabase;
+import awais.instagrabber.db.dao.DMLastNotifiedDao;
+import awais.instagrabber.db.entities.DMLastNotified;
+
+public class DMLastNotifiedDataSource {
+ private static final String TAG = DMLastNotifiedDataSource.class.getSimpleName();
+
+ private static DMLastNotifiedDataSource INSTANCE;
+
+ private final DMLastNotifiedDao dmLastNotifiedDao;
+
+ private DMLastNotifiedDataSource(final DMLastNotifiedDao dmLastNotifiedDao) {
+ this.dmLastNotifiedDao = dmLastNotifiedDao;
+ }
+
+ public static DMLastNotifiedDataSource getInstance(@NonNull Context context) {
+ if (INSTANCE == null) {
+ synchronized (DMLastNotifiedDataSource.class) {
+ if (INSTANCE == null) {
+ final AppDatabase database = AppDatabase.getDatabase(context);
+ INSTANCE = new DMLastNotifiedDataSource(database.dmLastNotifiedDao());
+ }
+ }
+ }
+ return INSTANCE;
+ }
+
+ @Nullable
+ public final DMLastNotified getDMLastNotified(final String threadId) {
+ return dmLastNotifiedDao.findDMLastNotifiedByThreadId(threadId);
+ }
+
+ @NonNull
+ public final List getAllDMDmLastNotified() {
+ return dmLastNotifiedDao.getAllDMDmLastNotified();
+ }
+
+ public final void insertOrUpdateDMLastNotified(final String threadId,
+ final LocalDateTime lastNotifiedMsgTs,
+ final LocalDateTime lastNotifiedAt) {
+ final DMLastNotified dmLastNotified = getDMLastNotified(threadId);
+ final DMLastNotified toUpdate = new DMLastNotified(dmLastNotified == null ? 0 : dmLastNotified.getId(),
+ threadId,
+ lastNotifiedMsgTs,
+ lastNotifiedAt);
+ if (dmLastNotified != null) {
+ dmLastNotifiedDao.updateDMLastNotified(toUpdate);
+ return;
+ }
+ dmLastNotifiedDao.insertDMLastNotified(toUpdate);
+ }
+
+ public final void deleteDMLastNotified(@NonNull final DMLastNotified dmLastNotified) {
+ dmLastNotifiedDao.deleteDMLastNotified(dmLastNotified);
+ }
+
+ public final void deleteAllDMLastNotified() {
+ dmLastNotifiedDao.deleteAllDMLastNotified();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java b/app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java
new file mode 100644
index 00000000..c0ae776f
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java
@@ -0,0 +1,85 @@
+package awais.instagrabber.db.entities;
+
+import androidx.annotation.NonNull;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+@Entity(tableName = DMLastNotified.TABLE_NAME, indices = {@Index(value = DMLastNotified.COL_THREAD_ID, unique = true)})
+public class DMLastNotified {
+ public final static String TABLE_NAME = "dm_last_notified";
+ public final static String COL_ID = "id";
+ public final static String COL_THREAD_ID = "thread_id";
+ public final static String COL_LAST_NOTIFIED_MSG_TS = "last_notified_msg_ts";
+ public final static String COL_LAST_NOTIFIED_AT = "last_notified_at";
+
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ private final int id;
+
+ @ColumnInfo(name = COL_THREAD_ID)
+ private final String threadId;
+
+ @ColumnInfo(name = COL_LAST_NOTIFIED_MSG_TS)
+ private final LocalDateTime lastNotifiedMsgTs;
+
+ @ColumnInfo(name = COL_LAST_NOTIFIED_AT)
+ private final LocalDateTime lastNotifiedAt;
+
+ public DMLastNotified(final int id,
+ final String threadId,
+ final LocalDateTime lastNotifiedMsgTs,
+ final LocalDateTime lastNotifiedAt) {
+ this.id = id;
+ this.threadId = threadId;
+ this.lastNotifiedMsgTs = lastNotifiedMsgTs;
+ this.lastNotifiedAt = lastNotifiedAt;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getThreadId() {
+ return threadId;
+ }
+
+ public LocalDateTime getLastNotifiedMsgTs() {
+ return lastNotifiedMsgTs;
+ }
+
+ public LocalDateTime getLastNotifiedAt() {
+ return lastNotifiedAt;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final DMLastNotified that = (DMLastNotified) o;
+ return id == that.id &&
+ Objects.equals(threadId, that.threadId) &&
+ Objects.equals(lastNotifiedMsgTs, that.lastNotifiedMsgTs) &&
+ Objects.equals(lastNotifiedAt, that.lastNotifiedAt);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, threadId, lastNotifiedMsgTs, lastNotifiedAt);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DMLastNotified{" +
+ "id=" + id +
+ ", threadId='" + threadId + '\'' +
+ ", lastNotifiedMsgTs='" + lastNotifiedMsgTs + '\'' +
+ ", lastNotifiedAt='" + lastNotifiedAt + '\'' +
+ '}';
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java
new file mode 100644
index 00000000..9cb15291
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java
@@ -0,0 +1,126 @@
+package awais.instagrabber.db.repositories;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import awais.instagrabber.db.datasources.DMLastNotifiedDataSource;
+import awais.instagrabber.db.entities.DMLastNotified;
+import awais.instagrabber.utils.AppExecutors;
+
+public class DMLastNotifiedRepository {
+ private static final String TAG = DMLastNotifiedRepository.class.getSimpleName();
+
+ private static DMLastNotifiedRepository instance;
+
+ private final AppExecutors appExecutors;
+ private final DMLastNotifiedDataSource dmLastNotifiedDataSource;
+
+ private DMLastNotifiedRepository(final AppExecutors appExecutors, final DMLastNotifiedDataSource dmLastNotifiedDataSource) {
+ this.appExecutors = appExecutors;
+ this.dmLastNotifiedDataSource = dmLastNotifiedDataSource;
+ }
+
+ public static DMLastNotifiedRepository getInstance(final DMLastNotifiedDataSource dmLastNotifiedDataSource) {
+ if (instance == null) {
+ instance = new DMLastNotifiedRepository(AppExecutors.getInstance(), dmLastNotifiedDataSource);
+ }
+ return instance;
+ }
+
+ public void getDMLastNotified(final String threadId,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ final DMLastNotified dmLastNotified = dmLastNotifiedDataSource.getDMLastNotified(threadId);
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ if (dmLastNotified == null) {
+ callback.onDataNotAvailable();
+ return;
+ }
+ callback.onSuccess(dmLastNotified);
+ });
+ });
+ }
+
+ public void getAllDMDmLastNotified(final RepositoryCallback> callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ final List allDMDmLastNotified = dmLastNotifiedDataSource.getAllDMDmLastNotified();
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ if (allDMDmLastNotified == null) {
+ callback.onDataNotAvailable();
+ return;
+ }
+ // cachedAccounts = accounts;
+ callback.onSuccess(allDMDmLastNotified);
+ });
+ });
+ }
+
+ public void insertOrUpdateDMLastNotified(final List dmLastNotifiedList,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ for (final DMLastNotified dmLastNotified : dmLastNotifiedList) {
+ dmLastNotifiedDataSource.insertOrUpdateDMLastNotified(dmLastNotified.getThreadId(),
+ dmLastNotified.getLastNotifiedMsgTs(),
+ dmLastNotified.getLastNotifiedAt());
+ }
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ callback.onSuccess(null);
+ });
+ });
+ }
+
+ public void insertOrUpdateDMLastNotified(final String threadId,
+ final LocalDateTime lastNotifiedMsgTs,
+ final LocalDateTime lastNotifiedAt,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ dmLastNotifiedDataSource.insertOrUpdateDMLastNotified(threadId, lastNotifiedMsgTs, lastNotifiedAt);
+ final DMLastNotified updated = dmLastNotifiedDataSource.getDMLastNotified(threadId);
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ if (updated == null) {
+ callback.onDataNotAvailable();
+ return;
+ }
+ callback.onSuccess(updated);
+ });
+ });
+ }
+
+ public void deleteDMLastNotified(final DMLastNotified dmLastNotified,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ dmLastNotifiedDataSource.deleteDMLastNotified(dmLastNotified);
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ callback.onSuccess(null);
+ });
+ });
+ }
+
+ public void deleteAllDMLastNotified(final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ dmLastNotifiedDataSource.deleteAllDMLastNotified();
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ callback.onSuccess(null);
+ });
+ });
+ }
+
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DMPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DMPreferencesFragment.java
new file mode 100644
index 00000000..5f84468a
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/DMPreferencesFragment.java
@@ -0,0 +1,201 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+import android.content.Intent;
+import android.text.Editable;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+
+import java.util.Objects;
+
+import awais.instagrabber.R;
+import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
+import awais.instagrabber.databinding.PrefAutoRefreshDmFreqBinding;
+import awais.instagrabber.services.DMSyncAlarmReceiver;
+import awais.instagrabber.services.DMSyncService;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.Debouncer;
+import awais.instagrabber.utils.TextUtils;
+
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
+import static awais.instagrabber.utils.Utils.settingsHelper;
+
+public class DMPreferencesFragment extends BasePreferencesFragment {
+ private static final String TAG = DMPreferencesFragment.class.getSimpleName();
+
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ screen.addPreference(getMarkDMSeenPreference(context));
+ screen.addPreference(getAutoRefreshDMPreference(context));
+ screen.addPreference(getAutoRefreshDMFreqPreference(context));
+ }
+
+ private Preference getMarkDMSeenPreference(@NonNull final Context context) {
+ return PreferenceHelper.getSwitchPreference(
+ context,
+ Constants.DM_MARK_AS_SEEN,
+ R.string.dm_mark_as_seen_setting,
+ R.string.dm_mark_as_seen_setting_summary,
+ false,
+ null
+ );
+ }
+
+ private Preference getAutoRefreshDMPreference(@NonNull final Context context) {
+ return PreferenceHelper.getSwitchPreference(
+ context,
+ PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH,
+ R.string.enable_dm_auto_refesh,
+ -1,
+ false,
+ (preference, newValue) -> {
+ if (!(newValue instanceof Boolean)) return false;
+ final boolean enabled = (Boolean) newValue;
+ if (enabled) {
+ DMSyncAlarmReceiver.setAlarm(context);
+ return true;
+ }
+ DMSyncAlarmReceiver.cancelAlarm(context);
+ try {
+ final Context applicationContext = context.getApplicationContext();
+ applicationContext.stopService(new Intent(applicationContext, DMSyncService.class));
+ } catch (Exception e) {
+ Log.e(TAG, "getAutoRefreshDMPreference: ", e);
+ }
+ return true;
+ }
+ );
+ }
+
+ private Preference getAutoRefreshDMFreqPreference(@NonNull final Context context) {
+ return new AutoRefreshDMFrePreference(context);
+ }
+
+ public static class AutoRefreshDMFrePreference extends Preference {
+ private static final String TAG = AutoRefreshDMFrePreference.class.getSimpleName();
+ private static final String DEBOUNCE_KEY = "dm_sync_service_update";
+ public static final int INTERVAL = 2000;
+
+ private final Debouncer.Callback changeCallback;
+
+ private Debouncer serviceUpdateDebouncer;
+ private PrefAutoRefreshDmFreqBinding binding;
+
+ public AutoRefreshDMFrePreference(final Context context) {
+ super(context);
+ setLayoutResource(R.layout.pref_auto_refresh_dm_freq);
+ // setKey(key);
+ setIconSpaceReserved(false);
+ changeCallback = new Debouncer.Callback() {
+ @Override
+ public void call(final String key) {
+ DMSyncAlarmReceiver.setAlarm(context);
+ }
+
+ @Override
+ public void onError(final Throwable t) {
+ Log.e(TAG, "onError: ", t);
+ }
+ };
+ serviceUpdateDebouncer = new Debouncer<>(changeCallback, INTERVAL);
+ }
+
+ @Override
+ public void onDependencyChanged(final Preference dependency, final boolean disableDependent) {
+ // super.onDependencyChanged(dependency, disableDependent);
+ if (binding == null) return;
+ binding.startText.setEnabled(!disableDependent);
+ binding.freqNum.setEnabled(!disableDependent);
+ binding.freqUnit.setEnabled(!disableDependent);
+ if (disableDependent) {
+ serviceUpdateDebouncer.terminate();
+ return;
+ }
+ serviceUpdateDebouncer = new Debouncer<>(changeCallback, INTERVAL);
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ setDependency(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
+ binding = PrefAutoRefreshDmFreqBinding.bind(holder.itemView);
+ final Context context = getContext();
+ if (context == null) return;
+ setupUnitSpinner(context);
+ setupNumberEditText(context);
+ }
+
+ private void setupUnitSpinner(final Context context) {
+ final ArrayAdapter adapter = ArrayAdapter.createFromResource(context,
+ R.array.dm_auto_refresh_freq_unit_labels,
+ android.R.layout.simple_spinner_item);
+ final String[] values = context.getResources().getStringArray(R.array.dm_auto_refresh_freq_units);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ binding.freqUnit.setAdapter(adapter);
+
+ String unit = settingsHelper.getString(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT);
+ if (TextUtils.isEmpty(unit)) {
+ unit = "secs";
+ }
+ int position = 0;
+ for (int i = 0; i < values.length; i++) {
+ if (Objects.equals(unit, values[i])) {
+ position = i;
+ break;
+ }
+ }
+ binding.freqUnit.setSelection(position);
+ binding.freqUnit.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(final AdapterView> parent, final View view, final int position, final long id) {
+ settingsHelper.putString(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, values[position]);
+ if (!isEnabled()) {
+ serviceUpdateDebouncer.terminate();
+ return;
+ }
+ serviceUpdateDebouncer.call(DEBOUNCE_KEY);
+ }
+
+ @Override
+ public void onNothingSelected(final AdapterView> parent) {}
+ });
+ }
+
+ private void setupNumberEditText(final Context context) {
+ int currentValue = settingsHelper.getInteger(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER);
+ if (currentValue <= 0) {
+ currentValue = 5;
+ }
+ binding.freqNum.setText(String.valueOf(currentValue));
+ binding.freqNum.addTextChangedListener(new TextWatcherAdapter() {
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ if (TextUtils.isEmpty(s)) return;
+ try {
+ final int value = Integer.parseInt(s.toString());
+ if (value <= 0) return;
+ settingsHelper.putInteger(PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER, value);
+ if (!isEnabled()) {
+ serviceUpdateDebouncer.terminate();
+ return;
+ }
+ serviceUpdateDebouncer.call(DEBOUNCE_KEY);
+ } catch (Exception e) {
+ Log.e(TAG, "afterTextChanged: ", e);
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
new file mode 100644
index 00000000..c2637b78
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
@@ -0,0 +1,101 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.widget.AppCompatButton;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreferenceCompat;
+
+import com.google.android.material.switchmaterial.SwitchMaterial;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.DirectoryChooser;
+import awais.instagrabber.utils.TextUtils;
+
+import static awais.instagrabber.utils.Constants.FOLDER_PATH;
+import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
+import static awais.instagrabber.utils.Utils.settingsHelper;
+
+public class DownloadsPreferencesFragment extends BasePreferencesFragment {
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ screen.addPreference(getDownloadUserFolderPreference(context));
+ screen.addPreference(getSaveToCustomFolderPreference(context));
+ }
+
+ private Preference getDownloadUserFolderPreference(@NonNull final Context context) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(Constants.DOWNLOAD_USER_FOLDER);
+ preference.setTitle(R.string.download_user_folder);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+
+ private Preference getSaveToCustomFolderPreference(@NonNull final Context context) {
+ return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
+ .setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
+ .setInteractionListener(file -> {
+ settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
+ resultCallback.onResult(file.getAbsolutePath());
+ })
+ .show(getParentFragmentManager(), null));
+ }
+
+ public static class SaveToCustomFolderPreference extends Preference {
+ private AppCompatTextView customPathTextView;
+ private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
+ private final String key;
+
+ public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
+ super(context);
+ this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
+ key = Constants.FOLDER_SAVE_TO;
+ setLayoutResource(R.layout.pref_custom_folder);
+ setKey(key);
+ setTitle(R.string.save_to_folder);
+ setIconSpaceReserved(false);
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
+ final View buttonContainer = holder.findViewById(R.id.button_container);
+ customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
+ cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
+ buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
+ final String customPath = settingsHelper.getString(FOLDER_PATH);
+ customPathTextView.setText(customPath);
+ });
+ final boolean savedToEnabled = settingsHelper.getBoolean(key);
+ holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
+ cbSaveTo.setChecked(savedToEnabled);
+ buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
+ final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
+ btnSaveTo.setOnClickListener(v -> {
+ if (onSelectFolderButtonClickListener == null) return;
+ onSelectFolderButtonClickListener.onClick(result -> {
+ if (TextUtils.isEmpty(result)) return;
+ customPathTextView.setText(result);
+ });
+ });
+ }
+
+ public interface ResultCallback {
+ void onResult(String result);
+ }
+
+ public interface OnSelectFolderButtonClickListener {
+ void onClick(ResultCallback resultCallback);
+ }
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java
new file mode 100644
index 00000000..deb7c277
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java
@@ -0,0 +1,61 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.CookieUtils;
+import awais.instagrabber.utils.TextUtils;
+
+import static awais.instagrabber.utils.Utils.settingsHelper;
+
+public class GeneralPreferencesFragment extends BasePreferencesFragment {
+
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ final String cookie = settingsHelper.getString(Constants.COOKIE);
+ final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
+ if (isLoggedIn) {
+ screen.addPreference(getDefaultTabPreference(context));
+ }
+ screen.addPreference(getUpdateCheckPreference(context));
+ }
+
+ private Preference getDefaultTabPreference(@NonNull final Context context) {
+ final ListPreference preference = new ListPreference(context);
+ preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
+ final TypedArray mainNavIds = getResources().obtainTypedArray(R.array.main_nav_ids);
+ final int length = mainNavIds.length();
+ final String[] values = new String[length];
+ for (int i = 0; i < length; i++) {
+ final int resourceId = mainNavIds.getResourceId(i, -1);
+ if (resourceId < 0) continue;
+ values[i] = getResources().getResourceEntryName(resourceId);
+ }
+ mainNavIds.recycle();
+ preference.setKey(Constants.DEFAULT_TAB);
+ preference.setTitle(R.string.pref_start_screen);
+ preference.setDialogTitle(R.string.pref_start_screen);
+ preference.setEntries(R.array.main_nav_ids_values);
+ preference.setEntryValues(values);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+
+ private Preference getUpdateCheckPreference(@NonNull final Context context) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(Constants.CHECK_UPDATES);
+ preference.setTitle(R.string.update_check);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/LocalePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/LocalePreferencesFragment.java
new file mode 100644
index 00000000..2c140157
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/LocalePreferencesFragment.java
@@ -0,0 +1,48 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.LocaleUtils;
+import awais.instagrabber.utils.UserAgentUtils;
+
+import static awais.instagrabber.utils.Utils.settingsHelper;
+
+public class LocalePreferencesFragment extends BasePreferencesFragment {
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ screen.addPreference(getLanguagePreference(context));
+ }
+
+ private Preference getLanguagePreference(@NonNull final Context context) {
+ final ListPreference preference = new ListPreference(context);
+ preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
+ final int length = getResources().getStringArray(R.array.languages).length;
+ final String[] values = new String[length];
+ for (int i = 0; i < length; i++) {
+ values[i] = String.valueOf(i);
+ }
+ preference.setKey(Constants.APP_LANGUAGE);
+ preference.setTitle(R.string.select_language);
+ preference.setDialogTitle(R.string.select_language);
+ preference.setEntries(R.array.languages);
+ preference.setIconSpaceReserved(false);
+ preference.setEntryValues(values);
+ preference.setOnPreferenceChangeListener((preference1, newValue) -> {
+ shouldRecreate();
+ final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
+ final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage());
+ settingsHelper.putString(Constants.APP_UA, appUa);
+ return true;
+ });
+ return preference;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java
new file mode 100644
index 00000000..72f69f79
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java
@@ -0,0 +1,43 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.Constants;
+
+public class NotificationsPreferencesFragment extends BasePreferencesFragment {
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ screen.addPreference(getActivityNotificationsPreference(context));
+ screen.addPreference(getDMNotificationsPreference(context));
+ }
+
+ private Preference getActivityNotificationsPreference(@NonNull final Context context) {
+ return PreferenceHelper.getSwitchPreference(
+ context,
+ Constants.CHECK_ACTIVITY,
+ R.string.activity_setting,
+ -1,
+ false,
+ (preference, newValue) -> {
+ shouldRecreate();
+ return true;
+ });
+ }
+
+ private Preference getDMNotificationsPreference(@NonNull final Context context) {
+ return PreferenceHelper.getSwitchPreference(
+ context,
+ PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS,
+ R.string.enable_dm_notifications,
+ -1,
+ false,
+ null);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java
new file mode 100644
index 00000000..3e1885e6
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java
@@ -0,0 +1,94 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import awais.instagrabber.R;
+import awais.instagrabber.dialogs.TimeSettingsDialog;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.Utils;
+
+import static awais.instagrabber.utils.Utils.settingsHelper;
+
+public class PostPreferencesFragment extends BasePreferencesFragment {
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ // generalCategory.addPreference(getAutoPlayVideosPreference(context));
+ screen.addPreference(getAlwaysMuteVideosPreference(context));
+ screen.addPreference(getShowCaptionPreference(context));
+ screen.addPreference(getPostTimeFormatPreference(context));
+ }
+
+ private Preference getAutoPlayVideosPreference(@NonNull final Context context) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(Constants.AUTOPLAY_VIDEOS);
+ preference.setTitle(R.string.post_viewer_autoplay_video);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+
+ private Preference getAlwaysMuteVideosPreference(@NonNull final Context context) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(Constants.MUTED_VIDEOS);
+ preference.setTitle(R.string.post_viewer_muted_autoplay);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+
+ private Preference getShowCaptionPreference(@NonNull final Context context) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(Constants.SHOW_CAPTIONS);
+ preference.setDefaultValue(true);
+ preference.setTitle(R.string.post_viewer_show_captions);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+
+ private Preference getPostTimeFormatPreference(@NonNull final Context context) {
+ final Preference preference = new Preference(context);
+ preference.setTitle(R.string.time_settings);
+ preference.setSummary(Utils.datetimeParser.format(new Date()));
+ preference.setIconSpaceReserved(false);
+ preference.setOnPreferenceClickListener(preference1 -> {
+ new TimeSettingsDialog(
+ settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED),
+ settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT),
+ settingsHelper.getString(Constants.DATE_TIME_SELECTION),
+ settingsHelper.getBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED),
+ (isCustomFormat,
+ formatSelection,
+ spTimeFormatSelectedItemPosition,
+ spSeparatorSelectedItemPosition,
+ spDateFormatSelectedItemPosition,
+ selectedFormat,
+ currentFormat,
+ swapDateTime) -> {
+ if (isCustomFormat) {
+ settingsHelper.putString(Constants.CUSTOM_DATE_TIME_FORMAT, formatSelection);
+ } else {
+ final String formatSelectionUpdated = spTimeFormatSelectedItemPosition + ";"
+ + spSeparatorSelectedItemPosition + ';'
+ + spDateFormatSelectedItemPosition; // time;separator;date
+ settingsHelper.putString(Constants.DATE_TIME_FORMAT, selectedFormat);
+ settingsHelper.putString(Constants.DATE_TIME_SELECTION, formatSelectionUpdated);
+ }
+ settingsHelper.putBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED, isCustomFormat);
+ settingsHelper.putBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED, swapDateTime);
+ Utils.datetimeParser = (SimpleDateFormat) currentFormat.clone();
+ preference.setSummary(Utils.datetimeParser.format(new Date()));
+ }
+ ).show(getParentFragmentManager(), null);
+ return true;
+ });
+ return preference;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceHelper.java b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceHelper.java
new file mode 100644
index 00000000..824c18d7
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceHelper.java
@@ -0,0 +1,30 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import androidx.preference.Preference.OnPreferenceChangeListener;
+import androidx.preference.SwitchPreferenceCompat;
+
+public final class PreferenceHelper {
+
+ public static SwitchPreferenceCompat getSwitchPreference(@NonNull final Context context,
+ @NonNull final String key,
+ @StringRes final int titleResId,
+ @StringRes final int summaryResId,
+ final boolean iconSpaceReserved,
+ final OnPreferenceChangeListener onPreferenceChangeListener) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(key);
+ preference.setTitle(titleResId);
+ preference.setIconSpaceReserved(iconSpaceReserved);
+ if (summaryResId != -1) {
+ preference.setSummary(summaryResId);
+ }
+ if (onPreferenceChangeListener != null) {
+ preference.setOnPreferenceChangeListener(onPreferenceChangeListener);
+ }
+ return preference;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java
new file mode 100644
index 00000000..3f481685
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java
@@ -0,0 +1,8 @@
+package awais.instagrabber.fragments.settings;
+
+public final class PreferenceKeys {
+ public static final String PREF_ENABLE_DM_NOTIFICATIONS = "enable_dm_notifications";
+ public static final String PREF_ENABLE_DM_AUTO_REFRESH = "enable_dm_auto_refresh";
+ public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT = "enable_dm_auto_refresh_freq_unit";
+ public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number";
+}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java
index 00628d3f..b45739a0 100644
--- a/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java
@@ -1,91 +1,55 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
-import android.content.res.TypedArray;
-import android.view.View;
import androidx.annotation.NonNull;
-import androidx.appcompat.widget.AppCompatButton;
-import androidx.appcompat.widget.AppCompatTextView;
+import androidx.annotation.StringRes;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
-import androidx.preference.ListPreference;
import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
-import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreferenceCompat;
-import com.google.android.material.switchmaterial.SwitchMaterial;
+import com.google.common.collect.ImmutableList;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.util.List;
import awais.instagrabber.R;
-import awais.instagrabber.dialogs.TimeSettingsDialog;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
-import awais.instagrabber.utils.DirectoryChooser;
-import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.TextUtils;
-import awais.instagrabber.utils.UserAgentUtils;
-import awais.instagrabber.utils.Utils;
-import static awais.instagrabber.utils.Constants.FOLDER_PATH;
-import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToDm;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToDownloads;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToGeneral;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToLocale;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToNotifications;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToPost;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToStories;
+import static awais.instagrabber.fragments.settings.SettingsPreferencesFragmentDirections.actionSettingsToTheme;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class SettingsPreferencesFragment extends BasePreferencesFragment {
- private static final String TAG = "SettingsPrefsFrag";
- private boolean isLoggedIn;
+ private static final String TAG = SettingsPreferencesFragment.class.getSimpleName();
+ private static final List screens = ImmutableList.of(
+ new SettingScreen(R.string.pref_category_general, actionSettingsToGeneral()),
+ new SettingScreen(R.string.pref_category_theme, actionSettingsToTheme()),
+ new SettingScreen(R.string.pref_category_locale, actionSettingsToLocale()),
+ new SettingScreen(R.string.pref_category_post, actionSettingsToPost()),
+ new SettingScreen(R.string.pref_category_stories, actionSettingsToStories(), true),
+ new SettingScreen(R.string.pref_category_dm, actionSettingsToDm(), true),
+ new SettingScreen(R.string.pref_category_notifications, actionSettingsToNotifications(), true),
+ new SettingScreen(R.string.pref_category_downloads, actionSettingsToDownloads())
+ );
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
- final String cookie = settingsHelper.getString(Constants.COOKIE);
- isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
final Context context = getContext();
if (context == null) return;
- final PreferenceCategory generalCategory = new PreferenceCategory(context);
- screen.addPreference(generalCategory);
- generalCategory.setTitle(R.string.pref_category_general);
- generalCategory.setIconSpaceReserved(false);
- generalCategory.addPreference(getThemePreference(context));
- generalCategory.addPreference(getDefaultTabPreference());
- generalCategory.addPreference(getUpdateCheckPreference());
- // generalCategory.addPreference(getAutoPlayVideosPreference());
- generalCategory.addPreference(getAlwaysMuteVideosPreference());
- generalCategory.addPreference(getShowCaptionPreference());
-
- // screen.addPreference(getDivider(context));
- // final PreferenceCategory themeCategory = new PreferenceCategory(context);
- // screen.addPreference(themeCategory);
- // themeCategory.setTitle(R.string.pref_category_theme);
- // themeCategory.setIconSpaceReserved(false);
- // themeCategory.addPreference(getAmoledThemePreference());
-
- final PreferenceCategory downloadsCategory = new PreferenceCategory(context);
- screen.addPreference(downloadsCategory);
- downloadsCategory.setTitle(R.string.pref_category_downloads);
- downloadsCategory.setIconSpaceReserved(false);
- downloadsCategory.addPreference(getDownloadUserFolderPreference());
- downloadsCategory.addPreference(getSaveToCustomFolderPreference());
-
- final PreferenceCategory localeCategory = new PreferenceCategory(context);
- screen.addPreference(localeCategory);
- localeCategory.setTitle(R.string.pref_category_locale);
- localeCategory.setIconSpaceReserved(false);
- localeCategory.addPreference(getLanguagePreference());
- localeCategory.addPreference(getPostTimePreference());
-
- if (isLoggedIn) {
- final PreferenceCategory loggedInUsersPreferenceCategory = new PreferenceCategory(context);
- screen.addPreference(loggedInUsersPreferenceCategory);
- loggedInUsersPreferenceCategory.setIconSpaceReserved(false);
- loggedInUsersPreferenceCategory.setTitle(R.string.login_settings);
- loggedInUsersPreferenceCategory.addPreference(getStorySortPreference());
- loggedInUsersPreferenceCategory.addPreference(getMarkStoriesSeenPreference());
- loggedInUsersPreferenceCategory.addPreference(getMarkDMSeenPreference());
- loggedInUsersPreferenceCategory.addPreference(getEnableActivityNotificationsPreference());
+ final String cookie = settingsHelper.getString(Constants.COOKIE);
+ final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
+ for (final SettingScreen settingScreen : screens) {
+ if (settingScreen.isLoginRequired() && !isLoggedIn) continue;
+ screen.addPreference(getNavPreference(context, settingScreen));
}
// else {
// final PreferenceCategory anonUsersPreferenceCategory = new PreferenceCategory(context);
@@ -95,275 +59,43 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
// }
}
- private Preference getLanguagePreference() {
- final Context context = getContext();
- if (context == null) return null;
- final ListPreference preference = new ListPreference(context);
- preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
- final int length = getResources().getStringArray(R.array.languages).length;
- final String[] values = new String[length];
- for (int i = 0; i < length; i++) {
- values[i] = String.valueOf(i);
- }
- preference.setKey(Constants.APP_LANGUAGE);
- preference.setTitle(R.string.select_language);
- preference.setDialogTitle(R.string.select_language);
- preference.setEntries(R.array.languages);
- preference.setIconSpaceReserved(false);
- preference.setEntryValues(values);
- preference.setOnPreferenceChangeListener((preference1, newValue) -> {
- shouldRecreate();
- final int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
- final String appUa = UserAgentUtils.generateAppUA(appUaCode, LocaleUtils.getCurrentLocale().getLanguage());
- settingsHelper.putString(Constants.APP_UA, appUa);
- return true;
- });
- return preference;
- }
-
- private Preference getDefaultTabPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final ListPreference preference = new ListPreference(context);
- preference.setEnabled(isLoggedIn);
- preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
- final TypedArray mainNavIds = getResources().obtainTypedArray(R.array.main_nav_ids);
- final int length = mainNavIds.length();
- final String[] values = new String[length];
- for (int i = 0; i < length; i++) {
- final int resourceId = mainNavIds.getResourceId(i, -1);
- if (resourceId < 0) continue;
- values[i] = getResources().getResourceEntryName(resourceId);
- }
- mainNavIds.recycle();
- preference.setKey(Constants.DEFAULT_TAB);
- preference.setTitle(R.string.pref_start_screen);
- preference.setDialogTitle(R.string.pref_start_screen);
- preference.setEntries(R.array.main_nav_ids_values);
- preference.setEntryValues(values);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getUpdateCheckPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.CHECK_UPDATES);
- preference.setTitle(R.string.update_check);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getThemePreference(@NonNull final Context context) {
+ private Preference getNavPreference(@NonNull final Context context,
+ @NonNull final SettingScreen settingScreen) {
final Preference preference = new Preference(context);
- preference.setTitle(R.string.pref_category_theme);
- // preference.setIcon(R.drawable.ic_format_paint_24);
+ preference.setTitle(settingScreen.getTitleResId());
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
- final NavDirections navDirections = SettingsPreferencesFragmentDirections.actionSettingsPreferencesFragmentToThemePreferencesFragment();
- NavHostFragment.findNavController(this).navigate(navDirections);
+ NavHostFragment.findNavController(this).navigate(settingScreen.getDirections());
return true;
});
return preference;
}
- private Preference getDownloadUserFolderPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.DOWNLOAD_USER_FOLDER);
- preference.setTitle(R.string.download_user_folder);
- preference.setIconSpaceReserved(false);
- return preference;
- }
+ private static class SettingScreen {
+ private final int titleResId;
+ private final NavDirections directions;
+ private final boolean loginRequired;
- private Preference getSaveToCustomFolderPreference() {
- final Context context = getContext();
- if (context == null) return null;
- return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser()
- .setInitialDirectory(settingsHelper.getString(FOLDER_PATH))
- .setInteractionListener(file -> {
- settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath());
- resultCallback.onResult(file.getAbsolutePath());
- })
- .show(getParentFragmentManager(), null));
- }
-
- private Preference getAutoPlayVideosPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.AUTOPLAY_VIDEOS);
- preference.setTitle(R.string.post_viewer_autoplay_video);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getAlwaysMuteVideosPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.MUTED_VIDEOS);
- preference.setTitle(R.string.post_viewer_muted_autoplay);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getShowCaptionPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.SHOW_CAPTIONS);
- preference.setDefaultValue(true);
- preference.setTitle(R.string.post_viewer_show_captions);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getStorySortPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final ListPreference preference = new ListPreference(context);
- preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
- final int length = getResources().getStringArray(R.array.story_sorts).length;
- final String[] values = new String[length];
- for (int i = 0; i < length; i++) {
- values[i] = String.valueOf(i);
- }
- preference.setKey(Constants.STORY_SORT);
- preference.setTitle(R.string.story_sort_setting);
- preference.setDialogTitle(R.string.story_sort_setting);
- preference.setEntries(R.array.story_sorts);
- preference.setIconSpaceReserved(false);
- preference.setEntryValues(values);
- return preference;
- }
-
- private Preference getMarkStoriesSeenPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.MARK_AS_SEEN);
- preference.setTitle(R.string.mark_as_seen_setting);
- preference.setSummary(R.string.mark_as_seen_setting_summary);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getMarkDMSeenPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.DM_MARK_AS_SEEN);
- preference.setTitle(R.string.dm_mark_as_seen_setting);
- preference.setSummary(R.string.dm_mark_as_seen_setting_summary);
- preference.setIconSpaceReserved(false);
- return preference;
- }
-
- private Preference getEnableActivityNotificationsPreference() {
- final Context context = getContext();
- if (context == null) return null;
- final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
- preference.setKey(Constants.CHECK_ACTIVITY);
- preference.setTitle(R.string.activity_setting);
- preference.setIconSpaceReserved(false);
- preference.setOnPreferenceChangeListener((preference1, newValue) -> {
- shouldRecreate();
- return true;
- });
- return preference;
- }
-
- private Preference getPostTimePreference() {
- final Context context = getContext();
- if (context == null) return null;
- final Preference preference = new Preference(context);
- preference.setTitle(R.string.time_settings);
- preference.setSummary(Utils.datetimeParser.format(new Date()));
- preference.setIconSpaceReserved(false);
- preference.setOnPreferenceClickListener(preference1 -> {
- new TimeSettingsDialog(
- settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED),
- settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT),
- settingsHelper.getString(Constants.DATE_TIME_SELECTION),
- settingsHelper.getBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED),
- (isCustomFormat,
- formatSelection,
- spTimeFormatSelectedItemPosition,
- spSeparatorSelectedItemPosition,
- spDateFormatSelectedItemPosition,
- selectedFormat,
- currentFormat,
- swapDateTime) -> {
- if (isCustomFormat) {
- settingsHelper.putString(Constants.CUSTOM_DATE_TIME_FORMAT, formatSelection);
- } else {
- final String formatSelectionUpdated = spTimeFormatSelectedItemPosition + ";"
- + spSeparatorSelectedItemPosition + ';'
- + spDateFormatSelectedItemPosition; // time;separator;date
- settingsHelper.putString(Constants.DATE_TIME_FORMAT, selectedFormat);
- settingsHelper.putString(Constants.DATE_TIME_SELECTION, formatSelectionUpdated);
- }
- settingsHelper.putBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED, isCustomFormat);
- settingsHelper.putBoolean(Constants.SWAP_DATE_TIME_FORMAT_ENABLED, swapDateTime);
- Utils.datetimeParser = (SimpleDateFormat) currentFormat.clone();
- preference.setSummary(Utils.datetimeParser.format(new Date()));
- }
- ).show(getParentFragmentManager(), null);
- return true;
- });
- return preference;
- }
-
- public static class SaveToCustomFolderPreference extends Preference {
- private AppCompatTextView customPathTextView;
- private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;
- private final String key;
-
- public SaveToCustomFolderPreference(final Context context, final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener) {
- super(context);
- this.onSelectFolderButtonClickListener = onSelectFolderButtonClickListener;
- key = Constants.FOLDER_SAVE_TO;
- setLayoutResource(R.layout.pref_custom_folder);
- setKey(key);
- setTitle(R.string.save_to_folder);
- setIconSpaceReserved(false);
+ public SettingScreen(@StringRes final int titleResId, final NavDirections directions) {
+ this(titleResId, directions, false);
}
- @Override
- public void onBindViewHolder(final PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
- final SwitchMaterial cbSaveTo = (SwitchMaterial) holder.findViewById(R.id.cbSaveTo);
- final View buttonContainer = holder.findViewById(R.id.button_container);
- customPathTextView = (AppCompatTextView) holder.findViewById(R.id.custom_path);
- cbSaveTo.setOnCheckedChangeListener((buttonView, isChecked) -> {
- settingsHelper.putBoolean(FOLDER_SAVE_TO, isChecked);
- buttonContainer.setVisibility(isChecked ? View.VISIBLE : View.GONE);
- final String customPath = settingsHelper.getString(FOLDER_PATH);
- customPathTextView.setText(customPath);
- });
- final boolean savedToEnabled = settingsHelper.getBoolean(key);
- holder.itemView.setOnClickListener(v -> cbSaveTo.toggle());
- cbSaveTo.setChecked(savedToEnabled);
- buttonContainer.setVisibility(savedToEnabled ? View.VISIBLE : View.GONE);
- final AppCompatButton btnSaveTo = (AppCompatButton) holder.findViewById(R.id.btnSaveTo);
- btnSaveTo.setOnClickListener(v -> {
- if (onSelectFolderButtonClickListener == null) return;
- onSelectFolderButtonClickListener.onClick(result -> {
- if (TextUtils.isEmpty(result)) return;
- customPathTextView.setText(result);
- });
- });
+ public SettingScreen(@StringRes final int titleResId, final NavDirections directions, final boolean loginRequired) {
+ this.titleResId = titleResId;
+ this.directions = directions;
+ this.loginRequired = loginRequired;
}
- public interface ResultCallback {
- void onResult(String result);
+ public int getTitleResId() {
+ return titleResId;
}
- public interface OnSelectFolderButtonClickListener {
- void onClick(ResultCallback resultCallback);
+ public NavDirections getDirections() {
+ return directions;
+ }
+
+ public boolean isLoginRequired() {
+ return loginRequired;
}
}
}
diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java
new file mode 100644
index 00000000..8337f7c3
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java
@@ -0,0 +1,48 @@
+package awais.instagrabber.fragments.settings;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreferenceCompat;
+
+import awais.instagrabber.R;
+import awais.instagrabber.utils.Constants;
+
+public class StoriesPreferencesFragment extends BasePreferencesFragment {
+ @Override
+ void setupPreferenceScreen(final PreferenceScreen screen) {
+ final Context context = getContext();
+ if (context == null) return;
+ screen.addPreference(getStorySortPreference(context));
+ screen.addPreference(getMarkStoriesSeenPreference(context));
+ }
+
+ private Preference getStorySortPreference(@NonNull final Context context) {
+ final ListPreference preference = new ListPreference(context);
+ preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
+ final int length = getResources().getStringArray(R.array.story_sorts).length;
+ final String[] values = new String[length];
+ for (int i = 0; i < length; i++) {
+ values[i] = String.valueOf(i);
+ }
+ preference.setKey(Constants.STORY_SORT);
+ preference.setTitle(R.string.story_sort_setting);
+ preference.setDialogTitle(R.string.story_sort_setting);
+ preference.setEntries(R.array.story_sorts);
+ preference.setIconSpaceReserved(false);
+ preference.setEntryValues(values);
+ return preference;
+ }
+
+ private Preference getMarkStoriesSeenPreference(@NonNull final Context context) {
+ final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
+ preference.setKey(Constants.MARK_AS_SEEN);
+ preference.setTitle(R.string.mark_as_seen_setting);
+ preference.setSummary(R.string.mark_as_seen_setting_summary);
+ preference.setIconSpaceReserved(false);
+ return preference;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/managers/InboxManager.java b/app/src/main/java/awais/instagrabber/managers/InboxManager.java
index 81ddd195..a41f0fc9 100644
--- a/app/src/main/java/awais/instagrabber/managers/InboxManager.java
+++ b/app/src/main/java/awais/instagrabber/managers/InboxManager.java
@@ -323,6 +323,7 @@ public final class InboxManager {
if (insertIndex < 0) return;
synchronized (this.inbox) {
final DirectInbox currentDirectInbox = getCurrentDirectInbox();
+ if (currentDirectInbox == null) return;
final List threadsCopy = new LinkedList<>(currentDirectInbox.getThreads());
threadsCopy.add(insertIndex, thread);
try {
@@ -338,6 +339,7 @@ public final class InboxManager {
public void removeThread(@NonNull final String threadId) {
synchronized (this.inbox) {
final DirectInbox currentDirectInbox = getCurrentDirectInbox();
+ if (currentDirectInbox == null) return;
final List threadsCopy = currentDirectInbox.getThreads()
.stream()
.filter(t -> !t.getThreadId().equals(threadId))
diff --git a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java
index bf23a8bc..7e00e165 100644
--- a/app/src/main/java/awais/instagrabber/managers/ThreadManager.java
+++ b/app/src/main/java/awais/instagrabber/managers/ThreadManager.java
@@ -382,6 +382,8 @@ public final class ThreadManager {
}
public void fetchPendingRequests() {
+ final Boolean isGroup = this.isGroup.getValue();
+ if (isGroup == null || !isGroup) return;
final Call request = service.participantRequests(threadId, 1, null);
request.enqueue(new Callback() {
diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java
index 0bda29c7..35f8994d 100644
--- a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java
+++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java
@@ -2,6 +2,9 @@ package awais.instagrabber.repositories.responses.directmessages;
import androidx.annotation.NonNull;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@@ -41,6 +44,7 @@ public class DirectItem implements Cloneable {
private Date date;
private boolean isPending;
private boolean showForwardAttribution;
+ private LocalDateTime localDateTime;
public DirectItem(final String itemId,
final long userId,
@@ -214,6 +218,13 @@ public class DirectItem implements Cloneable {
return date;
}
+ public LocalDateTime getLocalDateTime() {
+ if (localDateTime == null) {
+ localDateTime = Instant.ofEpochMilli(timestamp / 1000).atZone(ZoneId.systemDefault()).toLocalDateTime();;
+ }
+ return localDateTime;
+ }
+
public void setItemId(final String itemId) {
this.itemId = itemId;
}
diff --git a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java
index f0ccea70..2ae44b79 100644
--- a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java
+++ b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java
@@ -133,6 +133,6 @@ public class ActivityCheckerService extends Service {
final Intent intent = new Intent(getApplicationContext(), MainActivity.class)
.setAction(Constants.ACTION_SHOW_ACTIVITY)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- return PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ return PendingIntent.getActivity(getApplicationContext(), Constants.SHOW_ACTIVITY_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
}
diff --git a/app/src/main/java/awais/instagrabber/services/DMSyncAlarmReceiver.java b/app/src/main/java/awais/instagrabber/services/DMSyncAlarmReceiver.java
new file mode 100644
index 00000000..4fc37973
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/services/DMSyncAlarmReceiver.java
@@ -0,0 +1,87 @@
+package awais.instagrabber.services;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
+
+import awais.instagrabber.fragments.settings.PreferenceKeys;
+import awais.instagrabber.utils.Constants;
+
+import static awais.instagrabber.utils.Utils.settingsHelper;
+
+public class DMSyncAlarmReceiver extends BroadcastReceiver {
+ private static final String TAG = DMSyncAlarmReceiver.class.getSimpleName();
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
+ if (!enabled) {
+ // If somehow the alarm was triggered even when auto refresh is disabled
+ cancelAlarm(context);
+ return;
+ }
+ try {
+ final Context applicationContext = context.getApplicationContext();
+ ContextCompat.startForegroundService(applicationContext, new Intent(applicationContext, DMSyncService.class));
+ } catch (Exception e) {
+ Log.e(TAG, "onReceive: ", e);
+ }
+ }
+
+ public static void setAlarm(@NonNull final Context context) {
+ Log.d(TAG, "setting DMSyncService Alarm");
+ final AlarmManager alarmManager = getAlarmManager(context);
+ if (alarmManager == null) return;
+ final PendingIntent pendingIntent = getPendingIntent(context);
+ alarmManager.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis(), getIntervalMillis(), pendingIntent);
+ }
+
+ public static void cancelAlarm(@NonNull final Context context) {
+ Log.d(TAG, "cancelling DMSyncService Alarm");
+ final AlarmManager alarmManager = getAlarmManager(context);
+ if (alarmManager == null) return;
+ final PendingIntent pendingIntent = getPendingIntent(context);
+ alarmManager.cancel(pendingIntent);
+ }
+
+ private static AlarmManager getAlarmManager(@NonNull final Context context) {
+ return (AlarmManager) context.getApplicationContext().getSystemService(Context.ALARM_SERVICE);
+ }
+
+ private static PendingIntent getPendingIntent(@NonNull final Context context) {
+ final Context applicationContext = context.getApplicationContext();
+ final Intent intent = new Intent(applicationContext, DMSyncAlarmReceiver.class);
+ return PendingIntent.getBroadcast(applicationContext,
+ Constants.DM_SYNC_SERVICE_REQUEST_CODE,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ private static long getIntervalMillis() {
+ int amount = settingsHelper.getInteger(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER);
+ if (amount <= 0) {
+ amount = 5;
+ }
+ final String unit = settingsHelper.getString(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT);
+ final TemporalUnit temporalUnit;
+ switch (unit) {
+ case "mins":
+ temporalUnit = ChronoUnit.MINUTES;
+ break;
+ default:
+ case "secs":
+ temporalUnit = ChronoUnit.SECONDS;
+ }
+ return Duration.of(amount, temporalUnit).toMillis();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/services/DMSyncService.java b/app/src/main/java/awais/instagrabber/services/DMSyncService.java
new file mode 100644
index 00000000..a4c18aab
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/services/DMSyncService.java
@@ -0,0 +1,248 @@
+package awais.instagrabber.services;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.lifecycle.LifecycleService;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import awais.instagrabber.R;
+import awais.instagrabber.activities.MainActivity;
+import awais.instagrabber.db.datasources.DMLastNotifiedDataSource;
+import awais.instagrabber.db.entities.DMLastNotified;
+import awais.instagrabber.db.repositories.DMLastNotifiedRepository;
+import awais.instagrabber.db.repositories.RepositoryCallback;
+import awais.instagrabber.fragments.settings.PreferenceKeys;
+import awais.instagrabber.managers.DirectMessagesManager;
+import awais.instagrabber.managers.InboxManager;
+import awais.instagrabber.models.Resource;
+import awais.instagrabber.repositories.responses.directmessages.DirectInbox;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
+import awais.instagrabber.utils.Constants;
+import awais.instagrabber.utils.DMUtils;
+import awais.instagrabber.utils.DateUtils;
+import awais.instagrabber.utils.Utils;
+
+public class DMSyncService extends LifecycleService {
+ private static final String TAG = DMSyncService.class.getSimpleName();
+
+ private InboxManager inboxManager;
+ private DMLastNotifiedRepository dmLastNotifiedRepository;
+ private Map dmLastNotifiedMap;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ startForeground(Constants.DM_CHECK_NOTIFICATION_ID, buildForegroundNotification());
+ Log.d(TAG, "onCreate: Service created");
+ final DirectMessagesManager directMessagesManager = DirectMessagesManager.getInstance();
+ inboxManager = directMessagesManager.getInboxManager();
+ dmLastNotifiedRepository = DMLastNotifiedRepository.getInstance(DMLastNotifiedDataSource.getInstance(getApplicationContext()));
+ }
+
+ private void parseUnread(@NonNull final DirectInbox directInbox) {
+ dmLastNotifiedRepository.getAllDMDmLastNotified(new RepositoryCallback>() {
+ @Override
+ public void onSuccess(final List result) {
+ dmLastNotifiedMap = result != null
+ ? result.stream().collect(Collectors.toMap(DMLastNotified::getThreadId, Function.identity()))
+ : Collections.emptyMap();
+ parseUnreadActual(directInbox);
+ }
+
+ @Override
+ public void onDataNotAvailable() {
+ dmLastNotifiedMap = Collections.emptyMap();
+ parseUnreadActual(directInbox);
+ }
+ });
+ // Log.d(TAG, "inbox observer: " + directInbox);
+ }
+
+ private void parseUnreadActual(@NonNull final DirectInbox directInbox) {
+ final List threads = directInbox.getThreads();
+ final ImmutableMap.Builder> unreadMessagesMapBuilder = ImmutableMap.builder();
+ if (threads == null) {
+ stopSelf();
+ return;
+ }
+ for (final DirectThread thread : threads) {
+ if (thread.isMuted()) continue;
+ final boolean read = DMUtils.isRead(thread);
+ if (read) continue;
+ final List unreadMessages = getUnreadMessages(thread);
+ if (unreadMessages.isEmpty()) continue;
+ unreadMessagesMapBuilder.put(thread.getThreadId(), unreadMessages);
+ }
+ final Map> unreadMessagesMap = unreadMessagesMapBuilder.build();
+ if (unreadMessagesMap.isEmpty()) {
+ stopSelf();
+ return;
+ }
+ showNotification(directInbox, unreadMessagesMap);
+ final LocalDateTime now = LocalDateTime.now();
+ // Update db
+ final ImmutableList.Builder lastNotifiedListBuilder = ImmutableList.builder();
+ for (final Map.Entry> unreadMessagesEntry : unreadMessagesMap.entrySet()) {
+ final List unreadItems = unreadMessagesEntry.getValue();
+ final DirectItem latestItem = unreadItems.get(unreadItems.size() - 1);
+ lastNotifiedListBuilder.add(new DMLastNotified(0,
+ unreadMessagesEntry.getKey(),
+ latestItem.getLocalDateTime(),
+ now));
+ }
+ dmLastNotifiedRepository.insertOrUpdateDMLastNotified(
+ lastNotifiedListBuilder.build(),
+ new RepositoryCallback() {
+ @Override
+ public void onSuccess(final Void result) {
+ stopSelf();
+ }
+
+ @Override
+ public void onDataNotAvailable() {
+ stopSelf();
+ }
+ }
+ );
+ }
+
+ @NonNull
+ private List getUnreadMessages(@NonNull final DirectThread thread) {
+ final List items = thread.getItems();
+ if (items == null) return Collections.emptyList();
+ final DMLastNotified dmLastNotified = dmLastNotifiedMap.get(thread.getThreadId());
+ final long viewerId = thread.getViewerId();
+ final Map lastSeenAt = thread.getLastSeenAt();
+ final ImmutableList.Builder unreadListBuilder = ImmutableList.builder();
+ int count = 0;
+ for (final DirectItem item : items) {
+ if (item == null) continue;
+ if (item.getUserId() == viewerId) break; // Reached a message from the viewer, it is assumed the viewer has read the next messages
+ final boolean read = DMUtils.isRead(item, lastSeenAt, Collections.singletonList(viewerId));
+ if (read) break;
+ if (dmLastNotified != null && dmLastNotified.getLastNotifiedMsgTs() != null) {
+ if (count == 0 && DateUtils.isBeforeOrEqual(item.getLocalDateTime(), dmLastNotified.getLastNotifiedMsgTs())) {
+ // The first unread item has been notified and hence all subsequent items can be ignored
+ // since the items are in desc timestamp order
+ break;
+ }
+ }
+ unreadListBuilder.add(item);
+ count++;
+ // Inbox style notification only allows 6 lines
+ if (count >= 6) break;
+ }
+ // Reversing, so that oldest messages are on top
+ return unreadListBuilder.build().reverse();
+ }
+
+ private void showNotification(final DirectInbox directInbox,
+ final Map> unreadMessagesMap) {
+ final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager == null) return;
+ for (final Map.Entry> unreadMessagesEntry : unreadMessagesMap.entrySet()) {
+ final Optional directThreadOptional = getThread(directInbox, unreadMessagesEntry.getKey());
+ if (!directThreadOptional.isPresent()) continue;
+ final DirectThread thread = directThreadOptional.get();
+ final DirectItem firstDirectItem = thread.getFirstDirectItem();
+ if (firstDirectItem == null) continue;
+ final List unreadMessages = unreadMessagesEntry.getValue();
+ final NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
+ inboxStyle.setBigContentTitle(thread.getThreadTitle());
+ for (final DirectItem item : unreadMessages) {
+ inboxStyle.addLine(DMUtils.getMessageString(thread, getResources(), thread.getViewerId(), item));
+ }
+ final Notification notification = new NotificationCompat.Builder(this, Constants.DM_UNREAD_CHANNEL_ID)
+ .setStyle(inboxStyle)
+ .setSmallIcon(R.drawable.ic_round_mode_comment_24)
+ .setContentTitle(thread.getThreadTitle())
+ .setContentText(DMUtils.getMessageString(thread, getResources(), thread.getViewerId(), firstDirectItem))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setDefaults(NotificationCompat.DEFAULT_ALL)
+ .setGroup(Constants.GROUP_KEY_DM)
+ .setAutoCancel(true)
+ .setContentIntent(getThreadPendingIntent(thread.getThreadId(), thread.getThreadTitle()))
+ .build();
+ notificationManager.notify(Constants.DM_UNREAD_PARENT_NOTIFICATION_ID, notification);
+ }
+ }
+
+ private Optional getThread(@NonNull final DirectInbox directInbox, final String threadId) {
+ return directInbox.getThreads()
+ .stream()
+ .filter(thread -> Objects.equals(thread.getThreadId(), threadId))
+ .findFirst();
+ }
+
+ @NonNull
+ private PendingIntent getThreadPendingIntent(final String threadId, final String threadTitle) {
+ final Intent intent = new Intent(getApplicationContext(), MainActivity.class)
+ .setAction(Constants.ACTION_SHOW_DM_THREAD)
+ .putExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID, threadId)
+ .putExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE, threadTitle)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ return PendingIntent.getActivity(getApplicationContext(), Constants.SHOW_DM_THREAD, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ @Override
+ public int onStartCommand(final Intent intent, final int flags, final int startId) {
+ super.onStartCommand(intent, flags, startId);
+ final boolean notificationsEnabled = Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS);
+ inboxManager.getInbox().observe(this, inboxResource -> {
+ if (!notificationsEnabled || inboxResource == null || inboxResource.status != Resource.Status.SUCCESS) {
+ stopSelf();
+ return;
+ }
+ final DirectInbox directInbox = inboxResource.data;
+ if (directInbox == null) {
+ stopSelf();
+ return;
+ }
+ parseUnread(directInbox);
+ });
+ Log.d(TAG, "onStartCommand: refreshing inbox");
+ inboxManager.refresh();
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(@NonNull final Intent intent) {
+ super.onBind(intent);
+ return null;
+ }
+
+ private Notification buildForegroundNotification() {
+ final Resources resources = getResources();
+ return new NotificationCompat.Builder(this, Constants.SILENT_NOTIFICATIONS_CHANNEL_ID)
+ .setOngoing(true)
+ .setSound(null)
+ .setContentTitle(resources.getString(R.string.app_name))
+ .setContentText(resources.getString(R.string.checking_for_new_messages))
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setGroup(Constants.GROUP_KEY_SILENT_NOTIFICATIONS)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java
index 1b06283c..62e5bf5a 100644
--- a/app/src/main/java/awais/instagrabber/utils/Constants.java
+++ b/app/src/main/java/awais/instagrabber/utils/Constants.java
@@ -58,6 +58,8 @@ public final class Constants {
// Notification ids
public static final int ACTIVITY_NOTIFICATION_ID = 10;
+ public static final int DM_UNREAD_PARENT_NOTIFICATION_ID = 20;
+ public static final int DM_CHECK_NOTIFICATION_ID = 11;
// see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts
public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" +
@@ -74,12 +76,6 @@ public final class Constants {
public static final String FDROID_SHA1_FINGERPRINT = "C1661EB8FD09F618307E687786D5E5056F65084D";
public static final String SKIPPED_VERSION = "skipped_version";
public static final String DEFAULT_TAB = "default_tab";
- public static final String ACTIVITY_CHANNEL_ID = "activity";
- public static final String DOWNLOAD_CHANNEL_ID = "download";
- public static final String ACTIVITY_CHANNEL_NAME = "Activity";
- public static final String DOWNLOAD_CHANNEL_NAME = "Downloads";
- public static final String NOTIF_GROUP_NAME = "awais.instagrabber.InstaNotif";
- public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String PREF_DARK_THEME = "dark_theme";
public static final String PREF_LIGHT_THEME = "light_theme";
public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png";
@@ -94,4 +90,27 @@ public final class Constants {
public static final String PREF_SAVED_POSTS_LAYOUT = "saved_posts_layout";
public static final String PREF_EMOJI_VARIANTS = "emoji_variants";
public static final String PREF_REACTIONS = "reactions";
+
+ public static final String ACTIVITY_CHANNEL_ID = "activity";
+ public static final String ACTIVITY_CHANNEL_NAME = "Activity";
+ public static final String DOWNLOAD_CHANNEL_ID = "download";
+ public static final String DOWNLOAD_CHANNEL_NAME = "Downloads";
+ public static final String DM_UNREAD_CHANNEL_ID = "dmUnread";
+ public static final String DM_UNREAD_CHANNEL_NAME = "Messages";
+ public static final String SILENT_NOTIFICATIONS_CHANNEL_ID = "silentNotifications";
+ public static final String SILENT_NOTIFICATIONS_CHANNEL_NAME = "Silent notifications";
+
+ public static final String NOTIF_GROUP_NAME = "awais.instagrabber.InstaNotif";
+ public static final String GROUP_KEY_DM = "awais.instagrabber.MESSAGES";
+ public static final String GROUP_KEY_SILENT_NOTIFICATIONS = "awais.instagrabber.SILENT_NOTIFICATIONS";
+
+ public static final int SHOW_ACTIVITY_REQUEST_CODE = 1738;
+ public static final int SHOW_DM_THREAD = 2000;
+ public static final int DM_SYNC_SERVICE_REQUEST_CODE = 3000;
+
+ public static final String ACTION_SHOW_ACTIVITY = "show_activity";
+ public static final String ACTION_SHOW_DM_THREAD = "show_dm_thread";
+
+ public static final String DM_THREAD_ACTION_EXTRA_THREAD_ID = "thread_id";
+ public static final String DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title";
}
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/utils/DMUtils.java b/app/src/main/java/awais/instagrabber/utils/DMUtils.java
new file mode 100644
index 00000000..6ff6126a
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/utils/DMUtils.java
@@ -0,0 +1,273 @@
+package awais.instagrabber.utils;
+
+import android.content.res.Resources;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+import awais.instagrabber.R;
+import awais.instagrabber.models.enums.DirectItemType;
+import awais.instagrabber.models.enums.MediaItemType;
+import awais.instagrabber.repositories.responses.User;
+import awais.instagrabber.repositories.responses.directmessages.DirectItem;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
+import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
+import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
+import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary;
+
+public final class DMUtils {
+ public static boolean isRead(final DirectItem item,
+ @NonNull final Map lastSeenAt,
+ final List userIdsToCheck) {
+ // Further check if directStory exists
+ // if (read && directStory != null) {
+ // read = false;
+ // }
+ return lastSeenAt.entrySet()
+ .stream()
+ .filter(entry -> userIdsToCheck.contains(entry.getKey()))
+ .anyMatch(entry -> {
+ final String userLastSeenTsString = entry.getValue().getTimestamp();
+ if (userLastSeenTsString == null) return false;
+ final long userTs = Long.parseLong(userLastSeenTsString);
+ final long itemTs = item.getTimestamp();
+ return userTs >= itemTs;
+ });
+ }
+
+ public static boolean isRead(@NonNull final DirectThread thread) {
+ final boolean read;
+ if (thread.getDirectStory() != null) {
+ return false;
+ }
+ final DirectItem item = thread.getFirstDirectItem();
+ final long viewerId = thread.getViewerId();
+ if (item != null && item.getUserId() == viewerId) {
+ // if last item was sent by user, then it is read (even though we have auto read unchecked?)
+ read = true;
+ } else {
+ final Map lastSeenAtMap = thread.getLastSeenAt();
+ read = isRead(item, lastSeenAtMap, Collections.singletonList(viewerId));
+ }
+ return read;
+ }
+
+ public static String getMessageString(@NonNull final DirectThread thread,
+ final Resources resources,
+ final long viewerId,
+ final DirectItem item) {
+ final long senderId = item.getUserId();
+ final DirectItemType itemType = item.getItemType();
+ String subtitle = null;
+ final String username = getUsername(thread.getUsers(), senderId, viewerId, resources);
+ String message = "";
+ if (itemType == null) {
+ message = resources.getString(R.string.dms_inbox_raven_message_unknown);
+ } else {
+ switch (itemType) {
+ case TEXT:
+ message = item.getText();
+ break;
+ case LIKE:
+ message = item.getLike();
+ break;
+ case LINK:
+ message = item.getLink().getText();
+ break;
+ case PLACEHOLDER:
+ message = item.getPlaceholder().getMessage();
+ break;
+ case MEDIA_SHARE:
+ subtitle = resources.getString(R.string.dms_inbox_shared_post, username != null ? username : "",
+ item.getMediaShare().getUser().getUsername());
+ break;
+ case ANIMATED_MEDIA:
+ subtitle = resources.getString(R.string.dms_inbox_shared_gif, username != null ? username : "");
+ break;
+ case PROFILE:
+ subtitle = resources
+ .getString(R.string.dms_inbox_shared_profile, username != null ? username : "", item.getProfile().getUsername());
+ break;
+ case LOCATION:
+ subtitle = resources
+ .getString(R.string.dms_inbox_shared_location, username != null ? username : "", item.getLocation().getName());
+ break;
+ case MEDIA: {
+ final MediaItemType mediaType = item.getMedia().getMediaType();
+ subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
+ break;
+ }
+ case STORY_SHARE: {
+ final String reelType = item.getStoryShare().getReelType();
+ if (reelType == null) {
+ subtitle = item.getStoryShare().getTitle();
+ } else {
+ final int format = reelType.equals("highlight_reel")
+ ? R.string.dms_inbox_shared_highlight
+ : R.string.dms_inbox_shared_story;
+ subtitle = resources.getString(format, username != null ? username : "",
+ item.getStoryShare().getMedia().getUser().getUsername());
+ }
+ break;
+ }
+ case VOICE_MEDIA:
+ subtitle = resources.getString(R.string.dms_inbox_shared_voice, username != null ? username : "");
+ break;
+ case ACTION_LOG:
+ subtitle = item.getActionLog().getDescription();
+ break;
+ case VIDEO_CALL_EVENT:
+ subtitle = item.getVideoCallEvent().getDescription();
+ break;
+ case CLIP:
+ subtitle = resources.getString(R.string.dms_inbox_shared_clip, username != null ? username : "",
+ item.getClip().getClip().getUser().getUsername());
+ break;
+ case FELIX_SHARE:
+ subtitle = resources.getString(R.string.dms_inbox_shared_igtv, username != null ? username : "",
+ item.getFelixShare().getVideo().getUser().getUsername());
+ break;
+ case RAVEN_MEDIA:
+ subtitle = getRavenMediaSubtitle(item, resources, username);
+ break;
+ case REEL_SHARE:
+ final DirectItemReelShare reelShare = item.getReelShare();
+ if (reelShare == null) {
+ subtitle = "";
+ break;
+ }
+ final String reelType = reelShare.getType();
+ switch (reelType) {
+ case "reply":
+ if (viewerId == item.getUserId()) {
+ subtitle = resources.getString(R.string.dms_inbox_replied_story_outgoing, reelShare.getText());
+ } else {
+ subtitle = resources
+ .getString(R.string.dms_inbox_replied_story_incoming, username != null ? username : "", reelShare.getText());
+ }
+ break;
+ case "mention":
+ if (viewerId == item.getUserId()) {
+ // You mentioned the other person
+ final long mentionedUserId = item.getReelShare().getMentionedUserId();
+ final String otherUsername = getUsername(thread.getUsers(), mentionedUserId, viewerId, resources);
+ subtitle = resources.getString(R.string.dms_inbox_mentioned_story_outgoing, otherUsername);
+ } else {
+ // They mentioned you
+ subtitle = resources.getString(R.string.dms_inbox_mentioned_story_incoming, username != null ? username : "");
+ }
+ break;
+ case "reaction":
+ if (viewerId == item.getUserId()) {
+ subtitle = resources.getString(R.string.dms_inbox_reacted_story_outgoing, reelShare.getText());
+ } else {
+ subtitle = resources
+ .getString(R.string.dms_inbox_reacted_story_incoming, username != null ? username : "", reelShare.getText());
+ }
+ break;
+ default:
+ subtitle = "";
+ break;
+ }
+ break;
+ default:
+ message = resources.getString(R.string.dms_inbox_raven_message_unknown);
+ }
+ }
+ if (subtitle == null) {
+ if (thread.isGroup() || (!thread.isGroup() && senderId == viewerId)) {
+ subtitle = String.format("%s: %s", username != null ? username : "", message);
+ } else {
+ subtitle = message;
+ }
+ }
+ return subtitle;
+ }
+
+ public static String getUsername(final List users,
+ final long userId,
+ final long viewerId,
+ final Resources resources) {
+ if (userId == viewerId) {
+ return resources.getString(R.string.you);
+ }
+ final Optional senderOptional = users.stream()
+ .filter(Objects::nonNull)
+ .filter(user -> user.getPk() == userId)
+ .findFirst();
+ return senderOptional.map(User::getUsername).orElse(null);
+ }
+
+ public static String getMediaSpecificSubtitle(final String username, final Resources resources, final MediaItemType mediaType) {
+ final String userSharedAnImage = resources.getString(R.string.dms_inbox_shared_image, username != null ? username : "");
+ final String userSharedAVideo = resources.getString(R.string.dms_inbox_shared_video, username != null ? username : "");
+ final String userSentAMessage = resources.getString(R.string.dms_inbox_shared_message, username != null ? username : "");
+ String subtitle;
+ switch (mediaType) {
+ case MEDIA_TYPE_IMAGE:
+ subtitle = userSharedAnImage;
+ break;
+ case MEDIA_TYPE_VIDEO:
+ subtitle = userSharedAVideo;
+ break;
+ default:
+ subtitle = userSentAMessage;
+ break;
+ }
+ return subtitle;
+ }
+
+ private static String getRavenMediaSubtitle(final DirectItem item,
+ final Resources resources,
+ final String username) {
+ String subtitle = "↗ ";
+ final DirectItemVisualMedia visualMedia = item.getVisualMedia();
+ final RavenExpiringMediaActionSummary summary = visualMedia.getExpiringMediaActionSummary();
+ if (summary != null) {
+ final RavenExpiringMediaActionSummary.ActionType expiringMediaType = summary.getType();
+ int textRes = 0;
+ switch (expiringMediaType) {
+ case DELIVERED:
+ textRes = R.string.dms_inbox_raven_media_delivered;
+ break;
+ case SENT:
+ textRes = R.string.dms_inbox_raven_media_sent;
+ break;
+ case OPENED:
+ textRes = R.string.dms_inbox_raven_media_opened;
+ break;
+ case REPLAYED:
+ textRes = R.string.dms_inbox_raven_media_replayed;
+ break;
+ case SENDING:
+ textRes = R.string.dms_inbox_raven_media_sending;
+ break;
+ case BLOCKED:
+ textRes = R.string.dms_inbox_raven_media_blocked;
+ break;
+ case SUGGESTED:
+ textRes = R.string.dms_inbox_raven_media_suggested;
+ break;
+ case SCREENSHOT:
+ textRes = R.string.dms_inbox_raven_media_screenshot;
+ break;
+ case CANNOT_DELIVER:
+ textRes = R.string.dms_inbox_raven_media_cant_deliver;
+ break;
+ }
+ if (textRes > 0) {
+ subtitle += resources.getString(textRes);
+ }
+ return subtitle;
+ }
+ final MediaItemType mediaType = visualMedia.getMedia().getMediaType();
+ subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
+ return subtitle;
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/utils/DateUtils.java b/app/src/main/java/awais/instagrabber/utils/DateUtils.java
index 648fbc59..6f8fdcd1 100644
--- a/app/src/main/java/awais/instagrabber/utils/DateUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/DateUtils.java
@@ -2,6 +2,7 @@ package awais.instagrabber.utils;
import androidx.annotation.NonNull;
+import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
@@ -34,4 +35,8 @@ public final class DateUtils {
final Calendar calendar = Calendar.getInstance(Locale.getDefault());
return -(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
}
+
+ public static boolean isBeforeOrEqual(final LocalDateTime localDateTime, final LocalDateTime comparedTo) {
+ return localDateTime.isBefore(comparedTo) || localDateTime.isEqual(comparedTo);
+ }
}
diff --git a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java
index 3200b499..ee859700 100755
--- a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java
+++ b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java
@@ -105,11 +105,11 @@ public final class FlavorTown {
if (settingsHelper.getInteger(Constants.PREV_INSTALL_VERSION) < BuildConfig.VERSION_CODE) {
int appUaCode = settingsHelper.getInteger(Constants.APP_UA_CODE);
int browserUaCode = settingsHelper.getInteger(Constants.BROWSER_UA_CODE);
- if (browserUaCode == -1) {
+ if (browserUaCode == -1 || browserUaCode >= UserAgentUtils.browsers.length) {
browserUaCode = ThreadLocalRandom.current().nextInt(0, UserAgentUtils.browsers.length);
settingsHelper.putInteger(Constants.BROWSER_UA_CODE, browserUaCode);
}
- if (appUaCode == -1) {
+ if (appUaCode == -1 || appUaCode >= UserAgentUtils.devices.length) {
appUaCode = ThreadLocalRandom.current().nextInt(0, UserAgentUtils.devices.length);
settingsHelper.putInteger(Constants.APP_UA_CODE, appUaCode);
}
diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
index d9ef6d66..17aa3684 100644
--- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
+++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
@@ -13,7 +13,6 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.models.StoryModel;
@@ -31,8 +30,6 @@ import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.VideoVersion;
-import awais.instagrabber.repositories.responses.directmessages.DirectItem;
-import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awaisomereport.LogCollector;
public final class ResponseBodyUtils {
@@ -1123,25 +1120,6 @@ public final class ResponseBodyUtils {
return candidate.getUrl();
}
- public static boolean isRead(final DirectItem item,
- final Map lastSeenAt,
- final List userIdsToCheck) {
- // Further check if directStory exists
- // if (read && directStory != null) {
- // read = false;
- // }
- return lastSeenAt.entrySet()
- .stream()
- .filter(entry -> userIdsToCheck.contains(entry.getKey()))
- .anyMatch(entry -> {
- final String userLastSeenTsString = entry.getValue().getTimestamp();
- if (userLastSeenTsString == null) return false;
- final long userTs = Long.parseLong(userLastSeenTsString);
- final long itemTs = item.getTimestamp();
- return userTs >= itemTs;
- });
- }
-
public static StoryModel parseBroadcastItem(final JSONObject data) throws JSONException {
final StoryModel model = new StoryModel(data.getString("id"),
data.getString("cover_frame_url"),
diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
index dd96f41e..e3426f99 100755
--- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
+++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
@@ -8,6 +8,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringDef;
import androidx.appcompat.app.AppCompatDelegate;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
+import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS;
import static awais.instagrabber.utils.Constants.APP_LANGUAGE;
import static awais.instagrabber.utils.Constants.APP_THEME;
import static awais.instagrabber.utils.Constants.APP_UA;
@@ -130,14 +134,14 @@ public final class SettingsHelper {
CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME,
PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT,
PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT,
- STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS})
+ STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY,
- CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED})
+ CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH})
public @interface BooleanSettings {}
- @StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE})
+ @StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER})
public @interface IntegerSettings {}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_round_mode_comment_24.xml b/app/src/main/res/drawable/ic_round_mode_comment_24.xml
new file mode 100644
index 00000000..366bca72
--- /dev/null
+++ b/app/src/main/res/drawable/ic_round_mode_comment_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/pref_auto_refresh_dm_freq.xml b/app/src/main/res/layout/pref_auto_refresh_dm_freq.xml
new file mode 100644
index 00000000..45930499
--- /dev/null
+++ b/app/src/main/res/layout/pref_auto_refresh_dm_freq.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml
index dd199b6f..6d35b9b7 100644
--- a/app/src/main/res/navigation/more_nav_graph.xml
+++ b/app/src/main/res/navigation/more_nav_graph.xml
@@ -78,8 +78,29 @@
android:name="awais.instagrabber.fragments.settings.SettingsPreferencesFragment"
android:label="@string/action_settings">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 49bcf692..e9d9d6d6 100755
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -124,4 +124,12 @@
- @style/AppTheme.Dark.Black
- @style/AppTheme.Dark.MaterialDark
+
+ - secs
+ - mins
+
+
+ - @string/secs
+ - @string/mins
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 664e8045..64e61a51 100755
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -474,4 +474,14 @@
Accept
You
No pending requests
+ Checking for new messages
+ Stories
+ DM
+ Notifications
+ Post
+ Enable DM notifications
+ Auto refresh messages
+ Auto refresh every
+ secs
+ mins