Browse Source
Merge pull request #753 from ammargitham/dm-notifications-enhancements
Merge pull request #753 from ammargitham/dm-notifications-enhancements
DM Syncrenovate/org.robolectric-robolectric-4.x
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
124 changed files with 6761 additions and 2709 deletions
-
15app/build.gradle
-
161app/schemas/awais.instagrabber.db.AppDatabase/5.json
-
20app/src/main/AndroidManifest.xml
-
115app/src/main/java/awais/instagrabber/activities/MainActivity.java
-
2app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
-
4app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
-
5app/src/main/java/awais/instagrabber/adapters/FeedAdapterV2.java
-
107app/src/main/java/awais/instagrabber/adapters/GifItemsAdapter.java
-
22app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java
-
222app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
-
12app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
-
4app/src/main/java/awais/instagrabber/asyncs/CreateThreadAction.java
-
3app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
-
22app/src/main/java/awais/instagrabber/db/AppDatabase.java
-
16app/src/main/java/awais/instagrabber/db/Converters.java
-
34app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java
-
70app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java
-
85app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java
-
126app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java
-
150app/src/main/java/awais/instagrabber/dialogs/GifPickerBottomDialogFragment.java
-
37app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
-
10app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
-
33app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
-
94app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
-
221app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
-
32app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java
-
81app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java
-
201app/src/main/java/awais/instagrabber/fragments/settings/DMPreferencesFragment.java
-
101app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java
-
61app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java
-
93app/src/main/java/awais/instagrabber/fragments/settings/LocalePreferencesFragment.java
-
49app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java
-
43app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java
-
49app/src/main/java/awais/instagrabber/fragments/settings/PostPreferencesFragment.java
-
30app/src/main/java/awais/instagrabber/fragments/settings/PreferenceHelper.java
-
8app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java
-
362app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java
-
48app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java
-
81app/src/main/java/awais/instagrabber/managers/DirectMessagesManager.java
-
360app/src/main/java/awais/instagrabber/managers/InboxManager.java
-
1798app/src/main/java/awais/instagrabber/managers/ThreadManager.java
-
17app/src/main/java/awais/instagrabber/models/Resource.java
-
3app/src/main/java/awais/instagrabber/models/enums/BroadcastItemType.java
-
14app/src/main/java/awais/instagrabber/repositories/GifRepository.java
-
27app/src/main/java/awais/instagrabber/repositories/requests/directmessages/AnimatedMediaBroadcastOptions.java
-
19app/src/main/java/awais/instagrabber/repositories/responses/AnimatedMediaFixedHeight.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/AnimatedMediaImages.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/Audio.java
-
16app/src/main/java/awais/instagrabber/repositories/responses/Caption.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/EndOfFeedDemarcator.java
-
17app/src/main/java/awais/instagrabber/repositories/responses/EndOfFeedGroup.java
-
19app/src/main/java/awais/instagrabber/repositories/responses/EndOfFeedGroupSet.java
-
24app/src/main/java/awais/instagrabber/repositories/responses/FriendshipStatus.java
-
14app/src/main/java/awais/instagrabber/repositories/responses/ImageVersions2.java
-
20app/src/main/java/awais/instagrabber/repositories/responses/Location.java
-
49app/src/main/java/awais/instagrabber/repositories/responses/Media.java
-
16app/src/main/java/awais/instagrabber/repositories/responses/MediaCandidate.java
-
31app/src/main/java/awais/instagrabber/repositories/responses/User.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/UsertagIn.java
-
14app/src/main/java/awais/instagrabber/repositories/responses/Usertags.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/VideoVersion.java
-
16app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectInbox.java
-
72app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java
-
32app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemActionLog.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemAnimatedMedia.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemClip.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemFelixShare.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemLink.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemLinkContext.java
-
17app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemPlaceholder.java
-
22app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemReelShare.java
-
16app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemReelShareReactionInfo.java
-
21app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemStoryShare.java
-
18app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemVideoCallEvent.java
-
22app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemVisualMedia.java
-
17app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemVoiceMedia.java
-
75app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThread.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThreadDirectStory.java
-
16app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThreadLastSeenAt.java
-
6app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectThreadParticipantRequestsResponse.java
-
17app/src/main/java/awais/instagrabber/repositories/responses/directmessages/RavenExpiringMediaActionSummary.java
-
15app/src/main/java/awais/instagrabber/repositories/responses/directmessages/ThreadContext.java
-
70app/src/main/java/awais/instagrabber/repositories/responses/giphy/GiphyGif.java
-
59app/src/main/java/awais/instagrabber/repositories/responses/giphy/GiphyGifImage.java
-
40app/src/main/java/awais/instagrabber/repositories/responses/giphy/GiphyGifImages.java
-
46app/src/main/java/awais/instagrabber/repositories/responses/giphy/GiphyGifResponse.java
-
47app/src/main/java/awais/instagrabber/repositories/responses/giphy/GiphyGifResults.java
-
2app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java
-
27app/src/main/java/awais/instagrabber/services/BootCompletedReceiver.java
-
87app/src/main/java/awais/instagrabber/services/DMSyncAlarmReceiver.java
-
259app/src/main/java/awais/instagrabber/services/DMSyncService.java
-
2app/src/main/java/awais/instagrabber/utils/BitmapUtils.java
-
31app/src/main/java/awais/instagrabber/utils/Constants.java
-
285app/src/main/java/awais/instagrabber/utils/DMUtils.java
-
5app/src/main/java/awais/instagrabber/utils/DateUtils.java
-
44app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java
-
5app/src/main/java/awais/instagrabber/utils/FlavorTown.java
-
39app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java
-
10app/src/main/java/awais/instagrabber/utils/SettingsHelper.java
-
15app/src/main/java/awais/instagrabber/utils/Utils.java
@ -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')" |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,107 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.net.Uri; |
|||
import android.util.Log; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.DiffUtil; |
|||
import androidx.recyclerview.widget.ListAdapter; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.facebook.drawee.backends.pipeline.Fresco; |
|||
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; |
|||
import com.facebook.drawee.controller.BaseControllerListener; |
|||
import com.facebook.drawee.drawable.ScalingUtils; |
|||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; |
|||
import com.facebook.imagepipeline.common.ResizeOptions; |
|||
import com.facebook.imagepipeline.image.ImageInfo; |
|||
import com.facebook.imagepipeline.request.ImageRequest; |
|||
import com.facebook.imagepipeline.request.ImageRequestBuilder; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
import awais.instagrabber.databinding.ItemMediaBinding; |
|||
import awais.instagrabber.repositories.responses.giphy.GiphyGif; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public class GifItemsAdapter extends ListAdapter<GiphyGif, GifItemsAdapter.GifViewHolder> { |
|||
|
|||
private static final DiffUtil.ItemCallback<GiphyGif> diffCallback = new DiffUtil.ItemCallback<GiphyGif>() { |
|||
@Override |
|||
public boolean areItemsTheSame(@NonNull final GiphyGif oldItem, @NonNull final GiphyGif newItem) { |
|||
return Objects.equals(oldItem.getId(), newItem.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public boolean areContentsTheSame(@NonNull final GiphyGif oldItem, @NonNull final GiphyGif newItem) { |
|||
return Objects.equals(oldItem.getId(), newItem.getId()); |
|||
} |
|||
}; |
|||
|
|||
private final OnItemClickListener onItemClickListener; |
|||
|
|||
public GifItemsAdapter(final OnItemClickListener onItemClickListener) { |
|||
super(diffCallback); |
|||
this.onItemClickListener = onItemClickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public GifViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
final ItemMediaBinding binding = ItemMediaBinding.inflate(layoutInflater, parent, false); |
|||
return new GifViewHolder(binding, onItemClickListener); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final GifViewHolder holder, final int position) { |
|||
holder.bind(getItem(position)); |
|||
} |
|||
|
|||
public static class GifViewHolder extends RecyclerView.ViewHolder { |
|||
private static final String TAG = GifViewHolder.class.getSimpleName(); |
|||
private static final int size = Utils.displayMetrics.widthPixels / 3; |
|||
|
|||
private final ItemMediaBinding binding; |
|||
private final OnItemClickListener onItemClickListener; |
|||
|
|||
public GifViewHolder(@NonNull final ItemMediaBinding binding, |
|||
final OnItemClickListener onItemClickListener) { |
|||
super(binding.getRoot()); |
|||
this.binding = binding; |
|||
this.onItemClickListener = onItemClickListener; |
|||
binding.duration.setVisibility(View.GONE); |
|||
final GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(itemView.getResources()); |
|||
builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER); |
|||
binding.item.setHierarchy(builder.build()); |
|||
} |
|||
|
|||
public void bind(final GiphyGif item) { |
|||
if (onItemClickListener != null) { |
|||
itemView.setOnClickListener(v -> onItemClickListener.onItemClick(item)); |
|||
} |
|||
final BaseControllerListener<ImageInfo> controllerListener = new BaseControllerListener<ImageInfo>() { |
|||
@Override |
|||
public void onFailure(final String id, final Throwable throwable) { |
|||
Log.e(TAG, "onFailure: ", throwable); |
|||
} |
|||
}; |
|||
final ImageRequest request = ImageRequestBuilder |
|||
.newBuilderWithSource(Uri.parse(item.getImages().getFixedHeight().getWebp())) |
|||
.setResizeOptions(ResizeOptions.forDimensions(size, size)) |
|||
.build(); |
|||
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder() |
|||
.setImageRequest(request) |
|||
.setAutoPlayAnimations(true) |
|||
.setControllerListener(controllerListener); |
|||
binding.item.setController(builder.build()); |
|||
} |
|||
} |
|||
|
|||
public interface OnItemClickListener { |
|||
void onItemClick(GiphyGif giphyGif); |
|||
} |
|||
} |
@ -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<DMLastNotified> getAllDMDmLastNotified(); |
|||
|
|||
@Query("SELECT * FROM dm_last_notified WHERE thread_id = :threadId") |
|||
DMLastNotified findDMLastNotifiedByThreadId(String threadId); |
|||
|
|||
@Insert(onConflict = OnConflictStrategy.REPLACE) |
|||
List<Long> insertDMLastNotified(DMLastNotified... dmLastNotified); |
|||
|
|||
@Update |
|||
void updateDMLastNotified(DMLastNotified... dmLastNotified); |
|||
|
|||
@Delete |
|||
void deleteDMLastNotified(DMLastNotified... dmLastNotified); |
|||
|
|||
@Query("DELETE from dm_last_notified") |
|||
void deleteAllDMLastNotified(); |
|||
} |
@ -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<DMLastNotified> 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(); |
|||
} |
|||
} |
@ -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 + '\'' + |
|||
'}'; |
|||
} |
|||
} |
@ -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<DMLastNotified> 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<List<DMLastNotified>> callback) { |
|||
// request on the I/O thread |
|||
appExecutors.diskIO().execute(() -> { |
|||
final List<DMLastNotified> 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<DMLastNotified> dmLastNotifiedList, |
|||
final RepositoryCallback<Void> 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<DMLastNotified> 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<Void> 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<Void> 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); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,150 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.os.Bundle; |
|||
import android.text.Editable; |
|||
import android.util.Log; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.DialogFragment; |
|||
import androidx.lifecycle.ViewModelProvider; |
|||
import androidx.recyclerview.widget.GridLayoutManager; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog; |
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
import com.google.android.material.snackbar.Snackbar; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.GifItemsAdapter; |
|||
import awais.instagrabber.customviews.helpers.TextWatcherAdapter; |
|||
import awais.instagrabber.databinding.LayoutGifPickerBinding; |
|||
import awais.instagrabber.repositories.responses.giphy.GiphyGif; |
|||
import awais.instagrabber.utils.Debouncer; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.viewmodels.GifPickerViewModel; |
|||
|
|||
public class GifPickerBottomDialogFragment extends BottomSheetDialogFragment { |
|||
private static final String TAG = GifPickerBottomDialogFragment.class.getSimpleName(); |
|||
private static final int INPUT_DEBOUNCE_INTERVAL = 500; |
|||
private static final String INPUT_KEY = "gif_search_input"; |
|||
|
|||
private LayoutGifPickerBinding binding; |
|||
private GifPickerViewModel viewModel; |
|||
private GifItemsAdapter gifItemsAdapter; |
|||
private OnSelectListener onSelectListener; |
|||
private Debouncer<String> inputDebouncer; |
|||
|
|||
public static GifPickerBottomDialogFragment newInstance() { |
|||
final Bundle args = new Bundle(); |
|||
final GifPickerBottomDialogFragment fragment = new GifPickerBottomDialogFragment(); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog); |
|||
final Debouncer.Callback<String> callback = new Debouncer.Callback<String>() { |
|||
@Override |
|||
public void call(final String key) { |
|||
final Editable text = binding.input.getText(); |
|||
if (TextUtils.isEmpty(text)) { |
|||
viewModel.search(null); |
|||
return; |
|||
} |
|||
viewModel.search(text.toString().trim()); |
|||
} |
|||
|
|||
@Override |
|||
public void onError(final Throwable t) { |
|||
Log.e(TAG, "onError: ", t); |
|||
} |
|||
}; |
|||
inputDebouncer = new Debouncer<>(callback, INPUT_DEBOUNCE_INTERVAL); |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { |
|||
binding = LayoutGifPickerBinding.inflate(inflater, container, false); |
|||
viewModel = new ViewModelProvider(this).get(GifPickerViewModel.class); |
|||
return binding.getRoot(); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |
|||
init(); |
|||
} |
|||
|
|||
@Override |
|||
public void onStart() { |
|||
super.onStart(); |
|||
final Dialog dialog = getDialog(); |
|||
if (dialog == null) return; |
|||
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog; |
|||
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); |
|||
if (bottomSheetInternal == null) return; |
|||
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT; |
|||
bottomSheetInternal.requestLayout(); |
|||
} |
|||
|
|||
private void init() { |
|||
setupList(); |
|||
setupInput(); |
|||
setupObservers(); |
|||
} |
|||
|
|||
private void setupList() { |
|||
final Context context = getContext(); |
|||
if (context == null) return; |
|||
binding.gifList.setLayoutManager(new GridLayoutManager(context, 3)); |
|||
binding.gifList.setHasFixedSize(true); |
|||
gifItemsAdapter = new GifItemsAdapter(entry -> { |
|||
if (onSelectListener == null) return; |
|||
onSelectListener.onSelect(entry); |
|||
}); |
|||
binding.gifList.setAdapter(gifItemsAdapter); |
|||
} |
|||
|
|||
private void setupInput() { |
|||
binding.input.addTextChangedListener(new TextWatcherAdapter() { |
|||
@Override |
|||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { |
|||
inputDebouncer.call(INPUT_KEY); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void setupObservers() { |
|||
viewModel.getImages().observe(getViewLifecycleOwner(), imagesResource -> { |
|||
if (imagesResource == null) return; |
|||
switch (imagesResource.status) { |
|||
case SUCCESS: |
|||
gifItemsAdapter.submitList(imagesResource.data); |
|||
break; |
|||
case ERROR: |
|||
final Context context = getContext(); |
|||
if (context != null && imagesResource.message != null) { |
|||
Snackbar.make(context, binding.getRoot(), imagesResource.message, Snackbar.LENGTH_LONG); |
|||
} |
|||
break; |
|||
case LOADING: |
|||
break; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void setOnSelectListener(final OnSelectListener onSelectListener) { |
|||
this.onSelectListener = onSelectListener; |
|||
} |
|||
|
|||
public interface OnSelectListener { |
|||
void onSelect(GiphyGif giphyGif); |
|||
} |
|||
} |
@ -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<String> changeCallback; |
|||
|
|||
private Debouncer<String> 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<String>() { |
|||
@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<CharSequence> 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 = 30; |
|||
} |
|||
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); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
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 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.LocaleUtils; |
|||
import awais.instagrabber.utils.UserAgentUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
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)); |
|||
screen.addPreference(getPostTimeFormatPreference(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; |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
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 awais.instagrabber.R; |
|||
import awais.instagrabber.utils.Constants; |
|||
|
|||
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)); |
|||
} |
|||
|
|||
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; |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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"; |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -0,0 +1,81 @@ |
|||
package awais.instagrabber.managers; |
|||
|
|||
import android.content.ContentResolver; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import com.google.common.collect.Iterables; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.repositories.responses.User; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectItem; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThread; |
|||
|
|||
public final class DirectMessagesManager { |
|||
private static final String TAG = DirectMessagesManager.class.getSimpleName(); |
|||
private static final Object LOCK = new Object(); |
|||
|
|||
private static DirectMessagesManager instance; |
|||
|
|||
private final InboxManager inboxManager; |
|||
private final InboxManager pendingInboxManager; |
|||
|
|||
public static DirectMessagesManager getInstance() { |
|||
if (instance == null) { |
|||
synchronized (LOCK) { |
|||
if (instance == null) { |
|||
instance = new DirectMessagesManager(); |
|||
} |
|||
} |
|||
} |
|||
return instance; |
|||
} |
|||
|
|||
private DirectMessagesManager() { |
|||
inboxManager = InboxManager.getInstance(false); |
|||
pendingInboxManager = InboxManager.getInstance(true); |
|||
} |
|||
|
|||
public void moveThreadFromPending(@NonNull final String threadId) { |
|||
final List<DirectThread> pendingThreads = pendingInboxManager.getThreads().getValue(); |
|||
if (pendingThreads == null) return; |
|||
final int index = Iterables.indexOf(pendingThreads, t -> t.getThreadId().equals(threadId)); |
|||
if (index < 0) return; |
|||
final DirectThread thread = pendingThreads.get(index); |
|||
final DirectItem threadFirstDirectItem = thread.getFirstDirectItem(); |
|||
if (threadFirstDirectItem == null) return; |
|||
final List<DirectThread> threads = inboxManager.getThreads().getValue(); |
|||
int insertIndex = 0; |
|||
for (final DirectThread tempThread : threads) { |
|||
final DirectItem firstDirectItem = tempThread.getFirstDirectItem(); |
|||
if (firstDirectItem == null) continue; |
|||
final long timestamp = firstDirectItem.getTimestamp(); |
|||
if (timestamp < threadFirstDirectItem.getTimestamp()) { |
|||
break; |
|||
} |
|||
insertIndex++; |
|||
} |
|||
thread.setPending(false); |
|||
inboxManager.addThread(thread, insertIndex); |
|||
pendingInboxManager.removeThread(threadId); |
|||
final Integer currentTotal = inboxManager.getPendingRequestsTotal().getValue(); |
|||
if (currentTotal == null) return; |
|||
inboxManager.setPendingRequestsTotal(currentTotal - 1); |
|||
} |
|||
|
|||
public InboxManager getInboxManager() { |
|||
return inboxManager; |
|||
} |
|||
|
|||
public InboxManager getPendingInboxManager() { |
|||
return pendingInboxManager; |
|||
} |
|||
|
|||
public ThreadManager getThreadManager(@NonNull final String threadId, |
|||
final boolean pending, |
|||
@NonNull final User currentUser, |
|||
@NonNull final ContentResolver contentResolver) { |
|||
return ThreadManager.getInstance(threadId, pending, currentUser, contentResolver); |
|||
} |
|||
} |
@ -0,0 +1,360 @@ |
|||
package awais.instagrabber.managers; |
|||
|
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.lifecycle.LiveData; |
|||
import androidx.lifecycle.MutableLiveData; |
|||
import androidx.lifecycle.Transformations; |
|||
|
|||
import com.google.common.cache.CacheBuilder; |
|||
import com.google.common.cache.CacheLoader; |
|||
import com.google.common.cache.LoadingCache; |
|||
import com.google.common.collect.ImmutableList; |
|||
import com.google.common.collect.Iterables; |
|||
|
|||
import java.util.Collection; |
|||
import java.util.Collections; |
|||
import java.util.Comparator; |
|||
import java.util.LinkedList; |
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.stream.Collectors; |
|||
|
|||
import awais.instagrabber.models.Resource; |
|||
import awais.instagrabber.repositories.responses.User; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectBadgeCount; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectInbox; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectInboxResponse; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectItem; |
|||
import awais.instagrabber.repositories.responses.directmessages.DirectThread; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
import awais.instagrabber.webservices.DirectMessagesService; |
|||
import retrofit2.Call; |
|||
import retrofit2.Callback; |
|||
import retrofit2.Response; |
|||
|
|||
import static androidx.lifecycle.Transformations.distinctUntilChanged; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class InboxManager { |
|||
private static final String TAG = InboxManager.class.getSimpleName(); |
|||
private static final LoadingCache<String, Object> THREAD_LOCKS = CacheBuilder |
|||
.newBuilder() |
|||
.expireAfterAccess(1, TimeUnit.MINUTES) // max lock time ever expected |
|||
.build(CacheLoader.from(Object::new)); |
|||
private static final Comparator<DirectThread> THREAD_COMPARATOR = (t1, t2) -> { |
|||
final DirectItem t1FirstDirectItem = t1.getFirstDirectItem(); |
|||
final DirectItem t2FirstDirectItem = t2.getFirstDirectItem(); |
|||
if (t1FirstDirectItem == null && t2FirstDirectItem == null) return 0; |
|||
if (t1FirstDirectItem == null) return 1; |
|||
if (t2FirstDirectItem == null) return -1; |
|||
return Long.compare(t2FirstDirectItem.getTimestamp(), t1FirstDirectItem.getTimestamp()); |
|||
}; |
|||
|
|||
private final MutableLiveData<Resource<DirectInbox>> inbox = new MutableLiveData<>(); |
|||
private final MutableLiveData<Resource<Integer>> unseenCount = new MutableLiveData<>(); |
|||
private final MutableLiveData<Integer> pendingRequestsTotal = new MutableLiveData<>(0); |
|||
|
|||
private final LiveData<List<DirectThread>> threads; |
|||
private final DirectMessagesService service; |
|||
private final boolean pending; |
|||
|
|||
private Call<DirectInboxResponse> inboxRequest; |
|||
private Call<DirectBadgeCount> unseenCountRequest; |
|||
private long seqId; |
|||
private String cursor; |
|||
private boolean hasOlder = true; |
|||
private User viewer; |
|||
|
|||
@NonNull |
|||
public static InboxManager getInstance(final boolean pending) { |
|||
return new InboxManager(pending); |
|||
} |
|||
|
|||
private InboxManager(final boolean pending) { |
|||
this.pending = pending; |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
final long userId = CookieUtils.getUserIdFromCookie(cookie); |
|||
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID); |
|||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); |
|||
if (TextUtils.isEmpty(csrfToken) || userId <= 0 || TextUtils.isEmpty(deviceUuid)) { |
|||
throw new IllegalArgumentException("User is not logged in!"); |
|||
} |
|||
service = DirectMessagesService.getInstance(csrfToken, userId, deviceUuid); |
|||
|
|||
// Transformations |
|||
threads = distinctUntilChanged(Transformations.map(inbox, inboxResource -> { |
|||
if (inboxResource == null) { |
|||
return Collections.emptyList(); |
|||
} |
|||
final DirectInbox inbox = inboxResource.data; |
|||
if (inbox == null) { |
|||
return Collections.emptyList(); |
|||
} |
|||
return ImmutableList.sortedCopyOf(THREAD_COMPARATOR, inbox.getThreads()); |
|||
})); |
|||
|
|||
fetchInbox(); |
|||
if (!pending) { |
|||
fetchUnseenCount(); |
|||
} |
|||
} |
|||
|
|||
public LiveData<Resource<DirectInbox>> getInbox() { |
|||
return distinctUntilChanged(inbox); |
|||
} |
|||
|
|||
public LiveData<List<DirectThread>> getThreads() { |
|||
return threads; |
|||
} |
|||
|
|||
public LiveData<Resource<Integer>> getUnseenCount() { |
|||
return distinctUntilChanged(unseenCount); |
|||
} |
|||
|
|||
public LiveData<Integer> getPendingRequestsTotal() { |
|||
return distinctUntilChanged(pendingRequestsTotal); |
|||
} |
|||
|
|||
public User getViewer() { |
|||
return viewer; |
|||
} |
|||
|
|||
public void fetchInbox() { |
|||
final Resource<DirectInbox> inboxResource = inbox.getValue(); |
|||
if ((inboxResource != null && inboxResource.status == Resource.Status.LOADING) || !hasOlder) return; |
|||
stopCurrentInboxRequest(); |
|||
inbox.postValue(Resource.loading(getCurrentDirectInbox())); |
|||
inboxRequest = pending ? service.fetchPendingInbox(cursor, seqId) : service.fetchInbox(cursor, seqId); |
|||
inboxRequest.enqueue(new Callback<DirectInboxResponse>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<DirectInboxResponse> call, @NonNull final Response<DirectInboxResponse> response) { |
|||
parseInboxResponse(response.body()); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<DirectInboxResponse> call, @NonNull final Throwable t) { |
|||
Log.e(TAG, "Failed fetching dm inbox", t); |
|||
inbox.postValue(Resource.error(t.getMessage(), getCurrentDirectInbox())); |
|||
hasOlder = false; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void fetchUnseenCount() { |
|||
final Resource<Integer> unseenCountResource = unseenCount.getValue(); |
|||
if ((unseenCountResource != null && unseenCountResource.status == Resource.Status.LOADING)) return; |
|||
stopCurrentUnseenCountRequest(); |
|||
unseenCount.postValue(Resource.loading(getCurrentUnseenCount())); |
|||
unseenCountRequest = service.fetchUnseenCount(); |
|||
unseenCountRequest.enqueue(new Callback<DirectBadgeCount>() { |
|||
@Override |
|||
public void onResponse(@NonNull final Call<DirectBadgeCount> call, @NonNull final Response<DirectBadgeCount> response) { |
|||
final DirectBadgeCount directBadgeCount = response.body(); |
|||
if (directBadgeCount == null) { |
|||
Log.e(TAG, "onResponse: directBadgeCount Response is null"); |
|||
unseenCount.postValue(Resource.error("Unseen count response is null", getCurrentUnseenCount())); |
|||
return; |
|||
} |
|||
unseenCount.postValue(Resource.success(directBadgeCount.getBadgeCount())); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(@NonNull final Call<DirectBadgeCount> call, @NonNull final Throwable t) { |
|||
Log.e(TAG, "Failed fetching unseen count", t); |
|||
unseenCount.postValue(Resource.error(t.getMessage(), getCurrentUnseenCount())); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
public void refresh() { |
|||
cursor = null; |
|||
seqId = 0; |
|||
hasOlder = true; |
|||
fetchInbox(); |
|||
if (!pending) { |
|||
fetchUnseenCount(); |
|||
} |
|||
} |
|||
|
|||
private DirectInbox getCurrentDirectInbox() { |
|||
final Resource<DirectInbox> inboxResource = inbox.getValue(); |
|||
return inboxResource != null ? inboxResource.data : null; |
|||
} |
|||
|
|||
private void parseInboxResponse(final DirectInboxResponse response) { |
|||
if (response == null) { |
|||
Log.e(TAG, "parseInboxResponse: Response is null"); |
|||
inbox.postValue(Resource.error("Response is null", getCurrentDirectInbox())); |
|||
hasOlder = false; |
|||
return; |
|||
} |
|||
if (!response.getStatus().equals("ok")) { |
|||
final String msg = "DM inbox fetch response: status not ok"; |
|||
Log.e(TAG, msg); |
|||
inbox.postValue(Resource.error(msg, getCurrentDirectInbox())); |
|||
hasOlder = false; |
|||
return; |
|||
} |
|||
seqId = response.getSeqId(); |
|||
if (viewer == null) { |
|||
viewer = response.getViewer(); |
|||
} |
|||
final DirectInbox inbox = response.getInbox(); |
|||
if (!TextUtils.isEmpty(cursor)) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox != null) { |
|||
List<DirectThread> threads = currentDirectInbox.getThreads(); |
|||
threads = threads == null ? new LinkedList<>() : new LinkedList<>(threads); |
|||
threads.addAll(inbox.getThreads()); |
|||
inbox.setThreads(threads); |
|||
} |
|||
} |
|||
this.inbox.postValue(Resource.success(inbox)); |
|||
cursor = inbox.getOldestCursor(); |
|||
hasOlder = inbox.hasOlder(); |
|||
pendingRequestsTotal.postValue(response.getPendingRequestsTotal()); |
|||
} |
|||
|
|||
public void setThread(@NonNull final String threadId, |
|||
@NonNull final DirectThread thread) { |
|||
final DirectInbox inbox = getCurrentDirectInbox(); |
|||
if (inbox == null) return; |
|||
final int index = getThreadIndex(threadId, inbox); |
|||
setThread(inbox, index, thread); |
|||
} |
|||
|
|||
private void setThread(@NonNull final DirectInbox inbox, |
|||
final int index, |
|||
@NonNull final DirectThread thread) { |
|||
if (index < 0) return; |
|||
synchronized (this.inbox) { |
|||
final List<DirectThread> threadsCopy = new LinkedList<>(inbox.getThreads()); |
|||
threadsCopy.set(index, thread); |
|||
try { |
|||
final DirectInbox clone = (DirectInbox) inbox.clone(); |
|||
clone.setThreads(threadsCopy); |
|||
this.inbox.postValue(Resource.success(clone)); |
|||
} catch (CloneNotSupportedException e) { |
|||
Log.e(TAG, "setThread: ", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void addItemsToThread(@NonNull final String threadId, |
|||
final int insertIndex, |
|||
@NonNull final Collection<DirectItem> items) { |
|||
final DirectInbox inbox = getCurrentDirectInbox(); |
|||
if (inbox == null) return; |
|||
synchronized (THREAD_LOCKS.getUnchecked(threadId)) { |
|||
final int index = getThreadIndex(threadId, inbox); |
|||
if (index < 0) return; |
|||
final List<DirectThread> threads = inbox.getThreads(); |
|||
final DirectThread thread = threads.get(index); |
|||
List<DirectItem> list = thread.getItems(); |
|||
list = list == null ? new LinkedList<>() : new LinkedList<>(list); |
|||
if (insertIndex >= 0) { |
|||
list.addAll(insertIndex, items); |
|||
} else { |
|||
list.addAll(items); |
|||
} |
|||
try { |
|||
final DirectThread threadClone = (DirectThread) thread.clone(); |
|||
threadClone.setItems(list); |
|||
setThread(inbox, index, threadClone); |
|||
} catch (Exception e) { |
|||
Log.e(TAG, "addItemsToThread: ", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void setItemsToThread(@NonNull final String threadId, |
|||
@NonNull final List<DirectItem> updatedItems) { |
|||
final DirectInbox inbox = getCurrentDirectInbox(); |
|||
if (inbox == null) return; |
|||
synchronized (THREAD_LOCKS.getUnchecked(threadId)) { |
|||
final int index = getThreadIndex(threadId, inbox); |
|||
if (index < 0) return; |
|||
final List<DirectThread> threads = inbox.getThreads(); |
|||
final DirectThread thread = threads.get(index); |
|||
thread.setItems(updatedItems); |
|||
setThread(inbox, index, thread); |
|||
} |
|||
} |
|||
|
|||
private int getThreadIndex(@NonNull final String threadId, |
|||
@NonNull final DirectInbox inbox) { |
|||
final List<DirectThread> threads = inbox.getThreads(); |
|||
if (threads == null || threads.isEmpty()) { |
|||
return -1; |
|||
} |
|||
return Iterables.indexOf(threads, t -> { |
|||
if (t == null) return false; |
|||
return t.getThreadId().equals(threadId); |
|||
}); |
|||
} |
|||
|
|||
private Integer getCurrentUnseenCount() { |
|||
final Resource<Integer> unseenCountResource = unseenCount.getValue(); |
|||
return unseenCountResource != null ? unseenCountResource.data : null; |
|||
} |
|||
|
|||
private void stopCurrentInboxRequest() { |
|||
if (inboxRequest == null || inboxRequest.isCanceled() || inboxRequest.isExecuted()) return; |
|||
inboxRequest.cancel(); |
|||
inboxRequest = null; |
|||
} |
|||
|
|||
private void stopCurrentUnseenCountRequest() { |
|||
if (unseenCountRequest == null || unseenCountRequest.isCanceled() || unseenCountRequest.isExecuted()) return; |
|||
unseenCountRequest.cancel(); |
|||
unseenCountRequest = null; |
|||
} |
|||
|
|||
public void onDestroy() { |
|||
stopCurrentInboxRequest(); |
|||
stopCurrentUnseenCountRequest(); |
|||
} |
|||
|
|||
public void addThread(@NonNull final DirectThread thread, final int insertIndex) { |
|||
if (insertIndex < 0) return; |
|||
synchronized (this.inbox) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox == null) return; |
|||
final List<DirectThread> threadsCopy = new LinkedList<>(currentDirectInbox.getThreads()); |
|||
threadsCopy.add(insertIndex, thread); |
|||
try { |
|||
final DirectInbox clone = (DirectInbox) currentDirectInbox.clone(); |
|||
clone.setThreads(threadsCopy); |
|||
this.inbox.setValue(Resource.success(clone)); |
|||
} catch (CloneNotSupportedException e) { |
|||
Log.e(TAG, "setThread: ", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void removeThread(@NonNull final String threadId) { |
|||
synchronized (this.inbox) { |
|||
final DirectInbox currentDirectInbox = getCurrentDirectInbox(); |
|||
if (currentDirectInbox == null) return; |
|||
final List<DirectThread> threadsCopy = currentDirectInbox.getThreads() |
|||
.stream() |
|||
.filter(t -> !t.getThreadId().equals(threadId)) |
|||
.collect(Collectors.toList()); |
|||
try { |
|||
final DirectInbox clone = (DirectInbox) currentDirectInbox.clone(); |
|||
clone.setThreads(threadsCopy); |
|||
this.inbox.postValue(Resource.success(clone)); |
|||
} catch (CloneNotSupportedException e) { |
|||
Log.e(TAG, "setThread: ", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void setPendingRequestsTotal(final int total) { |
|||
pendingRequestsTotal.postValue(total); |
|||
} |
|||
} |
1798
app/src/main/java/awais/instagrabber/managers/ThreadManager.java
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,14 @@ |
|||
package awais.instagrabber.repositories; |
|||
|
|||
import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse; |
|||
import retrofit2.Call; |
|||
import retrofit2.http.GET; |
|||
import retrofit2.http.Query; |
|||
|
|||
public interface GifRepository { |
|||
|
|||
@GET("/api/v1/creatives/story_media_search_keyed_format/") |
|||
Call<GiphyGifResponse> searchGiphyGifs(@Query("request_surface") final String requestSurface, |
|||
@Query("q") final String query, |
|||
@Query("media_types") final String mediaTypes); |
|||
} |
@ -0,0 +1,27 @@ |
|||
package awais.instagrabber.repositories.requests.directmessages; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
import awais.instagrabber.models.enums.BroadcastItemType; |
|||
import awais.instagrabber.repositories.responses.giphy.GiphyGif; |
|||
|
|||
public class AnimatedMediaBroadcastOptions extends BroadcastOptions { |
|||
|
|||
private final GiphyGif giphyGif; |
|||
|
|||
public AnimatedMediaBroadcastOptions(final String clientContext, |
|||
final ThreadIdOrUserIds threadIdOrUserIds, |
|||
final GiphyGif giphyGif) { |
|||
super(clientContext, threadIdOrUserIds, BroadcastItemType.ANIMATED_MEDIA); |
|||
this.giphyGif = giphyGif; |
|||
} |
|||
|
|||
@Override |
|||
public Map<String, String> getFormMap() { |
|||
final Map<String, String> form = new HashMap<>(); |
|||
form.put("is_sticker", String.valueOf(giphyGif.isSticker())); |
|||
form.put("id", giphyGif.getId()); |
|||
return form; |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
package awais.instagrabber.repositories.responses.giphy; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
public class GiphyGif { |
|||
private final String type; |
|||
private final String id; |
|||
private final String title; |
|||
private final int isSticker; |
|||
private final GiphyGifImages images; |
|||
|
|||
public GiphyGif(final String type, final String id, final String title, final int isSticker, final GiphyGifImages images) { |
|||
this.type = type; |
|||
this.id = id; |
|||
this.title = title; |
|||
this.isSticker = isSticker; |
|||
this.images = images; |
|||
} |
|||
|
|||
public String getType() { |
|||
return type; |
|||
} |
|||
|
|||
public String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public String getTitle() { |
|||
return title; |
|||
} |
|||
|
|||
public boolean isSticker() { |
|||
return isSticker == 1; |
|||
} |
|||
|
|||
public GiphyGifImages getImages() { |
|||
return images; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(final Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
final GiphyGif giphyGif = (GiphyGif) o; |
|||
return isSticker == giphyGif.isSticker && |
|||
Objects.equals(type, giphyGif.type) && |
|||
Objects.equals(id, giphyGif.id) && |
|||
Objects.equals(title, giphyGif.title) && |
|||
Objects.equals(images, giphyGif.images); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(type, id, title, isSticker, images); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public String toString() { |
|||
return "GiphyGif{" + |
|||
"type='" + type + '\'' + |
|||
", id='" + id + '\'' + |
|||
", title='" + title + '\'' + |
|||
", isSticker=" + isSticker() + |
|||
", images=" + images + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
package awais.instagrabber.repositories.responses.giphy; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
public class GiphyGifImage { |
|||
private final int height; |
|||
private final int width; |
|||
private final long webpSize; |
|||
private final String webp; |
|||
|
|||
public GiphyGifImage(final int height, final int width, final long webpSize, final String webp) { |
|||
this.height = height; |
|||
this.width = width; |
|||
this.webpSize = webpSize; |
|||
this.webp = webp; |
|||
} |
|||
|
|||
public int getHeight() { |
|||
return height; |
|||
} |
|||
|
|||
public int getWidth() { |
|||
return width; |
|||
} |
|||
|
|||
public long getWebpSize() { |
|||
return webpSize; |
|||
} |
|||
|
|||
public String getWebp() { |
|||
return webp; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(final Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
final GiphyGifImage that = (GiphyGifImage) o; |
|||
return height == that.height && |
|||
width == that.width && |
|||
webpSize == that.webpSize && |
|||
Objects.equals(webp, that.webp); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(height, width, webpSize, webp); |
|||
} |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return "GiphyGifImage{" + |
|||
"height=" + height + |
|||
", width=" + width + |
|||
", webpSize=" + webpSize + |
|||
", webp='" + webp + '\'' + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
package awais.instagrabber.repositories.responses.giphy; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
import awais.instagrabber.repositories.responses.AnimatedMediaFixedHeight; |
|||
|
|||
public class GiphyGifImages { |
|||
private final AnimatedMediaFixedHeight fixedHeight; |
|||
|
|||
public GiphyGifImages(final AnimatedMediaFixedHeight fixedHeight) { |
|||
this.fixedHeight = fixedHeight; |
|||
} |
|||
|
|||
public AnimatedMediaFixedHeight getFixedHeight() { |
|||
return fixedHeight; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(final Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
final GiphyGifImages that = (GiphyGifImages) o; |
|||
return Objects.equals(fixedHeight, that.fixedHeight); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(fixedHeight); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public String toString() { |
|||
return "GiphyGifImages{" + |
|||
"fixedHeight=" + fixedHeight + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
package awais.instagrabber.repositories.responses.giphy; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
public class GiphyGifResponse { |
|||
private final GiphyGifResults results; |
|||
private final String status; |
|||
|
|||
public GiphyGifResponse(final GiphyGifResults results, final String status) { |
|||
this.results = results; |
|||
this.status = status; |
|||
} |
|||
|
|||
public GiphyGifResults getResults() { |
|||
return results; |
|||
} |
|||
|
|||
public String getStatus() { |
|||
return status; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(final Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
final GiphyGifResponse that = (GiphyGifResponse) o; |
|||
return Objects.equals(results, that.results) && |
|||
Objects.equals(status, that.status); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(results, status); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public String toString() { |
|||
return "GiphyGifResponse{" + |
|||
"results=" + results + |
|||
", status='" + status + '\'' + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
package awais.instagrabber.repositories.responses.giphy; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
|
|||
public class GiphyGifResults { |
|||
private final List<GiphyGif> giphyGifs; |
|||
private final List<GiphyGif> giphy; |
|||
|
|||
public GiphyGifResults(final List<GiphyGif> giphyGifs, final List<GiphyGif> giphy) { |
|||
this.giphyGifs = giphyGifs; |
|||
this.giphy = giphy; |
|||
} |
|||
|
|||
public List<GiphyGif> getGiphyGifs() { |
|||
return giphyGifs; |
|||
} |
|||
|
|||
public List<GiphyGif> getGiphy() { |
|||
return giphy; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(final Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
final GiphyGifResults that = (GiphyGifResults) o; |
|||
return Objects.equals(giphyGifs, that.giphyGifs) && |
|||
Objects.equals(giphy, that.giphy); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return Objects.hash(giphyGifs, giphy); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public String toString() { |
|||
return "GiphyGifResults{" + |
|||
"giphyGifs=" + giphyGifs + |
|||
", giphy=" + giphy + |
|||
'}'; |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
package awais.instagrabber.services; |
|||
|
|||
import android.content.BroadcastReceiver; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
|
|||
import java.util.Objects; |
|||
|
|||
import awais.instagrabber.fragments.settings.PreferenceKeys; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.CookieUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class BootCompletedReceiver extends BroadcastReceiver { |
|||
@Override |
|||
public void onReceive(final Context context, final Intent intent) { |
|||
if (!Objects.equals(intent.getAction(), "android.intent.action.BOOT_COMPLETED")) return; |
|||
final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH); |
|||
if (!enabled) return; |
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0; |
|||
if (!isLoggedIn) return; |
|||
DMSyncAlarmReceiver.setAlarm(context); |
|||
} |
|||
} |
@ -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 = 30; |
|||
} |
|||
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(); |
|||
} |
|||
} |
@ -0,0 +1,259 @@ |
|||
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.CookieUtils; |
|||
import awais.instagrabber.utils.DMUtils; |
|||
import awais.instagrabber.utils.DateUtils; |
|||
import awais.instagrabber.utils.TextUtils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public class DMSyncService extends LifecycleService { |
|||
private static final String TAG = DMSyncService.class.getSimpleName(); |
|||
|
|||
private InboxManager inboxManager; |
|||
private DMLastNotifiedRepository dmLastNotifiedRepository; |
|||
private Map<String, DMLastNotified> 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<List<DMLastNotified>>() { |
|||
@Override |
|||
public void onSuccess(final List<DMLastNotified> 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<DirectThread> threads = directInbox.getThreads(); |
|||
final ImmutableMap.Builder<String, List<DirectItem>> 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<DirectItem> unreadMessages = getUnreadMessages(thread); |
|||
if (unreadMessages.isEmpty()) continue; |
|||
unreadMessagesMapBuilder.put(thread.getThreadId(), unreadMessages); |
|||
} |
|||
final Map<String, List<DirectItem>> unreadMessagesMap = unreadMessagesMapBuilder.build(); |
|||
if (unreadMessagesMap.isEmpty()) { |
|||
stopSelf(); |
|||
return; |
|||
} |
|||
showNotification(directInbox, unreadMessagesMap); |
|||
final LocalDateTime now = LocalDateTime.now(); |
|||
// Update db |
|||
final ImmutableList.Builder<DMLastNotified> lastNotifiedListBuilder = ImmutableList.builder(); |
|||
for (final Map.Entry<String, List<DirectItem>> unreadMessagesEntry : unreadMessagesMap.entrySet()) { |
|||
final List<DirectItem> 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<Void>() { |
|||
@Override |
|||
public void onSuccess(final Void result) { |
|||
stopSelf(); |
|||
} |
|||
|
|||
@Override |
|||
public void onDataNotAvailable() { |
|||
stopSelf(); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
@NonNull |
|||
private List<DirectItem> getUnreadMessages(@NonNull final DirectThread thread) { |
|||
final List<DirectItem> items = thread.getItems(); |
|||
if (items == null) return Collections.emptyList(); |
|||
final DMLastNotified dmLastNotified = dmLastNotifiedMap.get(thread.getThreadId()); |
|||
final long viewerId = thread.getViewerId(); |
|||
final Map<Long, DirectThreadLastSeenAt> lastSeenAt = thread.getLastSeenAt(); |
|||
final ImmutableList.Builder<DirectItem> 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<String, List<DirectItem>> unreadMessagesMap) { |
|||
final NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); |
|||
if (notificationManager == null) return; |
|||
for (final Map.Entry<String, List<DirectItem>> unreadMessagesEntry : unreadMessagesMap.entrySet()) { |
|||
final Optional<DirectThread> 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<DirectItem> 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<DirectThread> 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 String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0; |
|||
if (!isLoggedIn) { |
|||
stopSelf(); |
|||
return START_NOT_STICKY; |
|||
} |
|||
// Need to setup here if service was started by the boot completed receiver |
|||
CookieUtils.setupCookies(cookie); |
|||
final boolean notificationsEnabled = 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(); |
|||
} |
|||
} |
@ -0,0 +1,285 @@ |
|||
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.DirectItemAnimatedMedia; |
|||
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<Long, DirectThreadLastSeenAt> lastSeenAt, |
|||
final List<Long> 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<Long, DirectThreadLastSeenAt> 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: |
|||
final User mediaShareUser = item.getMediaShare().getUser(); |
|||
subtitle = resources.getString(R.string.dms_inbox_shared_post, |
|||
username != null ? username : "", |
|||
mediaShareUser == null ? "" : mediaShareUser.getUsername()); |
|||
break; |
|||
case ANIMATED_MEDIA: |
|||
final DirectItemAnimatedMedia animatedMedia = item.getAnimatedMedia(); |
|||
subtitle = resources.getString(animatedMedia.isSticker() ? R.string.dms_inbox_shared_sticker |
|||
: 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; |
|||
final User storyShareMediaUser = item.getStoryShare().getMedia().getUser(); |
|||
subtitle = resources.getString(format, |
|||
username != null ? username : "", |
|||
storyShareMediaUser == null ? "" : storyShareMediaUser.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: |
|||
final User clipUser = item.getClip().getClip().getUser(); |
|||
subtitle = resources.getString(R.string.dms_inbox_shared_clip, |
|||
username != null ? username : "", |
|||
clipUser == null ? "" : clipUser.getUsername()); |
|||
break; |
|||
case FELIX_SHARE: |
|||
final User felixShareVideoUser = item.getFelixShare().getVideo().getUser(); |
|||
subtitle = resources.getString(R.string.dms_inbox_shared_igtv, |
|||
username != null ? username : "", |
|||
felixShareVideoUser == null ? "" : felixShareVideoUser.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<User> users, |
|||
final long userId, |
|||
final long viewerId, |
|||
final Resources resources) { |
|||
if (userId == viewerId) { |
|||
return resources.getString(R.string.you); |
|||
} |
|||
final Optional<User> 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; |
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue