diff --git a/app/build.gradle b/app/build.gradle
index e738ac73..53cfe918 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,13 +56,13 @@ configurations.all {
}
dependencies {
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
def appcompat_version = "1.2.0"
- def nav_version = '2.3.2'
+ def nav_version = '2.3.3'
def exoplayer_version = '2.12.0'
- implementation 'com.google.android.material:material:1.3.0'
+ implementation 'com.google.android.material:material:1.4.0-alpha01'
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
@@ -70,15 +70,16 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
- implementation "androidx.recyclerview:recyclerview:1.2.0-beta01"
+ implementation "androidx.recyclerview:recyclerview:1.2.0-beta02"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.preference:preference:1.1.1"
- implementation "androidx.work:work-runtime:2.4.0"
+ implementation "androidx.work:work-runtime:2.5.0"
implementation 'androidx.palette:palette:1.0.0'
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation 'com.google.guava:guava:27.0.1-android'
@@ -89,10 +90,10 @@ dependencies {
annotationProcessor "androidx.room:room-compiler:$room_version"
// CameraX
- def camerax_version = "1.0.0-rc01"
+ def camerax_version = "1.1.0-alpha02"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
- implementation "androidx.camera:camera-view:1.0.0-alpha20"
+ implementation "androidx.camera:camera-view:1.0.0-alpha22"
// EmojiCompat
def emoji_compat_version = "1.1.0"
diff --git a/app/schemas/awais.instagrabber.db.AppDatabase/5.json b/app/schemas/awais.instagrabber.db.AppDatabase/5.json
new file mode 100644
index 00000000..a60a7ea8
--- /dev/null
+++ b/app/schemas/awais.instagrabber.db.AppDatabase/5.json
@@ -0,0 +1,161 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 5,
+ "identityHash": "0b38e12b76bb081ec837191c5ef5b54e",
+ "entities": [
+ {
+ "tableName": "accounts",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "uid",
+ "columnName": "uid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "username",
+ "columnName": "username",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "cookie",
+ "columnName": "cookie",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "fullName",
+ "columnName": "full_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profilePic",
+ "columnName": "profile_pic",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "favorites",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "query",
+ "columnName": "query_text",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "display_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "picUrl",
+ "columnName": "pic_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "dateAdded",
+ "columnName": "date_added",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "dm_last_notified",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "threadId",
+ "columnName": "thread_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastNotifiedMsgTs",
+ "columnName": "last_notified_msg_ts",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastNotifiedAt",
+ "columnName": "last_notified_at",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": true
+ },
+ "indices": [
+ {
+ "name": "index_dm_last_notified_thread_id",
+ "unique": true,
+ "columnNames": [
+ "thread_id"
+ ],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
+ }
+ ],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0b38e12b76bb081ec837191c5ef5b54e')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ba525c14..fd517a33 100755
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,12 +8,12 @@
+
-
-
-
+
+
@@ -147,6 +147,19 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java
index 117ded8b..a9eb9ec0 100644
--- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java
+++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java
@@ -38,6 +38,8 @@ import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
@@ -45,6 +47,7 @@ import androidx.navigation.ui.NavigationUI;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
+import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.ArrayList;
@@ -61,12 +64,15 @@ import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
+import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
import awais.instagrabber.fragments.main.FeedFragment;
+import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.SuggestionModel;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.services.ActivityCheckerService;
+import awais.instagrabber.services.DMSyncAlarmReceiver;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
@@ -75,6 +81,7 @@ import awais.instagrabber.utils.IntentUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.emoji.EmojiParser;
+import awais.instagrabber.viewmodels.AppStateViewModel;
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
import static awais.instagrabber.utils.Utils.settingsHelper;
@@ -102,6 +109,8 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private int firstFragmentGraphIndex;
private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false;
+ private boolean isLoggedIn;
+ private HideBottomViewOnScrollBehavior behavior;
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
@@ -131,10 +140,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
binding = ActivityMainBinding.inflate(getLayoutInflater());
final String cookie = settingsHelper.getString(Constants.COOKIE);
CookieUtils.setupCookies(cookie);
+ isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
createNotificationChannels();
+ try {
+ final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();
+ //noinspection unchecked
+ behavior = (HideBottomViewOnScrollBehavior) layoutParams.getBehavior();
+ } catch (Exception e) {
+ Log.e(TAG, "onCreate: ", e);
+ }
if (savedInstanceState == null) {
setupBottomNavigationBar(true);
}
@@ -142,6 +159,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
if (checkUpdates) FlavorTown.updateCheck(this);
FlavorTown.changelogCheck(this);
+ new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
final Intent intent = getIntent();
handleIntent(intent);
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
@@ -154,6 +172,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
EmojiVariantManager.getInstance();
});
initEmojiCompat();
+ // initDmService();
+ }
+
+ private void initDmService() {
+ if (!isLoggedIn) return;
+ final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
+ if (!enabled) return;
+ DMSyncAlarmReceiver.setAlarm(this);
}
@Override
@@ -242,15 +268,22 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
private void createNotificationChannels() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
- notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
- Constants.DOWNLOAD_CHANNEL_NAME,
- NotificationManager.IMPORTANCE_DEFAULT));
- notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
- Constants.ACTIVITY_CHANNEL_NAME,
- NotificationManager.IMPORTANCE_DEFAULT));
- }
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+ final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
+ notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
+ Constants.DOWNLOAD_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
+ Constants.ACTIVITY_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ notificationManager.createNotificationChannel(new NotificationChannel(Constants.DM_UNREAD_CHANNEL_ID,
+ Constants.DM_UNREAD_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_DEFAULT));
+ final NotificationChannel silentNotificationChannel = new NotificationChannel(Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
+ Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_LOW);
+ silentNotificationChannel.setSound(null, null);
+ notificationManager.createNotificationChannel(silentNotificationChannel);
}
private void setupSuggestions() {
@@ -387,8 +420,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private void setupBottomNavigationBar(final boolean setDefaultFromSettings) {
int main_nav_ids = R.array.main_nav_ids;
- final String cookie = settingsHelper.getString(Constants.COOKIE);
- final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
if (!isLoggedIn) {
main_nav_ids = R.array.logged_out_main_nav_ids;
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
@@ -477,7 +508,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final int destinationId = destination.getId();
@SuppressLint("RestrictedApi") final Deque backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId);
- binding.bottomNavView.setVisibility(SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId) ? View.VISIBLE : View.GONE);
+ final boolean contains = SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId);
+ binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
+ if (contains && behavior != null) {
+ behavior.slideUp(binding.bottomNavView);
+ }
// explicitly hide keyboard when we navigate
final View view = getCurrentFocus();
@@ -518,6 +553,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
showActivityView();
return;
}
+ if (Constants.ACTION_SHOW_DM_THREAD.equals(action)) {
+ showThread(intent);
+ return;
+ }
if (Intent.ACTION_SEND.equals(action) && type != null) {
if (type.equals("text/plain")) {
handleUrl(intent.getStringExtra(Intent.EXTRA_TEXT));
@@ -531,6 +570,58 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
}
+ private void showThread(@NonNull final Intent intent) {
+ final String threadId = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID);
+ final String threadTitle = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE);
+ navigateToThread(threadId, threadTitle);
+ }
+
+ public void navigateToThread(final String threadId, final String threadTitle) {
+ if (threadId == null || threadTitle == null) return;
+ currentNavControllerLiveData.observe(this, new Observer() {
+ @Override
+ public void onChanged(final NavController navController) {
+ if (navController == null) return;
+ if (navController.getGraph().getId() != R.id.direct_messages_nav_graph) return;
+ try {
+ final NavDestination currentDestination = navController.getCurrentDestination();
+ if (currentDestination != null && currentDestination.getId() == R.id.directMessagesInboxFragment) {
+ // if we are already on the inbox page, navigate to the thread
+ // need handler.post() to wait for the fragment manager to be ready to navigate
+ new Handler().post(() -> {
+ final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
+ .actionInboxToThread(threadId, threadTitle);
+ navController.navigate(action);
+ });
+ return;
+ }
+ // add a destination change listener to navigate to thread once we are on the inbox page
+ navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
+ @Override
+ public void onDestinationChanged(@NonNull final NavController controller,
+ @NonNull final NavDestination destination,
+ @Nullable final Bundle arguments) {
+ if (destination.getId() == R.id.directMessagesInboxFragment) {
+ final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
+ .actionInboxToThread(threadId, threadTitle);
+ controller.navigate(action);
+ controller.removeOnDestinationChangedListener(this);
+ }
+ }
+ });
+ // pop back stack until we reach the inbox page
+ navController.popBackStack(R.id.directMessagesInboxFragment, false);
+ } finally {
+ currentNavControllerLiveData.removeObserver(this);
+ }
+ }
+ });
+ final int selectedItemId = binding.bottomNavView.getSelectedItemId();
+ if (selectedItemId != R.navigation.direct_messages_nav_graph) {
+ setBottomNavSelectedItem(R.navigation.direct_messages_nav_graph);
+ }
+ }
+
private void handleUrl(final String url) {
if (url == null) return;
// Log.d(TAG, url);
diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
index ae72002c..9fa309f2 100644
--- a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
+++ b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java
@@ -258,7 +258,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter list) {
diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
index ba3a7a17..2ccb5df3 100644
--- a/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
+++ b/app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
@@ -36,7 +36,9 @@ public final class DirectMessageInboxAdapter extends ListAdapter DIFF_CALLBACK = new DiffUtil.ItemCallback() {
@Override
public boolean areItemsTheSame(@NonNull final Media oldItem, @NonNull final Media newItem) {
- return oldItem.getPk().equals(newItem.getPk());
+ return Objects.equals(oldItem.getPk(), newItem.getPk());
}
@Override
public boolean areContentsTheSame(@NonNull final Media oldItem, @NonNull final Media newItem) {
final Caption oldItemCaption = oldItem.getCaption();
final Caption newItemCaption = newItem.getCaption();
- return oldItem.getPk().equals(newItem.getPk()) && Objects.equals(getCaptionText(oldItemCaption), getCaptionText(newItemCaption));
+ return Objects.equals(oldItem.getPk(), newItem.getPk())
+ && Objects.equals(getCaptionText(oldItemCaption), getCaptionText(newItemCaption));
}
private String getCaptionText(final Caption caption) {
diff --git a/app/src/main/java/awais/instagrabber/adapters/GifItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/GifItemsAdapter.java
new file mode 100644
index 00000000..83a6f62c
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/adapters/GifItemsAdapter.java
@@ -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 {
+
+ private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() {
+ @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 controllerListener = new BaseControllerListener() {
+ @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);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java
index e3e7f281..86b0d35b 100644
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/TopicClusterViewHolder.java
@@ -81,16 +81,18 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
}
if (bitmap != null) {
Palette.from(bitmap).generate(p -> {
- final Palette.Swatch swatch = p.getDominantSwatch();
final Resources resources = itemView.getResources();
int titleTextColor = resources.getColor(R.color.white);
- if (swatch != null) {
- backgroundColor.set(swatch.getRgb());
- GradientDrawable gd = new GradientDrawable(
- GradientDrawable.Orientation.TOP_BOTTOM,
- new int[]{Color.TRANSPARENT, backgroundColor.get()});
- titleTextColor = swatch.getTitleTextColor();
- binding.background.setBackground(gd);
+ if (p != null) {
+ final Palette.Swatch swatch = p.getDominantSwatch();
+ if (swatch != null) {
+ backgroundColor.set(swatch.getRgb());
+ GradientDrawable gd = new GradientDrawable(
+ GradientDrawable.Orientation.TOP_BOTTOM,
+ new int[]{Color.TRANSPARENT, backgroundColor.get()});
+ titleTextColor = swatch.getTitleTextColor();
+ binding.background.setBackground(gd);
+ }
}
titleColor.set(titleTextColor);
binding.title.setTextColor(titleTextColor);
@@ -127,8 +129,8 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
// binding.title.setTransitionName("title-" + topicCluster.getId());
binding.cover.setTransitionName("cover-" + topicCluster.getId());
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null
- ? topicCluster.getCoverMedia()
- : topicCluster.getCoverMedias().get(0));
+ ? topicCluster.getCoverMedia()
+ : topicCluster.getCoverMedias().get(0));
if (thumbUrl == null) {
binding.cover.setImageURI((String) null);
} else {
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
index dfecaa0c..f6b0f679 100644
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectInboxItemViewHolder.java
@@ -12,26 +12,17 @@ import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import com.google.common.collect.ImmutableList;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
-import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
-import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
-import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
-import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
-import awais.instagrabber.repositories.responses.directmessages.RavenExpiringMediaActionSummary;
-import awais.instagrabber.utils.ResponseBodyUtils;
+import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.TextUtils;
public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
@@ -133,218 +124,17 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
if (directStory != null && !directStory.getItems().isEmpty()) {
final DirectItem item = directStory.getItems().get(0);
final MediaItemType mediaType = item.getVisualMedia().getMedia().getMediaType();
- final String username = getUsername(thread.getUsers(), item.getUserId(), viewerId, resources);
- final String subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
+ final String username = DMUtils.getUsername(thread.getUsers(), item.getUserId(), viewerId, resources);
+ final String subtitle = DMUtils.getMediaSpecificSubtitle(username, resources, mediaType);
binding.subtitle.setText(subtitle);
return;
}
final DirectItem item = thread.getFirstDirectItem();
if (item == null) return;
- final long senderId = item.getUserId();
- final DirectItemType itemType = item.getItemType();
- String subtitle = null;
- final String username = getUsername(thread.getUsers(), senderId, viewerId, resources);
- String message = "";
- if (itemType == null) {
- message = resources.getString(R.string.dms_inbox_raven_message_unknown);
- } else {
- switch (itemType) {
- case TEXT:
- message = item.getText();
- break;
- case LIKE:
- message = item.getLike();
- break;
- case LINK:
- message = item.getLink().getText();
- break;
- case PLACEHOLDER:
- message = item.getPlaceholder().getMessage();
- break;
- case MEDIA_SHARE:
- subtitle = resources.getString(R.string.dms_inbox_shared_post, username != null ? username : "", item.getMediaShare().getUser().getUsername());
- break;
- case ANIMATED_MEDIA:
- subtitle = resources.getString(R.string.dms_inbox_shared_gif, username != null ? username : "");
- break;
- case PROFILE:
- subtitle = resources.getString(R.string.dms_inbox_shared_profile, username != null ? username : "", item.getProfile().getUsername());
- break;
- case LOCATION:
- subtitle = resources.getString(R.string.dms_inbox_shared_location, username != null ? username : "", item.getLocation().getName());
- break;
- case MEDIA: {
- final MediaItemType mediaType = item.getMedia().getMediaType();
- subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
- break;
- }
- case STORY_SHARE: {
- final String reelType = item.getStoryShare().getReelType();
- if (reelType == null) {
- subtitle = item.getStoryShare().getTitle();
- } else {
- final int format = reelType.equals("highlight_reel")
- ? R.string.dms_inbox_shared_highlight
- : R.string.dms_inbox_shared_story;
- subtitle = resources.getString(format, username != null ? username : "",
- item.getStoryShare().getMedia().getUser().getUsername());
- }
- break;
- }
- case VOICE_MEDIA:
- subtitle = resources.getString(R.string.dms_inbox_shared_voice, username != null ? username : "");
- break;
- case ACTION_LOG:
- subtitle = item.getActionLog().getDescription();
- break;
- case VIDEO_CALL_EVENT:
- subtitle = item.getVideoCallEvent().getDescription();
- break;
- case CLIP:
- subtitle = resources.getString(R.string.dms_inbox_shared_clip, username != null ? username : "",
- item.getClip().getClip().getUser().getUsername());
- break;
- case FELIX_SHARE:
- subtitle = resources.getString(R.string.dms_inbox_shared_igtv, username != null ? username : "",
- item.getFelixShare().getVideo().getUser().getUsername());
- break;
- case RAVEN_MEDIA:
- subtitle = getRavenMediaSubtitle(item, resources, username);
- break;
- case REEL_SHARE:
- final DirectItemReelShare reelShare = item.getReelShare();
- if (reelShare == null) {
- subtitle = "";
- break;
- }
- final String reelType = reelShare.getType();
- switch (reelType) {
- case "reply":
- if (viewerId == item.getUserId()) {
- subtitle = resources.getString(R.string.dms_inbox_replied_story_outgoing, reelShare.getText());
- } else {
- subtitle = resources.getString(R.string.dms_inbox_replied_story_incoming, username != null ? username : "", reelShare.getText());
- }
- break;
- case "mention":
- if (viewerId == item.getUserId()) {
- // You mentioned the other person
- final long mentionedUserId = item.getReelShare().getMentionedUserId();
- final String otherUsername = getUsername(thread.getUsers(), mentionedUserId, viewerId, resources);
- subtitle = resources.getString(R.string.dms_inbox_mentioned_story_outgoing, otherUsername);
- } else {
- // They mentioned you
- subtitle = resources.getString(R.string.dms_inbox_mentioned_story_incoming, username != null ? username : "");
- }
- break;
- case "reaction":
- if (viewerId == item.getUserId()) {
- subtitle = resources.getString(R.string.dms_inbox_reacted_story_outgoing, reelShare.getText());
- } else {
- subtitle = resources.getString(R.string.dms_inbox_reacted_story_incoming, username != null ? username : "", reelShare.getText());
- }
- break;
- default:
- subtitle = "";
- break;
- }
- break;
- default:
- message = resources.getString(R.string.dms_inbox_raven_message_unknown);
- }
- }
- if (subtitle == null) {
- if (thread.getUsers().size() > 1
- || (thread.getUsers().size() == 1 && senderId == viewerId)) {
- subtitle = String.format("%s: %s", username != null ? username : "", message);
- } else {
- subtitle = message;
- }
- }
+ final String subtitle = DMUtils.getMessageString(thread, resources, viewerId, item);
binding.subtitle.setText(subtitle != null ? subtitle : "");
}
- private String getMediaSpecificSubtitle(final String username, final Resources resources, final MediaItemType mediaType) {
- final String userSharedAnImage = resources.getString(R.string.dms_inbox_shared_image, username != null ? username : "");
- final String userSharedAVideo = resources.getString(R.string.dms_inbox_shared_video, username != null ? username : "");
- final String userSentAMessage = resources.getString(R.string.dms_inbox_shared_message, username != null ? username : "");
- String subtitle;
- switch (mediaType) {
- case MEDIA_TYPE_IMAGE:
- subtitle = userSharedAnImage;
- break;
- case MEDIA_TYPE_VIDEO:
- subtitle = userSharedAVideo;
- break;
- default:
- subtitle = userSentAMessage;
- break;
- }
- return subtitle;
- }
-
- private String getRavenMediaSubtitle(final DirectItem item,
- final Resources resources,
- final String username) {
- String subtitle = "↗ ";
- final DirectItemVisualMedia visualMedia = item.getVisualMedia();
- final RavenExpiringMediaActionSummary summary = visualMedia.getExpiringMediaActionSummary();
- if (summary != null) {
- final RavenExpiringMediaActionSummary.ActionType expiringMediaType = summary.getType();
- int textRes = 0;
- switch (expiringMediaType) {
- case DELIVERED:
- textRes = R.string.dms_inbox_raven_media_delivered;
- break;
- case SENT:
- textRes = R.string.dms_inbox_raven_media_sent;
- break;
- case OPENED:
- textRes = R.string.dms_inbox_raven_media_opened;
- break;
- case REPLAYED:
- textRes = R.string.dms_inbox_raven_media_replayed;
- break;
- case SENDING:
- textRes = R.string.dms_inbox_raven_media_sending;
- break;
- case BLOCKED:
- textRes = R.string.dms_inbox_raven_media_blocked;
- break;
- case SUGGESTED:
- textRes = R.string.dms_inbox_raven_media_suggested;
- break;
- case SCREENSHOT:
- textRes = R.string.dms_inbox_raven_media_screenshot;
- break;
- case CANNOT_DELIVER:
- textRes = R.string.dms_inbox_raven_media_cant_deliver;
- break;
- }
- if (textRes > 0) {
- subtitle += itemView.getContext().getString(textRes);
- }
- return subtitle;
- }
- final MediaItemType mediaType = visualMedia.getMedia().getMediaType();
- subtitle = getMediaSpecificSubtitle(username, resources, mediaType);
- return subtitle;
- }
-
- private String getUsername(final List users,
- final long userId,
- final long viewerId,
- final Resources resources) {
- if (userId == viewerId) {
- return resources.getString(R.string.you);
- }
- final Optional senderOptional = users.stream()
- .filter(Objects::nonNull)
- .filter(user -> user.getPk() == userId)
- .findFirst();
- return senderOptional.map(User::getUsername).orElse(null);
- }
-
private void setDateTime(@NonNull final DirectItem item) {
final long timestamp = item.getTimestamp() / 1000;
final String dateTimeString = TextUtils.getRelativeDateTimeString(itemView.getContext(), timestamp);
@@ -352,9 +142,7 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
}
private void setReadState(@NonNull final DirectThread thread) {
- final DirectItem item = thread.getItems().get(0);
- final Map lastSeenAtMap = thread.getLastSeenAt();
- final boolean read = ResponseBodyUtils.isRead(item, lastSeenAtMap, Collections.singletonList(thread.getViewerId()), thread.getDirectStory());
+ final boolean read = DMUtils.isRead(thread);
binding.unread.setVisibility(read ? View.GONE : View.VISIBLE);
binding.threadTitle.setTypeface(null, read ? Typeface.NORMAL : Typeface.BOLD);
binding.subtitle.setTypeface(null, read ? Typeface.NORMAL : Typeface.BOLD);
diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
index 571568bb..8271c38c 100644
--- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
+++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java
@@ -45,6 +45,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiR
import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions;
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
+import awais.instagrabber.utils.DMUtils;
import awais.instagrabber.utils.DeepLinkParser;
import awais.instagrabber.utils.ResponseBodyUtils;
@@ -196,7 +197,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
if (item.isPending()) {
binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
} else {
- final boolean read = ResponseBodyUtils.isRead(item, thread.getLastSeenAt(), userIds, null);
+ final boolean read = DMUtils.isRead(item,
+ thread.getLastSeenAt(),
+ userIds
+ );
binding.deliveryStatus.setImageResource(R.drawable.ic_check_all_24);
ImageViewCompat.setImageTintList(
binding.deliveryStatus,
@@ -321,8 +325,8 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
final String repliedToUsername = user != null ? user.getUsername() : "";
if (item.getUserId() == currentUser.getPk()) {
return thread.isGroup()
- ? resources.getString(R.string.replied_you_group, repliedToUsername)
- : resources.getString(R.string.replied_you);
+ ? resources.getString(R.string.replied_you_group, repliedToUsername)
+ : resources.getString(R.string.replied_you);
}
if (repliedToUserId == currentUser.getPk()) {
return resources.getString(R.string.replied_to_you);
@@ -361,7 +365,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
final DirectItemReactions reactions = item.getReactions();
final List emojis = reactions != null ? reactions.getEmojis() : null;
if (emojis == null || emojis.isEmpty()) {
- binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall);
+ binding.container.setPadding(messageInfoPaddingSmall, messageInfoPaddingSmall, messageInfoPaddingSmall, 0);
binding.reactionsWrapper.setVisibility(View.GONE);
return;
}
diff --git a/app/src/main/java/awais/instagrabber/asyncs/CreateThreadAction.java b/app/src/main/java/awais/instagrabber/asyncs/CreateThreadAction.java
index ba35ef7e..384b3bbd 100644
--- a/app/src/main/java/awais/instagrabber/asyncs/CreateThreadAction.java
+++ b/app/src/main/java/awais/instagrabber/asyncs/CreateThreadAction.java
@@ -5,7 +5,6 @@ import android.util.Log;
import androidx.annotation.NonNull;
-import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Locale;
@@ -13,15 +12,12 @@ import java.util.Locale;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
-import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.DirectMessagesService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
-import static awais.instagrabber.utils.Utils.settingsHelper;
-
public class CreateThreadAction extends AsyncTask {
private static final String TAG = "CommentAction";
diff --git a/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
index 5da3b5aa..612259bd 100755
--- a/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
+++ b/app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
@@ -5,9 +5,6 @@ import android.util.Log;
import androidx.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.List;
-
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.FriendshipStatus;
import awais.instagrabber.repositories.responses.User;
diff --git a/app/src/main/java/awais/instagrabber/db/AppDatabase.java b/app/src/main/java/awais/instagrabber/db/AppDatabase.java
index 11547601..63e2ce43 100644
--- a/app/src/main/java/awais/instagrabber/db/AppDatabase.java
+++ b/app/src/main/java/awais/instagrabber/db/AppDatabase.java
@@ -20,14 +20,16 @@ import java.util.Date;
import java.util.List;
import awais.instagrabber.db.dao.AccountDao;
+import awais.instagrabber.db.dao.DMLastNotifiedDao;
import awais.instagrabber.db.dao.FavoriteDao;
import awais.instagrabber.db.entities.Account;
+import awais.instagrabber.db.entities.DMLastNotified;
import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.utils.Utils;
-@Database(entities = {Account.class, Favorite.class},
- version = 4)
+@Database(entities = {Account.class, Favorite.class, DMLastNotified.class},
+ version = 5)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
private static final String TAG = AppDatabase.class.getSimpleName();
@@ -38,12 +40,14 @@ public abstract class AppDatabase extends RoomDatabase {
public abstract FavoriteDao favoriteDao();
+ public abstract DMLastNotifiedDao dmLastNotifiedDao();
+
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
- .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
+ .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.build();
}
}
@@ -140,6 +144,18 @@ public abstract class AppDatabase extends RoomDatabase {
}
};
+ static final Migration MIGRATION_4_5 = new Migration(4, 5) {
+ @Override
+ public void migrate(@NonNull final SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE IF NOT EXISTS `dm_last_notified` (" +
+ "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
+ "`thread_id` TEXT, " +
+ "`last_notified_msg_ts` INTEGER, " +
+ "`last_notified_at` INTEGER)");
+ database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `dm_last_notified` (`thread_id`)");
+ }
+ };
+
@NonNull
private static List backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
// check if old favorites table had the column query_display
diff --git a/app/src/main/java/awais/instagrabber/db/Converters.java b/app/src/main/java/awais/instagrabber/db/Converters.java
index 50252755..de10e3fc 100644
--- a/app/src/main/java/awais/instagrabber/db/Converters.java
+++ b/app/src/main/java/awais/instagrabber/db/Converters.java
@@ -2,6 +2,10 @@ package awais.instagrabber.db;
import androidx.room.TypeConverter;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.Date;
import awais.instagrabber.models.enums.FavoriteType;
@@ -30,4 +34,16 @@ public class Converters {
public static String favoriteTypeToString(FavoriteType favoriteType) {
return favoriteType == null ? null : favoriteType.toString();
}
+
+ @TypeConverter
+ public static LocalDateTime fromTimestampToLocalDateTime(Long value) {
+ if (value == null) return null;
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(value), ZoneOffset.systemDefault());
+ }
+
+ @TypeConverter
+ public static Long localDateTimeToTimestamp(LocalDateTime localDateTime) {
+ if (localDateTime == null) return null;
+ return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ }
}
diff --git a/app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java b/app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java
new file mode 100644
index 00000000..ddd6968c
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/dao/DMLastNotifiedDao.java
@@ -0,0 +1,34 @@
+package awais.instagrabber.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import java.util.List;
+
+import awais.instagrabber.db.entities.DMLastNotified;
+
+@Dao
+public interface DMLastNotifiedDao {
+
+ @Query("SELECT * FROM dm_last_notified")
+ List getAllDMDmLastNotified();
+
+ @Query("SELECT * FROM dm_last_notified WHERE thread_id = :threadId")
+ DMLastNotified findDMLastNotifiedByThreadId(String threadId);
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ List insertDMLastNotified(DMLastNotified... dmLastNotified);
+
+ @Update
+ void updateDMLastNotified(DMLastNotified... dmLastNotified);
+
+ @Delete
+ void deleteDMLastNotified(DMLastNotified... dmLastNotified);
+
+ @Query("DELETE from dm_last_notified")
+ void deleteAllDMLastNotified();
+}
diff --git a/app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java
new file mode 100644
index 00000000..70a9a171
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/datasources/DMLastNotifiedDataSource.java
@@ -0,0 +1,70 @@
+package awais.instagrabber.db.datasources;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import awais.instagrabber.db.AppDatabase;
+import awais.instagrabber.db.dao.DMLastNotifiedDao;
+import awais.instagrabber.db.entities.DMLastNotified;
+
+public class DMLastNotifiedDataSource {
+ private static final String TAG = DMLastNotifiedDataSource.class.getSimpleName();
+
+ private static DMLastNotifiedDataSource INSTANCE;
+
+ private final DMLastNotifiedDao dmLastNotifiedDao;
+
+ private DMLastNotifiedDataSource(final DMLastNotifiedDao dmLastNotifiedDao) {
+ this.dmLastNotifiedDao = dmLastNotifiedDao;
+ }
+
+ public static DMLastNotifiedDataSource getInstance(@NonNull Context context) {
+ if (INSTANCE == null) {
+ synchronized (DMLastNotifiedDataSource.class) {
+ if (INSTANCE == null) {
+ final AppDatabase database = AppDatabase.getDatabase(context);
+ INSTANCE = new DMLastNotifiedDataSource(database.dmLastNotifiedDao());
+ }
+ }
+ }
+ return INSTANCE;
+ }
+
+ @Nullable
+ public final DMLastNotified getDMLastNotified(final String threadId) {
+ return dmLastNotifiedDao.findDMLastNotifiedByThreadId(threadId);
+ }
+
+ @NonNull
+ public final List getAllDMDmLastNotified() {
+ return dmLastNotifiedDao.getAllDMDmLastNotified();
+ }
+
+ public final void insertOrUpdateDMLastNotified(final String threadId,
+ final LocalDateTime lastNotifiedMsgTs,
+ final LocalDateTime lastNotifiedAt) {
+ final DMLastNotified dmLastNotified = getDMLastNotified(threadId);
+ final DMLastNotified toUpdate = new DMLastNotified(dmLastNotified == null ? 0 : dmLastNotified.getId(),
+ threadId,
+ lastNotifiedMsgTs,
+ lastNotifiedAt);
+ if (dmLastNotified != null) {
+ dmLastNotifiedDao.updateDMLastNotified(toUpdate);
+ return;
+ }
+ dmLastNotifiedDao.insertDMLastNotified(toUpdate);
+ }
+
+ public final void deleteDMLastNotified(@NonNull final DMLastNotified dmLastNotified) {
+ dmLastNotifiedDao.deleteDMLastNotified(dmLastNotified);
+ }
+
+ public final void deleteAllDMLastNotified() {
+ dmLastNotifiedDao.deleteAllDMLastNotified();
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java b/app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java
new file mode 100644
index 00000000..c0ae776f
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/entities/DMLastNotified.java
@@ -0,0 +1,85 @@
+package awais.instagrabber.db.entities;
+
+import androidx.annotation.NonNull;
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+@Entity(tableName = DMLastNotified.TABLE_NAME, indices = {@Index(value = DMLastNotified.COL_THREAD_ID, unique = true)})
+public class DMLastNotified {
+ public final static String TABLE_NAME = "dm_last_notified";
+ public final static String COL_ID = "id";
+ public final static String COL_THREAD_ID = "thread_id";
+ public final static String COL_LAST_NOTIFIED_MSG_TS = "last_notified_msg_ts";
+ public final static String COL_LAST_NOTIFIED_AT = "last_notified_at";
+
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = COL_ID)
+ private final int id;
+
+ @ColumnInfo(name = COL_THREAD_ID)
+ private final String threadId;
+
+ @ColumnInfo(name = COL_LAST_NOTIFIED_MSG_TS)
+ private final LocalDateTime lastNotifiedMsgTs;
+
+ @ColumnInfo(name = COL_LAST_NOTIFIED_AT)
+ private final LocalDateTime lastNotifiedAt;
+
+ public DMLastNotified(final int id,
+ final String threadId,
+ final LocalDateTime lastNotifiedMsgTs,
+ final LocalDateTime lastNotifiedAt) {
+ this.id = id;
+ this.threadId = threadId;
+ this.lastNotifiedMsgTs = lastNotifiedMsgTs;
+ this.lastNotifiedAt = lastNotifiedAt;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getThreadId() {
+ return threadId;
+ }
+
+ public LocalDateTime getLastNotifiedMsgTs() {
+ return lastNotifiedMsgTs;
+ }
+
+ public LocalDateTime getLastNotifiedAt() {
+ return lastNotifiedAt;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final DMLastNotified that = (DMLastNotified) o;
+ return id == that.id &&
+ Objects.equals(threadId, that.threadId) &&
+ Objects.equals(lastNotifiedMsgTs, that.lastNotifiedMsgTs) &&
+ Objects.equals(lastNotifiedAt, that.lastNotifiedAt);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, threadId, lastNotifiedMsgTs, lastNotifiedAt);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "DMLastNotified{" +
+ "id=" + id +
+ ", threadId='" + threadId + '\'' +
+ ", lastNotifiedMsgTs='" + lastNotifiedMsgTs + '\'' +
+ ", lastNotifiedAt='" + lastNotifiedAt + '\'' +
+ '}';
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java b/app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java
new file mode 100644
index 00000000..9cb15291
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/db/repositories/DMLastNotifiedRepository.java
@@ -0,0 +1,126 @@
+package awais.instagrabber.db.repositories;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import awais.instagrabber.db.datasources.DMLastNotifiedDataSource;
+import awais.instagrabber.db.entities.DMLastNotified;
+import awais.instagrabber.utils.AppExecutors;
+
+public class DMLastNotifiedRepository {
+ private static final String TAG = DMLastNotifiedRepository.class.getSimpleName();
+
+ private static DMLastNotifiedRepository instance;
+
+ private final AppExecutors appExecutors;
+ private final DMLastNotifiedDataSource dmLastNotifiedDataSource;
+
+ private DMLastNotifiedRepository(final AppExecutors appExecutors, final DMLastNotifiedDataSource dmLastNotifiedDataSource) {
+ this.appExecutors = appExecutors;
+ this.dmLastNotifiedDataSource = dmLastNotifiedDataSource;
+ }
+
+ public static DMLastNotifiedRepository getInstance(final DMLastNotifiedDataSource dmLastNotifiedDataSource) {
+ if (instance == null) {
+ instance = new DMLastNotifiedRepository(AppExecutors.getInstance(), dmLastNotifiedDataSource);
+ }
+ return instance;
+ }
+
+ public void getDMLastNotified(final String threadId,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ final DMLastNotified dmLastNotified = dmLastNotifiedDataSource.getDMLastNotified(threadId);
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ if (dmLastNotified == null) {
+ callback.onDataNotAvailable();
+ return;
+ }
+ callback.onSuccess(dmLastNotified);
+ });
+ });
+ }
+
+ public void getAllDMDmLastNotified(final RepositoryCallback> callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ final List allDMDmLastNotified = dmLastNotifiedDataSource.getAllDMDmLastNotified();
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ if (allDMDmLastNotified == null) {
+ callback.onDataNotAvailable();
+ return;
+ }
+ // cachedAccounts = accounts;
+ callback.onSuccess(allDMDmLastNotified);
+ });
+ });
+ }
+
+ public void insertOrUpdateDMLastNotified(final List dmLastNotifiedList,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ for (final DMLastNotified dmLastNotified : dmLastNotifiedList) {
+ dmLastNotifiedDataSource.insertOrUpdateDMLastNotified(dmLastNotified.getThreadId(),
+ dmLastNotified.getLastNotifiedMsgTs(),
+ dmLastNotified.getLastNotifiedAt());
+ }
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ callback.onSuccess(null);
+ });
+ });
+ }
+
+ public void insertOrUpdateDMLastNotified(final String threadId,
+ final LocalDateTime lastNotifiedMsgTs,
+ final LocalDateTime lastNotifiedAt,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ dmLastNotifiedDataSource.insertOrUpdateDMLastNotified(threadId, lastNotifiedMsgTs, lastNotifiedAt);
+ final DMLastNotified updated = dmLastNotifiedDataSource.getDMLastNotified(threadId);
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ if (updated == null) {
+ callback.onDataNotAvailable();
+ return;
+ }
+ callback.onSuccess(updated);
+ });
+ });
+ }
+
+ public void deleteDMLastNotified(final DMLastNotified dmLastNotified,
+ final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ dmLastNotifiedDataSource.deleteDMLastNotified(dmLastNotified);
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ callback.onSuccess(null);
+ });
+ });
+ }
+
+ public void deleteAllDMLastNotified(final RepositoryCallback callback) {
+ // request on the I/O thread
+ appExecutors.diskIO().execute(() -> {
+ dmLastNotifiedDataSource.deleteAllDMLastNotified();
+ // notify on the main thread
+ appExecutors.mainThread().execute(() -> {
+ if (callback == null) return;
+ callback.onSuccess(null);
+ });
+ });
+ }
+
+}
diff --git a/app/src/main/java/awais/instagrabber/dialogs/GifPickerBottomDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/GifPickerBottomDialogFragment.java
new file mode 100644
index 00000000..a7ab61e4
--- /dev/null
+++ b/app/src/main/java/awais/instagrabber/dialogs/GifPickerBottomDialogFragment.java
@@ -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 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 callback = new Debouncer.Callback() {
+ @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);
+ }
+}
diff --git a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
index 09862f49..affe5cba 100644
--- a/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
+++ b/app/src/main/java/awais/instagrabber/dialogs/ProfilePicDialogFragment.java
@@ -41,20 +41,26 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfilePicDialogFragment extends DialogFragment {
private static final String TAG = "ProfilePicDlgFragment";
- private final long id;
- private final String name;
- private final String fallbackUrl;
+ private long id;
+ private String name;
+ private String fallbackUrl;
private boolean isLoggedIn;
private DialogProfilepicBinding binding;
private String url;
- public ProfilePicDialogFragment(final long id, final String name, final String fallbackUrl) {
- this.id = id;
- this.name = name;
- this.fallbackUrl = fallbackUrl;
+ public static ProfilePicDialogFragment getInstance(final long id, final String name, final String fallbackUrl) {
+ final Bundle args = new Bundle();
+ args.putLong("id", id);
+ args.putString("name", name);
+ args.putString("fallbackUrl", fallbackUrl);
+ final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment();
+ fragment.setArguments(args);
+ return fragment;
}
+ public ProfilePicDialogFragment() {}
+
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
@@ -94,6 +100,14 @@ public class ProfilePicDialogFragment extends DialogFragment {
}
private void init() {
+ final Bundle arguments = getArguments();
+ if (arguments == null) {
+ dismiss();
+ return;
+ }
+ id = arguments.getLong("id");
+ name = arguments.getString("name");
+ fallbackUrl = arguments.getString("fallbackUrl");
binding.download.setOnClickListener(v -> {
final Context context = getContext();
if (context == null) return;
@@ -127,11 +141,12 @@ public class ProfilePicDialogFragment extends DialogFragment {
@Override
public void onFailure(final Throwable t) {
final Context context = getContext();
- try {
- Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
+ if (context == null) {
+ dismiss();
+ return;
}
- catch(final Throwable e) {}
- getDialog().dismiss();
+ Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
+ dismiss();
}
});
} else setupPhoto(fallbackUrl);
diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
index ee2852c8..d21e750e 100644
--- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
@@ -416,6 +416,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
// binding.locationBiography.setCaptionIsExpandable(true);
// binding.locationBiography.setCaptionIsExpanded(true);
+ final Context context = getContext();
+ if (context == null) return;
if (TextUtils.isEmpty(biography)) {
locationDetailsBinding.locationBiography.setVisibility(View.GONE);
} else {
@@ -432,13 +434,13 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final String originalText = autoLinkItem.getOriginalText().trim();
navigateToProfile(originalText);
});
- locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(getContext(),
+ locationDetailsBinding.locationBiography.addOnEmailClickListener(autoLinkItem -> Utils.openEmailAddress(context,
autoLinkItem.getOriginalText()
.trim()));
locationDetailsBinding.locationBiography
- .addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim()));
+ .addOnURLClickListener(autoLinkItem -> Utils.openURL(context, autoLinkItem.getOriginalText().trim()));
locationDetailsBinding.locationBiography.setOnLongClickListener(v -> {
- Utils.copyText(getContext(), biography);
+ Utils.copyText(context, biography);
return true;
});
}
@@ -465,7 +467,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
locationDetailsBinding.locationUrl.setVisibility(View.VISIBLE);
locationDetailsBinding.locationUrl.setText(TextUtils.getSpannableUrl(url));
}
- final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(getContext());
+ final FavoriteDataSource dataSource = FavoriteDataSource.getInstance(context);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(dataSource);
locationDetailsBinding.favChip.setVisibility(View.VISIBLE);
favoriteRepository.getFavorite(String.valueOf(locationId), FavoriteType.LOCATION, new RepositoryCallback() {
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
index 5d086d53..d2db9f8f 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
@@ -29,6 +29,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.bottomnavigation.BottomNavigationView;
+import com.google.android.material.snackbar.Snackbar;
import java.util.List;
@@ -172,6 +173,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
}
private void setupObservers() {
+ removeViewModelObservers();
threadsObserver = list -> {
if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> {
@@ -181,8 +183,28 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
});
};
viewModel.getThreads().observe(fragmentActivity, threadsObserver);
- viewModel.getFetchingInbox().observe(getViewLifecycleOwner(), fetching -> binding.swipeRefreshLayout.setRefreshing(fetching));
- viewModel.getUnseenCount().observe(getViewLifecycleOwner(), this::setBottomNavBarBadge);
+ viewModel.getInbox().observe(getViewLifecycleOwner(), inboxResource -> {
+ if (inboxResource == null) return;
+ switch (inboxResource.status) {
+ case SUCCESS:
+ binding.swipeRefreshLayout.setRefreshing(false);
+ break;
+ case ERROR:
+ if (inboxResource.message != null) {
+ Snackbar.make(binding.getRoot(), inboxResource.message, Snackbar.LENGTH_LONG).show();
+ }
+ binding.swipeRefreshLayout.setRefreshing(false);
+ break;
+ case LOADING:
+ binding.swipeRefreshLayout.setRefreshing(true);
+ break;
+ }
+ });
+ viewModel.getUnseenCount().observe(getViewLifecycleOwner(), unseenCountResource -> {
+ if (unseenCountResource == null) return;
+ final Integer unseenCount = unseenCountResource.data;
+ setBottomNavBarBadge(unseenCount == null ? 0 : unseenCount);
+ });
viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
}
@@ -230,11 +252,10 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
inboxAdapter = new DirectMessageInboxAdapter(thread -> {
if (navigating) return;
navigating = true;
- final Bundle bundle = new Bundle();
- bundle.putString("threadId", thread.getThreadId());
- bundle.putString("title", thread.getThreadTitle());
if (isAdded()) {
- NavHostFragment.findNavController(this).navigate(R.id.action_inbox_to_thread, bundle);
+ final DirectMessageInboxFragmentDirections.ActionInboxToThread directions = DirectMessageInboxFragmentDirections
+ .actionInboxToThread(thread.getThreadId(), thread.getThreadTitle());
+ NavHostFragment.findNavController(this).navigate(directions);
}
navigating = false;
});
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
index f29dd393..d35ecd09 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java
@@ -11,13 +11,11 @@ import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
-import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
@@ -31,13 +29,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R;
import awais.instagrabber.UserSearchNavGraphDirections;
+import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DirectPendingUsersAdapter;
import awais.instagrabber.adapters.DirectPendingUsersAdapter.PendingUser;
import awais.instagrabber.adapters.DirectPendingUsersAdapter.PendingUserCallback;
@@ -52,12 +50,11 @@ import awais.instagrabber.fragments.UserSearchFragment;
import awais.instagrabber.fragments.UserSearchFragmentDirections;
import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.User;
-import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
-import awais.instagrabber.viewmodels.DirectInboxViewModel;
-import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
+import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectSettingsViewModel;
+import awais.instagrabber.viewmodels.factories.DirectSettingsViewModelFactory;
public class DirectMessageSettingsFragment extends Fragment implements ConfirmDialogFragmentCallback {
private static final String TAG = DirectMessageSettingsFragment.class.getSimpleName();
@@ -77,33 +74,14 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
super.onCreate(savedInstanceState);
final Bundle arguments = getArguments();
if (arguments == null) return;
- final NavController navController = NavHostFragment.findNavController(this);
- final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
final DirectMessageSettingsFragmentArgs args = DirectMessageSettingsFragmentArgs.fromBundle(arguments);
- final boolean pending = args.getPending();
- final List threads;
- final User viewer;
- if (pending) {
- final DirectPendingInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
- threads = inboxViewModel.getThreads().getValue();
- viewer = inboxViewModel.getViewer();
- } else {
- final DirectInboxViewModel inboxViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
- threads = inboxViewModel.getThreads().getValue();
- viewer = inboxViewModel.getViewer();
- }
- final String threadId = args.getThreadId();
- final Optional first = threads != null ? threads.stream()
- .filter(thread -> thread.getThreadId().equals(threadId))
- .findFirst()
- : Optional.empty();
- if (!first.isPresent()) {
- navController.navigateUp();
- return;
- }
- viewModel = new ViewModelProvider(this).get(DirectSettingsViewModel.class);
- viewModel.setViewer(viewer);
- viewModel.setThread(first.get());
+ final MainActivity fragmentActivity = (MainActivity) requireActivity();
+ final AppStateViewModel appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
+ viewModel = new ViewModelProvider(this, new DirectSettingsViewModelFactory(fragmentActivity.getApplication(),
+ args.getThreadId(),
+ args.getPending(),
+ appStateViewModel.getCurrentUser()))
+ .get(DirectSettingsViewModel.class);
}
@NonNull
@@ -143,21 +121,23 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
binding.muteMessages.setVisibility(View.GONE);
}
});
- viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
+ // Need to observe, so that getValue is correct
+ viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {});
+ viewModel.getLeftUsers().observe(getViewLifecycleOwner(), users -> {});
+ viewModel.getUsersAndLeftUsers().observe(getViewLifecycleOwner(), usersPair -> {
if (usersAdapter == null) return;
- usersAdapter.submitUsers(users.first, users.second);
+ usersAdapter.submitUsers(usersPair.first, usersPair.second);
});
viewModel.getTitle().observe(getViewLifecycleOwner(), title -> binding.titleEdit.setText(title));
viewModel.getAdminUserIds().observe(getViewLifecycleOwner(), adminUserIds -> {
if (usersAdapter == null) return;
usersAdapter.setAdminUserIds(adminUserIds);
});
- viewModel.getMuted().observe(getViewLifecycleOwner(), muted -> binding.muteMessages.setChecked(muted));
+ viewModel.isMuted().observe(getViewLifecycleOwner(), muted -> binding.muteMessages.setChecked(muted));
viewModel.isPending().observe(getViewLifecycleOwner(), pending -> binding.muteMessages.setVisibility(pending ? View.GONE : View.VISIBLE));
- if (viewModel.isViewerAdmin()) {
- viewModel.getApprovalRequiredToJoin().observe(getViewLifecycleOwner(), required -> binding.approvalRequired.setChecked(required));
- viewModel.getPendingRequests().observe(getViewLifecycleOwner(), this::setPendingRequests);
- }
+ viewModel.isViewerAdmin().observe(getViewLifecycleOwner(), this::setApprovalRelatedUI);
+ viewModel.getApprovalRequiredToJoin().observe(getViewLifecycleOwner(), required -> binding.approvalRequired.setChecked(required));
+ viewModel.getPendingRequests().observe(getViewLifecycleOwner(), this::setPendingRequests);
final NavController navController = NavHostFragment.findNavController(this);
final NavBackStackEntry backStackEntry = navController.getCurrentBackStackEntry();
if (backStackEntry != null) {
@@ -192,7 +172,11 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
private void addMembers(final Set users) {
final Boolean approvalRequired = viewModel.getApprovalRequiredToJoin().getValue();
- if (!viewModel.isViewerAdmin() && approvalRequired != null && approvalRequired) {
+ Boolean isViewerAdmin = viewModel.isViewerAdmin().getValue();
+ if (isViewerAdmin == null) {
+ isViewerAdmin = false;
+ }
+ if (!isViewerAdmin && approvalRequired != null && approvalRequired) {
approvalRequiredUsers = users;
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
APPROVAL_REQUIRED_REQUEST_CODE,
@@ -226,13 +210,15 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
}
private void setupSettings() {
- binding.groupSettings.setVisibility(viewModel.isGroup() ? View.VISIBLE : View.GONE);
+ Boolean isGroup = viewModel.isGroup().getValue();
+ if (isGroup == null) isGroup = false;
+ binding.groupSettings.setVisibility(isGroup ? View.VISIBLE : View.GONE);
binding.muteMessagesLabel.setOnClickListener(v -> binding.muteMessages.toggle());
binding.muteMessages.setOnCheckedChangeListener((buttonView, isChecked) -> {
final LiveData> resourceLiveData = isChecked ? viewModel.mute() : viewModel.unmute();
handleSwitchChangeResource(resourceLiveData, buttonView);
});
- if (!viewModel.isGroup()) return;
+ if (!isGroup) return;
binding.titleEdit.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
@@ -256,14 +242,13 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final NavDestination currentDestination = navController.getCurrentDestination();
if (currentDestination == null) return;
if (currentDestination.getId() != R.id.directMessagesSettingsFragment) return;
- final Pair, List> users = viewModel.getUsers().getValue();
+ final List users = viewModel.getUsers().getValue();
final long[] currentUserIds;
- if (users != null && users.first != null) {
- final List currentMembers = users.first;
- currentUserIds = currentMembers.stream()
- .mapToLong(User::getPk)
- .sorted()
- .toArray();
+ if (users != null) {
+ currentUserIds = users.stream()
+ .mapToLong(User::getPk)
+ .sorted()
+ .toArray();
} else {
currentUserIds = new long[0];
}
@@ -281,7 +266,6 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final LiveData> resourceLiveData = isChecked ? viewModel.muteMentions() : viewModel.unmuteMentions();
handleSwitchChangeResource(resourceLiveData, buttonView);
});
- setApprovalRelatedUI();
binding.leave.setOnClickListener(v -> {
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
LEAVE_THREAD_REQUEST_CODE,
@@ -293,7 +277,9 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
);
confirmDialogFragment.show(getChildFragmentManager(), "leave_thread_confirmation_dialog");
});
- if (viewModel.isViewerAdmin()) {
+ Boolean isViewerAdmin = viewModel.isViewerAdmin().getValue();
+ if (isViewerAdmin == null) isViewerAdmin = false;
+ if (isViewerAdmin) {
binding.end.setVisibility(View.VISIBLE);
binding.end.setOnClickListener(v -> {
final ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(
@@ -311,8 +297,8 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
}
}
- private void setApprovalRelatedUI() {
- if (!viewModel.isViewerAdmin()) {
+ private void setApprovalRelatedUI(final boolean isViewerAdmin) {
+ if (!isViewerAdmin) {
binding.pendingMembersGroup.setVisibility(View.GONE);
binding.approvalRequired.setVisibility(View.GONE);
binding.approvalRequiredLabel.setVisibility(View.GONE);
@@ -352,7 +338,7 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi
final Context context = getContext();
if (context == null) return;
binding.users.setLayoutManager(new LinearLayoutManager(context));
- final User inviter = viewModel.getThread().getInviter();
+ final User inviter = viewModel.getInviter().getValue();
usersAdapter = new DirectUsersAdapter(
inviter != null ? inviter.getPk() : -1,
(position, user, selected) -> {
diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
index 7fa55fea..4ce2ef40 100644
--- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
+++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
@@ -30,14 +30,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
-import androidx.lifecycle.ViewModelStoreOwner;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
@@ -58,7 +56,6 @@ import com.google.common.collect.ImmutableList;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
import java.util.Set;
import awais.instagrabber.ProfileNavGraphDirections;
@@ -83,6 +80,7 @@ import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCall
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
+import awais.instagrabber.dialogs.GifPickerBottomDialogFragment;
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.UserSearchFragment;
@@ -104,9 +102,8 @@ import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.AppStateViewModel;
-import awais.instagrabber.viewmodels.DirectInboxViewModel;
-import awais.instagrabber.viewmodels.DirectPendingInboxViewModel;
import awais.instagrabber.viewmodels.DirectThreadViewModel;
+import awais.instagrabber.viewmodels.factories.DirectThreadViewModelFactory;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
@@ -232,7 +229,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
@Override
public void onReaction(final DirectItem item, final Emoji emoji) {
if (item == null) return;
- final LiveData> resourceLiveData = viewModel.sendReaction(item, emoji);
+ final LiveData> resourceLiveData = viewModel.sendReaction(item, emoji);
if (resourceLiveData != null) {
resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
}
@@ -292,13 +289,29 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
};
private final MutableLiveData inputLength = new MutableLiveData<>(0);
private ItemTouchHelper itemTouchHelper;
+ private LiveData pendingLiveData;
+ private LiveData threadLiveData;
+ private LiveData inputModeLiveData;
+ private LiveData threadTitleLiveData;
+ private LiveData> fetchingLiveData;
+ private LiveData> itemsLiveData;
+ private LiveData replyToItemLiveData;
+ private LiveData pendingRequestsCountLiveData;
+ private LiveData> usersLiveData;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity();
appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
- viewModel = new ViewModelProvider(this).get(DirectThreadViewModel.class);
+ final Bundle arguments = getArguments();
+ if (arguments == null) return;
+ final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(arguments);
+ viewModel = new ViewModelProvider(this, new DirectThreadViewModelFactory(fragmentActivity.getApplication(),
+ fragmentArgs.getThreadId(),
+ fragmentArgs.getPending(),
+ appStateViewModel.getCurrentUser()))
+ .get(DirectThreadViewModel.class);
setHasOptionsMenu(true);
}
@@ -329,7 +342,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
init();
binding.send.post(() -> initialSendX = binding.send.getX());
shouldRefresh = false;
- setObservers();
}
@Override
@@ -403,6 +415,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
wasKbShowing = true;
binding.emojiPicker.setAlpha(0);
}
+ removeObservers();
super.onPause();
}
@@ -420,7 +433,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
binding.send.stopScale();
setupBackStackResultObserver();
- attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
+ setObservers();
+ // attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
}
@Override
@@ -460,42 +474,38 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (context == null) return;
if (getArguments() == null) return;
actionBar = fragmentActivity.getSupportActionBar();
- final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(getArguments());
- viewModel.getThreadTitle().postValue(fragmentArgs.getTitle());
- final String threadId = fragmentArgs.getThreadId();
- viewModel.setThreadId(threadId);
setupList();
root.post(this::setupInput);
- root.post(this::getInitialData);
+ // root.post(this::getInitialData);
}
- private void getInitialData() {
- final Bundle arguments = getArguments();
- if (arguments == null) return;
- final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
- final boolean pending = args.getPending();
- final NavController navController = NavHostFragment.findNavController(this);
- final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
- final List threads;
- if (!pending) {
- final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
- threads = threadListViewModel.getThreads().getValue();
- } else {
- final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
- threads = threadListViewModel.getThreads().getValue();
- }
- final Optional first = threads != null
- ? threads.stream()
- .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
- .findFirst()
- : Optional.empty();
- if (first.isPresent()) {
- final DirectThread thread = first.get();
- viewModel.setThread(thread);
- return;
- }
- viewModel.fetchChats();
- }
+ // private void getInitialData() {
+ // final Bundle arguments = getArguments();
+ // if (arguments == null) return;
+ // final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
+ // final boolean pending = args.getPending();
+ // final NavController navController = NavHostFragment.findNavController(this);
+ // final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
+ // final List threads;
+ // if (!pending) {
+ // final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
+ // threads = threadListViewModel.getThreads().getValue();
+ // } else {
+ // final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
+ // threads = threadListViewModel.getThreads().getValue();
+ // }
+ // final Optional first = threads != null
+ // ? threads.stream()
+ // .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
+ // .findFirst()
+ // : Optional.empty();
+ // if (first.isPresent()) {
+ // final DirectThread thread = first.get();
+ // viewModel.setThread(thread);
+ // return;
+ // }
+ // viewModel.fetchChats();
+ // }
private void setupList() {
final Context context = getContext();
@@ -539,7 +549,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
private void setObservers() {
- viewModel.isPending().observe(getViewLifecycleOwner(), isPending -> {
+ threadLiveData = viewModel.getThread();
+ if (threadLiveData == null) {
+ final NavController navController = NavHostFragment.findNavController(this);
+ navController.navigateUp();
+ return;
+ }
+ pendingLiveData = viewModel.isPending();
+ pendingLiveData.observe(getViewLifecycleOwner(), isPending -> {
if (isPending == null) {
hideInput();
return;
@@ -553,7 +570,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (inputMode != null && inputMode == 1) return;
showInput();
});
- viewModel.getInputMode().observe(getViewLifecycleOwner(), inputMode -> {
+ inputModeLiveData = viewModel.getInputMode();
+ inputModeLiveData.observe(getViewLifecycleOwner(), inputMode -> {
final Boolean isPending = viewModel.isPending().getValue();
if (isPending != null && isPending) return;
if (inputMode == null || inputMode == 0) return;
@@ -561,21 +579,34 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
hideInput();
}
});
- viewModel.getThreadTitle().observe(getViewLifecycleOwner(), this::setTitle);
- viewModel.getFetching().observe(getViewLifecycleOwner(), fetching -> {
- if (fetching) {
- setTitle(getString(R.string.dms_thread_updating));
- return;
+ threadTitleLiveData = viewModel.getThreadTitle();
+ threadTitleLiveData.observe(getViewLifecycleOwner(), this::setTitle);
+ fetchingLiveData = viewModel.isFetching();
+ fetchingLiveData.observe(getViewLifecycleOwner(), fetchingResource -> {
+ if (fetchingResource == null) return;
+ switch (fetchingResource.status) {
+ case SUCCESS:
+ case ERROR:
+ setTitle(viewModel.getThreadTitle().getValue());
+ if (fetchingResource.message != null) {
+ Snackbar.make(binding.getRoot(), fetchingResource.message, Snackbar.LENGTH_LONG).show();
+ }
+ break;
+ case LOADING:
+ setTitle(getString(R.string.dms_thread_updating));
+ break;
}
- setTitle(viewModel.getThreadTitle().getValue());
});
- final ItemsAdapterDataMerger itemsAdapterDataMerger = new ItemsAdapterDataMerger(appStateViewModel.getCurrentUser(), viewModel.getThread());
- itemsAdapterDataMerger.observe(getViewLifecycleOwner(), userThreadPair -> {
- viewModel.setCurrentUser(userThreadPair.first);
- setupItemsAdapter(userThreadPair.first, userThreadPair.second);
- });
- viewModel.getItems().observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
- viewModel.getReplyToItem().observe(getViewLifecycleOwner(), item -> {
+ // final ItemsAdapterDataMerger itemsAdapterDataMerger = new ItemsAdapterDataMerger(appStateViewModel.getCurrentUser(), viewModel.getThread());
+ // itemsAdapterDataMerger.observe(getViewLifecycleOwner(), userThreadPair -> {
+ // viewModel.setCurrentUser(userThreadPair.first);
+ // setupItemsAdapter(userThreadPair.first, userThreadPair.second);
+ // });
+ threadLiveData.observe(getViewLifecycleOwner(), this::setupItemsAdapter);
+ itemsLiveData = viewModel.getItems();
+ itemsLiveData.observe(getViewLifecycleOwner(), this::submitItemsToAdapter);
+ replyToItemLiveData = viewModel.getReplyToItem();
+ replyToItemLiveData.observe(getViewLifecycleOwner(), item -> {
if (item == null) {
if (binding.input.length() == 0) {
showExtraInputOption(true);
@@ -630,14 +661,30 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}
prevLength = length;
});
- viewModel.getPendingRequestsCount().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
- viewModel.getUsers().observe(getViewLifecycleOwner(), users -> {
+ pendingRequestsCountLiveData = viewModel.getPendingRequestsCount();
+ pendingRequestsCountLiveData.observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
+ usersLiveData = viewModel.getUsers();
+ usersLiveData.observe(getViewLifecycleOwner(), users -> {
if (users == null || users.isEmpty()) return;
final User user = users.get(0);
binding.acceptPendingRequestQuestion.setText(getString(R.string.accept_request_from_user, user.getUsername(), user.getFullName()));
});
}
+ private void removeObservers() {
+ pendingLiveData.removeObservers(getViewLifecycleOwner());
+ inputModeLiveData.removeObservers(getViewLifecycleOwner());
+ threadTitleLiveData.removeObservers(getViewLifecycleOwner());
+ fetchingLiveData.removeObservers(getViewLifecycleOwner());
+ threadLiveData.removeObservers(getViewLifecycleOwner());
+ itemsLiveData.removeObservers(getViewLifecycleOwner());
+ replyToItemLiveData.removeObservers(getViewLifecycleOwner());
+ inputLength.removeObservers(getViewLifecycleOwner());
+ pendingRequestsCountLiveData.removeObservers(getViewLifecycleOwner());
+ usersLiveData.removeObservers(getViewLifecycleOwner());
+
+ }
+
private void hidePendingOptions() {
binding.acceptPendingRequestQuestion.setVisibility(View.GONE);
binding.decline.setVisibility(View.GONE);
@@ -666,9 +713,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
case SUCCESS:
resourceLiveData.removeObservers(getViewLifecycleOwner());
if (isDecline) {
+ removeObservers();
+ viewModel.removeThread();
final NavController navController = NavHostFragment.findNavController(this);
navController.navigateUp();
+ return;
}
+ removeObservers();
+ viewModel.moveFromPending();
+ setObservers();
break;
case LOADING:
break;
@@ -684,6 +737,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void hideInput() {
binding.emojiToggle.setVisibility(View.GONE);
+ binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
binding.input.setVisibility(View.GONE);
@@ -697,6 +751,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void showInput() {
binding.emojiToggle.setVisibility(View.VISIBLE);
+ binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
binding.input.setVisibility(View.VISIBLE);
@@ -735,16 +790,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
binding.send.setListenForRecord(true);
startIconAnimation();
}
- binding.gallery.setVisibility(View.VISIBLE);
+ binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
+ binding.gallery.setVisibility(View.VISIBLE);
return;
}
if (binding.send.isListenForRecord()) {
binding.send.setListenForRecord(false);
startIconAnimation();
}
- binding.gallery.setVisibility(View.GONE);
+ binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
+ binding.gallery.setVisibility(View.GONE);
}
private String getDirectItemPreviewText(final DirectItem item) {
@@ -835,12 +892,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
});
}
- private void setupItemsAdapter(final User currentUser, final DirectThread thread) {
+ private void setupItemsAdapter(final DirectThread thread) {
+ if (thread == null) return;
if (itemsAdapter != null) {
if (itemsAdapter.getThread() == thread) return;
itemsAdapter.setThread(thread);
return;
}
+ final User currentUser = appStateViewModel.getCurrentUser();
+ if (currentUser == null) return;
itemsAdapter = new DirectItemsAdapter(currentUser, thread, directItemCallback, directItemLongClickListener);
itemsAdapter.setHasStableIds(true);
itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
@@ -881,8 +941,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onStart() {
isRecording = true;
binding.input.setHint(null);
- binding.gallery.setVisibility(View.GONE);
+ binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
+ binding.gallery.setVisibility(View.GONE);
if (PermissionUtils.hasAudioRecordPerms(context)) {
viewModel.startRecording();
return;
@@ -902,8 +963,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onFinish(final long recordTime) {
Log.d(TAG, "onFinish");
binding.input.setHint("Message");
- binding.gallery.setVisibility(View.VISIBLE);
+ binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
+ binding.gallery.setVisibility(View.VISIBLE);
viewModel.stopRecording(false);
isRecording = false;
}
@@ -915,16 +977,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (PermissionUtils.hasAudioRecordPerms(context)) {
tooltip.show(binding.send);
}
- binding.gallery.setVisibility(View.VISIBLE);
+ binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
+ binding.gallery.setVisibility(View.VISIBLE);
viewModel.stopRecording(true);
isRecording = false;
}
});
binding.recordView.setOnBasketAnimationEndListener(() -> {
binding.input.setHint(R.string.dms_thread_message_hint);
- binding.gallery.setVisibility(View.VISIBLE);
+ binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
+ binding.gallery.setVisibility(View.VISIBLE);
});
binding.input.addTextChangedListener(new TextWatcherAdapter() {
// int prevLength = 0;
@@ -955,7 +1019,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
binding.send.setOnRecordClickListener(v -> {
final Editable text = binding.input.getText();
if (TextUtils.isEmpty(text)) return;
- final LiveData> resourceLiveData = viewModel.sendText(text.toString());
+ final LiveData> resourceLiveData = viewModel.sendText(text.toString());
resourceLiveData.observe(getViewLifecycleOwner(), resource -> handleSentMessage(resourceLiveData));
binding.input.setText("");
viewModel.setReplyToItem(null);
@@ -1001,6 +1065,16 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
mediaPicker.show(getChildFragmentManager(), "MediaPicker");
hideKeyboard(true);
});
+ binding.gif.setOnClickListener(v -> {
+ final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance();
+ gifPicker.setOnSelectListener(giphyGif -> {
+ gifPicker.dismiss();
+ if (giphyGif == null) return;
+ handleSentMessage(viewModel.sendAnimatedMedia(giphyGif));
+ });
+ gifPicker.show(getChildFragmentManager(), "GifPicker");
+ hideKeyboard(true);
+ });
binding.camera.setOnClickListener(v -> {
final Intent intent = new Intent(context, CameraActivity.class);
startActivityForResult(intent, CAMERA_REQUEST_CODE);
@@ -1028,8 +1102,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
navController.navigate(navDirections);
}
- private void handleSentMessage(final LiveData> resourceLiveData) {
- final Resource resource = resourceLiveData.getValue();
+ private void handleSentMessage(final LiveData> resourceLiveData) {
+ final Resource