Migrate ProfileFragment to kotlin and use viewmodel
This commit is contained in:
parent
bdad254f5d
commit
27d919e6b2
@ -111,8 +111,10 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
try {
|
try {
|
||||||
DownloadUtils.init(this,
|
DownloadUtils.init(
|
||||||
Utils.settingsHelper.getString(PreferenceKeys.PREF_BARINSTA_DIR_URI))
|
this,
|
||||||
|
Utils.settingsHelper.getString(PreferenceKeys.PREF_BARINSTA_DIR_URI)
|
||||||
|
)
|
||||||
} catch (e: ReselectDocumentTreeException) {
|
} catch (e: ReselectDocumentTreeException) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val intent = Intent(this, DirectorySelectActivity::class.java)
|
val intent = Intent(this, DirectorySelectActivity::class.java)
|
||||||
@ -324,6 +326,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
// } catch (e: Exception) {
|
// } catch (e: Exception) {
|
||||||
// Log.e(TAG, "onDestroy: ", e)
|
// Log.e(TAG, "onDestroy: ", e)
|
||||||
// }
|
// }
|
||||||
|
DownloadUtils.destroy()
|
||||||
instance = null
|
instance = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,21 +361,27 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
private fun createNotificationChannels() {
|
private fun createNotificationChannels() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
val notificationManager = NotificationManagerCompat.from(applicationContext)
|
val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||||
notificationManager.createNotificationChannel(NotificationChannel(
|
notificationManager.createNotificationChannel(
|
||||||
Constants.DOWNLOAD_CHANNEL_ID,
|
NotificationChannel(
|
||||||
Constants.DOWNLOAD_CHANNEL_NAME,
|
Constants.DOWNLOAD_CHANNEL_ID,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
Constants.DOWNLOAD_CHANNEL_NAME,
|
||||||
))
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
notificationManager.createNotificationChannel(NotificationChannel(
|
)
|
||||||
Constants.ACTIVITY_CHANNEL_ID,
|
)
|
||||||
Constants.ACTIVITY_CHANNEL_NAME,
|
notificationManager.createNotificationChannel(
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationChannel(
|
||||||
))
|
Constants.ACTIVITY_CHANNEL_ID,
|
||||||
notificationManager.createNotificationChannel(NotificationChannel(
|
Constants.ACTIVITY_CHANNEL_NAME,
|
||||||
Constants.DM_UNREAD_CHANNEL_ID,
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
Constants.DM_UNREAD_CHANNEL_NAME,
|
)
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
)
|
||||||
))
|
notificationManager.createNotificationChannel(
|
||||||
|
NotificationChannel(
|
||||||
|
Constants.DM_UNREAD_CHANNEL_ID,
|
||||||
|
Constants.DM_UNREAD_CHANNEL_NAME,
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
)
|
||||||
|
)
|
||||||
val silentNotificationChannel = NotificationChannel(
|
val silentNotificationChannel = NotificationChannel(
|
||||||
Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
|
Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
|
||||||
Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
|
Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
|
||||||
@ -404,7 +413,8 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
supportFragmentManager,
|
supportFragmentManager,
|
||||||
R.id.main_nav_host,
|
R.id.main_nav_host,
|
||||||
intent,
|
intent,
|
||||||
firstFragmentGraphIndex)
|
firstFragmentGraphIndex
|
||||||
|
)
|
||||||
navControllerLiveData.observe(this, { navController: NavController? -> setupNavigation(binding.toolbar, navController) })
|
navControllerLiveData.observe(this, { navController: NavController? -> setupNavigation(binding.toolbar, navController) })
|
||||||
currentNavControllerLiveData = navControllerLiveData
|
currentNavControllerLiveData = navControllerLiveData
|
||||||
}
|
}
|
||||||
@ -432,27 +442,33 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
|
|
||||||
private fun setupAnonBottomNav(): List<Tab> {
|
private fun setupAnonBottomNav(): List<Tab> {
|
||||||
val selectedItemId = binding.bottomNavView.selectedItemId
|
val selectedItemId = binding.bottomNavView.selectedItemId
|
||||||
val favoriteTab = Tab(R.drawable.ic_star_24,
|
val favoriteTab = Tab(
|
||||||
|
R.drawable.ic_star_24,
|
||||||
getString(R.string.title_favorites),
|
getString(R.string.title_favorites),
|
||||||
false,
|
false,
|
||||||
"favorites_nav_graph",
|
"favorites_nav_graph",
|
||||||
R.navigation.favorites_nav_graph,
|
R.navigation.favorites_nav_graph,
|
||||||
R.id.favorites_nav_graph,
|
R.id.favorites_nav_graph,
|
||||||
R.id.favoritesFragment)
|
R.id.favoritesFragment
|
||||||
val profileTab = Tab(R.drawable.ic_person_24,
|
)
|
||||||
|
val profileTab = Tab(
|
||||||
|
R.drawable.ic_person_24,
|
||||||
getString(R.string.profile),
|
getString(R.string.profile),
|
||||||
false,
|
false,
|
||||||
"profile_nav_graph",
|
"profile_nav_graph",
|
||||||
R.navigation.profile_nav_graph,
|
R.navigation.profile_nav_graph,
|
||||||
R.id.profile_nav_graph,
|
R.id.profile_nav_graph,
|
||||||
R.id.profileFragment)
|
R.id.profileFragment
|
||||||
val moreTab = Tab(R.drawable.ic_more_horiz_24,
|
)
|
||||||
|
val moreTab = Tab(
|
||||||
|
R.drawable.ic_more_horiz_24,
|
||||||
getString(R.string.more),
|
getString(R.string.more),
|
||||||
false,
|
false,
|
||||||
"more_nav_graph",
|
"more_nav_graph",
|
||||||
R.navigation.more_nav_graph,
|
R.navigation.more_nav_graph,
|
||||||
R.id.more_nav_graph,
|
R.id.more_nav_graph,
|
||||||
R.id.morePreferencesFragment)
|
R.id.morePreferencesFragment
|
||||||
|
)
|
||||||
val menu = binding.bottomNavView.menu
|
val menu = binding.bottomNavView.menu
|
||||||
menu.clear()
|
menu.clear()
|
||||||
menu.add(0, favoriteTab.navigationRootId, 0, favoriteTab.title).setIcon(favoriteTab.iconResId)
|
menu.add(0, favoriteTab.navigationRootId, 0, favoriteTab.title).setIcon(favoriteTab.iconResId)
|
||||||
@ -489,9 +505,15 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
if (destination.id == R.id.directMessagesThreadFragment && arguments != null) {
|
if (destination.id == R.id.directMessagesThreadFragment && arguments != null) {
|
||||||
// Set the thread title earlier for better ux
|
// Set the thread title earlier for better ux
|
||||||
val title = arguments.getString("title")
|
val title = arguments.getString("title")
|
||||||
val actionBar = supportActionBar
|
if (!title.isNullOrBlank()) {
|
||||||
if (actionBar != null && !isEmpty(title)) {
|
supportActionBar?.title = title
|
||||||
actionBar.title = title
|
}
|
||||||
|
}
|
||||||
|
if (destination.id == R.id.profileFragment && arguments != null) {
|
||||||
|
// Set the title to username
|
||||||
|
val username = arguments.getString("username")
|
||||||
|
if (!username.isNullOrBlank()) {
|
||||||
|
supportActionBar?.title = username.substringAfter("@")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// below is a hack to check if we are at the end of the current stack, to setup the search view
|
// below is a hack to check if we are at the end of the current stack, to setup the search view
|
||||||
@ -764,7 +786,8 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
|
|||||||
"com.google.android.gms.fonts",
|
"com.google.android.gms.fonts",
|
||||||
"com.google.android.gms",
|
"com.google.android.gms",
|
||||||
"Noto Color Emoji Compat",
|
"Noto Color Emoji Compat",
|
||||||
R.array.com_google_android_gms_fonts_certs)
|
R.array.com_google_android_gms_fonts_certs
|
||||||
|
)
|
||||||
val config: EmojiCompat.Config = FontRequestEmojiCompatConfig(applicationContext, fontRequest)
|
val config: EmojiCompat.Config = FontRequestEmojiCompatConfig(applicationContext, fontRequest)
|
||||||
config.setReplaceAll(true) // .setUseEmojiAsDefaultStyle(true)
|
config.setReplaceAll(true) // .setUseEmojiAsDefaultStyle(true)
|
||||||
.registerInitCallback(object : InitCallback() {
|
.registerInitCallback(object : InitCallback() {
|
||||||
|
@ -156,6 +156,13 @@ public class RamboTextViewV2 extends AutoLinkTextView {
|
|||||||
onEmailClickListeners.clear();
|
onEmailClickListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearAllAutoLinkListeners() {
|
||||||
|
clearOnMentionClickListeners();
|
||||||
|
clearOnHashtagClickListeners();
|
||||||
|
clearOnURLClickListeners();
|
||||||
|
clearOnEmailClickListeners();
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnMentionClickListener {
|
public interface OnMentionClickListener {
|
||||||
void onMentionClick(final AutoLinkItem autoLinkItem);
|
void onMentionClick(final AutoLinkItem autoLinkItem);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.common.primitives.Booleans;
|
import com.google.common.primitives.Booleans;
|
||||||
@ -36,18 +38,21 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
private List<Option<?>> options;
|
private List<Option<?>> options;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static <E extends Serializable> MultiOptionDialogFragment<E> newInstance(@StringRes final int title,
|
public static <E extends Serializable> MultiOptionDialogFragment<E> newInstance(final int requestCode,
|
||||||
|
@StringRes final int title,
|
||||||
@NonNull final ArrayList<Option<E>> options) {
|
@NonNull final ArrayList<Option<E>> options) {
|
||||||
return newInstance(title, 0, 0, options, Type.SINGLE);
|
return newInstance(requestCode, title, 0, 0, options, Type.SINGLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static <E extends Serializable> MultiOptionDialogFragment<E> newInstance(@StringRes final int title,
|
public static <E extends Serializable> MultiOptionDialogFragment<E> newInstance(final int requestCode,
|
||||||
|
@StringRes final int title,
|
||||||
@StringRes final int positiveButtonText,
|
@StringRes final int positiveButtonText,
|
||||||
@StringRes final int negativeButtonText,
|
@StringRes final int negativeButtonText,
|
||||||
@NonNull final ArrayList<Option<E>> options,
|
@NonNull final ArrayList<Option<E>> options,
|
||||||
@NonNull final Type type) {
|
@NonNull final Type type) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
args.putInt("requestCode", requestCode);
|
||||||
args.putInt("title", title);
|
args.putInt("title", title);
|
||||||
args.putInt("positiveButtonText", positiveButtonText);
|
args.putInt("positiveButtonText", positiveButtonText);
|
||||||
args.putInt("negativeButtonText", negativeButtonText);
|
args.putInt("negativeButtonText", negativeButtonText);
|
||||||
@ -58,10 +63,28 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull final Context context) {
|
public void onAttach(@NonNull final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
final Fragment parentFragment = getParentFragment();
|
||||||
|
if (parentFragment != null) {
|
||||||
|
if (parentFragment instanceof MultiOptionDialogCallback) {
|
||||||
|
callback = (MultiOptionDialogCallback) parentFragment;
|
||||||
|
}
|
||||||
|
if (parentFragment instanceof MultiOptionDialogSingleCallback) {
|
||||||
|
singleCallback = (MultiOptionDialogSingleCallback) parentFragment;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final FragmentActivity fragmentActivity = getActivity();
|
||||||
|
if (fragmentActivity instanceof MultiOptionDialogCallback) {
|
||||||
|
callback = (MultiOptionDialogCallback) fragmentActivity;
|
||||||
|
}
|
||||||
|
if (fragmentActivity instanceof MultiOptionDialogSingleCallback) {
|
||||||
|
singleCallback = (MultiOptionDialogSingleCallback) fragmentActivity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -69,12 +92,15 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final Bundle arguments = getArguments();
|
final Bundle arguments = getArguments();
|
||||||
int title = 0;
|
int title = 0;
|
||||||
|
int rc = 0;
|
||||||
if (arguments != null) {
|
if (arguments != null) {
|
||||||
|
rc = arguments.getInt("requestCode");
|
||||||
title = arguments.getInt("title");
|
title = arguments.getInt("title");
|
||||||
type = (Type) arguments.getSerializable("type");
|
type = (Type) arguments.getSerializable("type");
|
||||||
}
|
}
|
||||||
|
final int requestCode = rc;
|
||||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
|
||||||
if (title > 0) {
|
if (title != 0) {
|
||||||
builder.setTitle(title);
|
builder.setTitle(title);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@ -89,11 +115,11 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
if (negativeButtonText > 0) {
|
if (negativeButtonText > 0) {
|
||||||
builder.setNegativeButton(negativeButtonText, (dialog, which) -> {
|
builder.setNegativeButton(negativeButtonText, (dialog, which) -> {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onCancel();
|
callback.onCancel(requestCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (singleCallback != null) {
|
if (singleCallback != null) {
|
||||||
singleCallback.onCancel();
|
singleCallback.onCancel(requestCode);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -113,7 +139,7 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
final Option<T> option = (Option<T>) options.get(position);
|
final Option<T> option = (Option<T>) options.get(position);
|
||||||
selected.add(option.value);
|
selected.add(option.value);
|
||||||
}
|
}
|
||||||
callback.onMultipleSelect(selected);
|
callback.onMultipleSelect(requestCode, selected);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "onCreateDialog: ", e);
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
}
|
}
|
||||||
@ -133,7 +159,7 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
try {
|
try {
|
||||||
final Option<?> option = options.get(which);
|
final Option<?> option = options.get(which);
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
callback.onCheckChange((T) option.value, isChecked);
|
callback.onCheckChange(requestCode, (T) option.value, isChecked);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "onCreateDialog: ", e);
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
}
|
}
|
||||||
@ -157,7 +183,7 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
try {
|
try {
|
||||||
final Option<?> option = options.get(which);
|
final Option<?> option = options.get(which);
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
callback.onCheckChange((T) option.value, true);
|
callback.onCheckChange(requestCode, (T) option.value, true);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "onCreateDialog: ", e);
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
}
|
}
|
||||||
@ -168,7 +194,7 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
try {
|
try {
|
||||||
final Option<?> option = options.get(which);
|
final Option<?> option = options.get(which);
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
singleCallback.onSelect((T) option.value);
|
singleCallback.onSelect(requestCode, (T) option.value);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "onCreateDialog: ", e);
|
Log.e(TAG, "onCreateDialog: ", e);
|
||||||
}
|
}
|
||||||
@ -190,19 +216,19 @@ public class MultiOptionDialogFragment<T extends Serializable> extends DialogFra
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface MultiOptionDialogCallback<T> {
|
public interface MultiOptionDialogCallback<T> {
|
||||||
void onSelect(T result);
|
void onSelect(int requestCode, T result);
|
||||||
|
|
||||||
void onMultipleSelect(List<T> result);
|
void onMultipleSelect(int requestCode, List<T> result);
|
||||||
|
|
||||||
void onCheckChange(T item, boolean isChecked);
|
void onCheckChange(int requestCode, T item, boolean isChecked);
|
||||||
|
|
||||||
void onCancel();
|
void onCancel(int requestCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MultiOptionDialogSingleCallback<T> {
|
public interface MultiOptionDialogSingleCallback<T> {
|
||||||
void onSelect(T result);
|
void onSelect(int requestCode, T result);
|
||||||
|
|
||||||
void onCancel();
|
void onCancel(int requestCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Option<T extends Serializable> {
|
public static class Option<T extends Serializable> {
|
||||||
|
@ -2,7 +2,6 @@ package awais.instagrabber.fragments;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -31,8 +30,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.view.GestureDetectorCompat;
|
import androidx.core.view.GestureDetectorCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
@ -98,7 +95,7 @@ import awais.instagrabber.viewmodels.ArchivesViewModel;
|
|||||||
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
|
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
|
||||||
import awais.instagrabber.viewmodels.HighlightsViewModel;
|
import awais.instagrabber.viewmodels.HighlightsViewModel;
|
||||||
import awais.instagrabber.viewmodels.StoriesViewModel;
|
import awais.instagrabber.viewmodels.StoriesViewModel;
|
||||||
import awais.instagrabber.webservices.DirectMessagesService;
|
import awais.instagrabber.webservices.DirectMessagesRepository;
|
||||||
import awais.instagrabber.webservices.MediaRepository;
|
import awais.instagrabber.webservices.MediaRepository;
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
import awais.instagrabber.webservices.StoriesRepository;
|
import awais.instagrabber.webservices.StoriesRepository;
|
||||||
@ -148,7 +145,7 @@ public class StoryViewerFragment extends Fragment {
|
|||||||
// private boolean isHighlight;
|
// private boolean isHighlight;
|
||||||
// private boolean isArchive;
|
// private boolean isArchive;
|
||||||
// private boolean isNotification;
|
// private boolean isNotification;
|
||||||
private DirectMessagesService directMessagesService;
|
private DirectMessagesRepository directMessagesRepository;
|
||||||
private StoryViewerOptions options;
|
private StoryViewerOptions options;
|
||||||
private String csrfToken;
|
private String csrfToken;
|
||||||
private String deviceId;
|
private String deviceId;
|
||||||
@ -164,7 +161,7 @@ public class StoryViewerFragment extends Fragment {
|
|||||||
fragmentActivity = (AppCompatActivity) requireActivity();
|
fragmentActivity = (AppCompatActivity) requireActivity();
|
||||||
storiesRepository = StoriesRepository.Companion.getInstance();
|
storiesRepository = StoriesRepository.Companion.getInstance();
|
||||||
mediaRepository = MediaRepository.Companion.getInstance();
|
mediaRepository = MediaRepository.Companion.getInstance();
|
||||||
directMessagesService = DirectMessagesService.INSTANCE;
|
directMessagesRepository = DirectMessagesRepository.Companion.getInstance();
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +215,7 @@ public class StoryViewerFragment extends Fragment {
|
|||||||
final AlertDialog ad = new AlertDialog.Builder(context)
|
final AlertDialog ad = new AlertDialog.Builder(context)
|
||||||
.setTitle(R.string.reply_story)
|
.setTitle(R.string.reply_story)
|
||||||
.setView(input)
|
.setView(input)
|
||||||
.setPositiveButton(R.string.confirm, (d, w) -> directMessagesService.broadcastStoryReply(
|
.setPositiveButton(R.string.confirm, (d, w) -> directMessagesRepository.broadcastStoryReply(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
userId,
|
userId,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
@ -308,9 +308,9 @@ class DirectMessageSettingsFragment : Fragment(), ConfirmDialogFragmentCallback
|
|||||||
{ _: Int, user: User? ->
|
{ _: Int, user: User? ->
|
||||||
val options = viewModel.createUserOptions(user)
|
val options = viewModel.createUserOptions(user)
|
||||||
if (options.isEmpty()) return@DirectUsersAdapter true
|
if (options.isEmpty()) return@DirectUsersAdapter true
|
||||||
val fragment = MultiOptionDialogFragment.newInstance(-1, options)
|
val fragment = MultiOptionDialogFragment.newInstance(0, -1, options)
|
||||||
fragment.setSingleCallback(object : MultiOptionDialogSingleCallback<String?> {
|
fragment.setSingleCallback(object : MultiOptionDialogSingleCallback<String?> {
|
||||||
override fun onSelect(action: String?) {
|
override fun onSelect(requestCode: Int, action: String?) {
|
||||||
if (action == null) return
|
if (action == null) return
|
||||||
val resourceLiveData = viewModel.doAction(user, action)
|
val resourceLiveData = viewModel.doAction(user, action)
|
||||||
if (resourceLiveData != null) {
|
if (resourceLiveData != null) {
|
||||||
@ -318,7 +318,7 @@ class DirectMessageSettingsFragment : Fragment(), ConfirmDialogFragmentCallback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancel() {}
|
override fun onCancel(requestCode: Int) {}
|
||||||
})
|
})
|
||||||
val fragmentManager = childFragmentManager
|
val fragmentManager = childFragmentManager
|
||||||
fragment.show(fragmentManager, "actions")
|
fragment.show(fragmentManager, "actions")
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,981 @@
|
|||||||
|
package awais.instagrabber.fragments.main
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.style.RelativeSizeSpan
|
||||||
|
import android.text.style.StyleSpan
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.*
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||||
|
import awais.instagrabber.R
|
||||||
|
import awais.instagrabber.activities.MainActivity
|
||||||
|
import awais.instagrabber.adapters.FeedAdapterV2
|
||||||
|
import awais.instagrabber.adapters.HighlightsAdapter
|
||||||
|
import awais.instagrabber.asyncs.ProfilePostFetchService
|
||||||
|
import awais.instagrabber.customviews.PrimaryActionModeCallback
|
||||||
|
import awais.instagrabber.customviews.RamboTextViewV2
|
||||||
|
import awais.instagrabber.customviews.RamboTextViewV2.*
|
||||||
|
import awais.instagrabber.databinding.FragmentProfileBinding
|
||||||
|
import awais.instagrabber.db.repositories.AccountRepository
|
||||||
|
import awais.instagrabber.db.repositories.FavoriteRepository
|
||||||
|
import awais.instagrabber.dialogs.ConfirmDialogFragment
|
||||||
|
import awais.instagrabber.dialogs.ConfirmDialogFragment.ConfirmDialogFragmentCallback
|
||||||
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment
|
||||||
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment.MultiOptionDialogSingleCallback
|
||||||
|
import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option
|
||||||
|
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment
|
||||||
|
import awais.instagrabber.dialogs.ProfilePicDialogFragment
|
||||||
|
import awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG
|
||||||
|
import awais.instagrabber.fragments.PostViewV2Fragment
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragment
|
||||||
|
import awais.instagrabber.fragments.UserSearchFragmentDirections
|
||||||
|
import awais.instagrabber.managers.DirectMessagesManager
|
||||||
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.models.enums.PostItemType
|
||||||
|
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
||||||
|
import awais.instagrabber.repositories.responses.FriendshipStatus
|
||||||
|
import awais.instagrabber.repositories.responses.Media
|
||||||
|
import awais.instagrabber.repositories.responses.User
|
||||||
|
import awais.instagrabber.repositories.responses.UserProfileContextLink
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
||||||
|
import awais.instagrabber.utils.*
|
||||||
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
|
import awais.instagrabber.utils.extensions.isReallyPrivate
|
||||||
|
import awais.instagrabber.utils.extensions.trimAll
|
||||||
|
import awais.instagrabber.viewmodels.AppStateViewModel
|
||||||
|
import awais.instagrabber.viewmodels.ProfileFragmentViewModel
|
||||||
|
import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileEvent.*
|
||||||
|
import awais.instagrabber.viewmodels.ProfileFragmentViewModelFactory
|
||||||
|
import awais.instagrabber.webservices.*
|
||||||
|
|
||||||
|
class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCallback, MultiOptionDialogSingleCallback<String> {
|
||||||
|
private var backStackSavedStateResultLiveData: MutableLiveData<Any?>? = null
|
||||||
|
private var shareDmMenuItem: MenuItem? = null
|
||||||
|
private var shareLinkMenuItem: MenuItem? = null
|
||||||
|
private var removeFollowerMenuItem: MenuItem? = null
|
||||||
|
private var chainingMenuItem: MenuItem? = null
|
||||||
|
private var mutePostsMenuItem: MenuItem? = null
|
||||||
|
private var muteStoriesMenuItem: MenuItem? = null
|
||||||
|
private var restrictMenuItem: MenuItem? = null
|
||||||
|
private var blockMenuItem: MenuItem? = null
|
||||||
|
private var setupPostsDone: Boolean = false
|
||||||
|
private var selectedMedia: List<Media>? = null
|
||||||
|
private var actionMode: ActionMode? = null
|
||||||
|
private var disableDm: Boolean = false
|
||||||
|
private var shouldRefresh: Boolean = true
|
||||||
|
private var highlightsAdapter: HighlightsAdapter? = null
|
||||||
|
private var layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_PROFILE_POSTS_LAYOUT)
|
||||||
|
|
||||||
|
private lateinit var mainActivity: MainActivity
|
||||||
|
private lateinit var root: MotionLayout
|
||||||
|
private lateinit var binding: FragmentProfileBinding
|
||||||
|
private lateinit var appStateViewModel: AppStateViewModel
|
||||||
|
private lateinit var viewModel: ProfileFragmentViewModel
|
||||||
|
|
||||||
|
private val confirmDialogFragmentRequestCode = 100
|
||||||
|
private val ppOptsDialogRequestCode = 101
|
||||||
|
private val bioDialogRequestCode = 102
|
||||||
|
private val translationDialogRequestCode = 103
|
||||||
|
private val feedItemCallback: FeedAdapterV2.FeedItemCallback = object : FeedAdapterV2.FeedItemCallback {
|
||||||
|
override fun onPostClick(media: Media?, profilePicView: View?, mainPostImage: View?) {
|
||||||
|
openPostDialog(media ?: return, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProfilePicClick(media: Media?, profilePicView: View?) {
|
||||||
|
navigateToProfile(media?.user?.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNameClick(media: Media?, profilePicView: View?) {
|
||||||
|
navigateToProfile(media?.user?.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocationClick(media: Media?) {
|
||||||
|
val action = FeedFragmentDirections.actionGlobalLocationFragment(media?.location?.pk ?: return)
|
||||||
|
NavHostFragment.findNavController(this@ProfileFragment).navigate(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMentionClick(mention: String?) {
|
||||||
|
navigateToProfile(mention?.trimAll() ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHashtagClick(hashtag: String?) {
|
||||||
|
val action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag ?: return)
|
||||||
|
NavHostFragment.findNavController(this@ProfileFragment).navigate(action)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCommentsClick(media: Media?) {
|
||||||
|
val commentsAction = ProfileFragmentDirections.actionGlobalCommentsViewerFragment(
|
||||||
|
media?.code ?: return,
|
||||||
|
media.pk ?: return,
|
||||||
|
media.user?.pk ?: return
|
||||||
|
)
|
||||||
|
NavHostFragment.findNavController(this@ProfileFragment).navigate(commentsAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDownloadClick(media: Media?, childPosition: Int) {
|
||||||
|
DownloadUtils.showDownloadDialog(context ?: return, media ?: return, childPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEmailClick(emailId: String?) {
|
||||||
|
Utils.openEmailAddress(context ?: return, emailId ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onURLClick(url: String?) {
|
||||||
|
Utils.openURL(context ?: return, url ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSliderClick(media: Media?, position: Int) {
|
||||||
|
openPostDialog(media ?: return, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val onBackPressedCallback = object : OnBackPressedCallback(false) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
binding.postsRecyclerView.endSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val multiSelectAction = PrimaryActionModeCallback(
|
||||||
|
R.menu.multi_select_download_menu,
|
||||||
|
object : PrimaryActionModeCallback.CallbacksHelper() {
|
||||||
|
override fun onDestroy(mode: ActionMode?) {
|
||||||
|
binding.postsRecyclerView.endSelection()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
|
val item1 = item ?: return false
|
||||||
|
if (item1.itemId == R.id.action_download) {
|
||||||
|
val selectedMedia = this@ProfileFragment.selectedMedia ?: return false
|
||||||
|
val context = context ?: return false
|
||||||
|
DownloadUtils.download(context, selectedMedia)
|
||||||
|
binding.postsRecyclerView.endSelection()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
private val selectionModeCallback = object : FeedAdapterV2.SelectionModeCallback {
|
||||||
|
override fun onSelectionStart() {
|
||||||
|
if (!onBackPressedCallback.isEnabled) {
|
||||||
|
onBackPressedCallback.isEnabled = true
|
||||||
|
mainActivity.onBackPressedDispatcher.addCallback(viewLifecycleOwner, onBackPressedCallback)
|
||||||
|
}
|
||||||
|
if (actionMode == null) {
|
||||||
|
actionMode = mainActivity.startActionMode(multiSelectAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelectionChange(mediaSet: Set<Media>?) {
|
||||||
|
if (mediaSet == null) {
|
||||||
|
selectedMedia = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val title = getString(R.string.number_selected, mediaSet.size)
|
||||||
|
actionMode?.title = title
|
||||||
|
selectedMedia = mediaSet.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSelectionEnd() {
|
||||||
|
if (onBackPressedCallback.isEnabled) {
|
||||||
|
onBackPressedCallback.isEnabled = false
|
||||||
|
onBackPressedCallback.remove()
|
||||||
|
}
|
||||||
|
(actionMode ?: return).finish()
|
||||||
|
actionMode = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val onProfilePicClickListener = View.OnClickListener {
|
||||||
|
val hasStories = viewModel.userStories.value?.data?.isNotEmpty() ?: false
|
||||||
|
if (!hasStories) {
|
||||||
|
showProfilePicDialog()
|
||||||
|
return@OnClickListener
|
||||||
|
}
|
||||||
|
val dialog = MultiOptionDialogFragment.newInstance(
|
||||||
|
ppOptsDialogRequestCode,
|
||||||
|
0,
|
||||||
|
arrayListOf(
|
||||||
|
Option(getString(R.string.view_pfp), "profile_pic"),
|
||||||
|
Option(getString(R.string.show_stories), "show_stories")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
dialog.show(childFragmentManager, MultiOptionDialogFragment::class.java.simpleName)
|
||||||
|
}
|
||||||
|
private val onFollowersClickListener = View.OnClickListener {
|
||||||
|
try {
|
||||||
|
val action = ProfileFragmentDirections.actionProfileFragmentToFollowViewerFragment(
|
||||||
|
viewModel.profile.value?.data?.pk ?: return@OnClickListener,
|
||||||
|
true,
|
||||||
|
viewModel.profile.value?.data?.username ?: return@OnClickListener
|
||||||
|
)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onFollowersClickListener: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val onFollowingClickListener = View.OnClickListener {
|
||||||
|
try {
|
||||||
|
val action = ProfileFragmentDirections.actionProfileFragmentToFollowViewerFragment(
|
||||||
|
viewModel.profile.value?.data?.pk ?: return@OnClickListener,
|
||||||
|
false,
|
||||||
|
viewModel.profile.value?.data?.username ?: return@OnClickListener
|
||||||
|
)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onFollowersClickListener: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val onEmailClickListener = OnEmailClickListener {
|
||||||
|
Utils.openEmailAddress(context ?: return@OnEmailClickListener, it.originalText.trimAll())
|
||||||
|
}
|
||||||
|
private val onHashtagClickListener = OnHashtagClickListener {
|
||||||
|
try {
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString(ARG_HASHTAG, it.originalText.trimAll())
|
||||||
|
NavHostFragment.findNavController(this).navigate(R.id.action_global_hashTagFragment, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onHashtagClickListener: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val onMentionClickListener = OnMentionClickListener {
|
||||||
|
navigateToProfile(it.originalText.trimAll())
|
||||||
|
}
|
||||||
|
private val onURLClickListener = OnURLClickListener {
|
||||||
|
Utils.openURL(context ?: return@OnURLClickListener, it.originalText.trimAll())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private val backStackSavedStateObserver = Observer<Any?> { result ->
|
||||||
|
if (result == null) return@Observer
|
||||||
|
if ((result is RankedRecipient)) {
|
||||||
|
if (context != null) {
|
||||||
|
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
viewModel.shareDm(result)
|
||||||
|
} else if ((result is Set<*>)) {
|
||||||
|
try {
|
||||||
|
if (context != null) {
|
||||||
|
Toast.makeText(context, R.string.sending, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
viewModel.shareDm(result as Set<RankedRecipient>)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "share: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clear result
|
||||||
|
backStackSavedStateResultLiveData?.postValue(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openPostDialog(media: Media, position: Int) {
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putSerializable(PostViewV2Fragment.ARG_MEDIA, media)
|
||||||
|
putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val navController = NavHostFragment.findNavController(this)
|
||||||
|
navController.navigate(R.id.action_global_post_view, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "openPostDialog: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
appStateViewModel = ViewModelProvider(mainActivity).get(AppStateViewModel::class.java)
|
||||||
|
val cookie = Utils.settingsHelper.getString(Constants.COOKIE)
|
||||||
|
val deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID)
|
||||||
|
val csrfToken = getCsrfTokenFromCookie(cookie)
|
||||||
|
val userId = getUserIdFromCookie(cookie)
|
||||||
|
val isLoggedIn = !csrfToken.isNullOrBlank() && userId != 0L && deviceUuid.isNotBlank()
|
||||||
|
viewModel = ViewModelProvider(
|
||||||
|
this,
|
||||||
|
ProfileFragmentViewModelFactory(
|
||||||
|
csrfToken,
|
||||||
|
deviceUuid,
|
||||||
|
UserRepository.getInstance(),
|
||||||
|
FriendshipRepository.getInstance(),
|
||||||
|
StoriesRepository.getInstance(),
|
||||||
|
MediaRepository.getInstance(),
|
||||||
|
GraphQLRepository.getInstance(),
|
||||||
|
AccountRepository.getInstance(requireContext()),
|
||||||
|
FavoriteRepository.getInstance(requireContext()),
|
||||||
|
DirectMessagesRepository.getInstance(),
|
||||||
|
if (isLoggedIn) DirectMessagesManager else null,
|
||||||
|
this,
|
||||||
|
arguments
|
||||||
|
)
|
||||||
|
).get(ProfileFragmentViewModel::class.java)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
|
if (this::root.isInitialized) {
|
||||||
|
shouldRefresh = false
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
appStateViewModel.currentUserLiveData.observe(viewLifecycleOwner, viewModel::setCurrentUser)
|
||||||
|
binding = FragmentProfileBinding.inflate(inflater, container, false)
|
||||||
|
root = binding.root
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
if (!shouldRefresh) {
|
||||||
|
setupObservers()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
init()
|
||||||
|
shouldRefresh = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRefresh() {
|
||||||
|
viewModel.refresh()
|
||||||
|
binding.postsRecyclerView.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.profile_menu, menu)
|
||||||
|
blockMenuItem = menu.findItem(R.id.block)
|
||||||
|
restrictMenuItem = menu.findItem(R.id.restrict)
|
||||||
|
muteStoriesMenuItem = menu.findItem(R.id.mute_stories)
|
||||||
|
mutePostsMenuItem = menu.findItem(R.id.mute_posts)
|
||||||
|
chainingMenuItem = menu.findItem(R.id.chaining)
|
||||||
|
removeFollowerMenuItem = menu.findItem(R.id.remove_follower)
|
||||||
|
shareLinkMenuItem = menu.findItem(R.id.share_link)
|
||||||
|
shareDmMenuItem = menu.findItem(R.id.share_dm)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.layout -> showPostsLayoutPreferences()
|
||||||
|
R.id.restrict -> viewModel.restrictUser()
|
||||||
|
R.id.block -> viewModel.blockUser()
|
||||||
|
R.id.chaining -> navigateToChaining()
|
||||||
|
R.id.mute_stories -> viewModel.muteStories()
|
||||||
|
R.id.mute_posts -> viewModel.mutePosts()
|
||||||
|
R.id.remove_follower -> viewModel.removeFollower()
|
||||||
|
R.id.share_link -> shareProfileLink()
|
||||||
|
R.id.share_dm -> shareProfileViaDm()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
try {
|
||||||
|
val backStackEntry = NavHostFragment.findNavController(this).currentBackStackEntry
|
||||||
|
if (backStackEntry != null) {
|
||||||
|
backStackSavedStateResultLiveData = backStackEntry.savedStateHandle.getLiveData("result")
|
||||||
|
backStackSavedStateResultLiveData?.observe(viewLifecycleOwner, backStackSavedStateObserver)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onResume: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareProfileViaDm() {
|
||||||
|
val actionGlobalUserSearch = UserSearchFragmentDirections.actionGlobalUserSearch().apply {
|
||||||
|
setTitle(getString(R.string.share))
|
||||||
|
setActionLabel(getString(R.string.send))
|
||||||
|
showGroups = true
|
||||||
|
multiple = true
|
||||||
|
setSearchMode(UserSearchFragment.SearchMode.RAVEN)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val navController = NavHostFragment.findNavController(this@ProfileFragment)
|
||||||
|
navController.navigate(actionGlobalUserSearch)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "shareProfileViaDm: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareProfileLink() {
|
||||||
|
val profile = viewModel.profile.value?.data ?: return
|
||||||
|
val sharingIntent = Intent(Intent.ACTION_SEND)
|
||||||
|
sharingIntent.type = "text/plain"
|
||||||
|
sharingIntent.putExtra(Intent.EXTRA_TEXT, "https://instagram.com/" + profile.username)
|
||||||
|
startActivity(Intent.createChooser(sharingIntent, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToChaining() {
|
||||||
|
viewModel.currentUser.value?.data ?: return
|
||||||
|
val profile = viewModel.profile.value?.data ?: return
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putString("type", "chaining")
|
||||||
|
putLong("targetId", profile.pk)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "navigateToChaining: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun init() {
|
||||||
|
binding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||||
|
disableDm = !Utils.isNavRootInCurrentTabs("direct_messages_nav_graph")
|
||||||
|
setupHighlights()
|
||||||
|
setupObservers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupObservers() {
|
||||||
|
viewModel.isLoggedIn.observe(viewLifecycleOwner) {} // observe so that `isLoggedIn.value` is correct
|
||||||
|
viewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) {
|
||||||
|
val (currentUserResource, profileResource) = it
|
||||||
|
if (currentUserResource.status == Resource.Status.ERROR || profileResource.status == Resource.Status.ERROR) {
|
||||||
|
context?.let { ctx -> Toast.makeText(ctx, R.string.error_loading_profile, Toast.LENGTH_LONG).show() }
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
if (currentUserResource.status == Resource.Status.LOADING || profileResource.status == Resource.Status.LOADING) {
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
val currentUser = currentUserResource.data
|
||||||
|
val profile = profileResource.data
|
||||||
|
val stateUsername = arguments?.getString("username")
|
||||||
|
setupOptionsMenuItems(currentUser, profile)
|
||||||
|
if (currentUser == null && profile == null && stateUsername.isNullOrBlank()) {
|
||||||
|
// default anonymous state, show default message
|
||||||
|
showDefaultMessage()
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
if (profile == null && !stateUsername.isNullOrBlank()) {
|
||||||
|
context?.let { ctx -> Toast.makeText(ctx, R.string.error_loading_profile, Toast.LENGTH_LONG).show() }
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
root.loadLayoutDescription(R.xml.header_list_scene)
|
||||||
|
setupFavChip(profile, currentUser)
|
||||||
|
setupFavButton(currentUser, profile)
|
||||||
|
setupSavedButton(currentUser, profile)
|
||||||
|
setupTaggedButton(currentUser, profile)
|
||||||
|
setupLikedButton(currentUser, profile)
|
||||||
|
setupDMButton(currentUser, profile)
|
||||||
|
if (profile == null) return@observe
|
||||||
|
if (profile.isReallyPrivate(currentUser)) {
|
||||||
|
showPrivateAccountMessage()
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
if (!setupPostsDone) {
|
||||||
|
setupPosts(profile, currentUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.username.observe(viewLifecycleOwner) {
|
||||||
|
mainActivity.supportActionBar?.title = it
|
||||||
|
mainActivity.supportActionBar?.subtitle = null
|
||||||
|
}
|
||||||
|
viewModel.profilePicUrl.observe(viewLifecycleOwner) {
|
||||||
|
val visibility = if (it.isNullOrBlank()) View.INVISIBLE else View.VISIBLE
|
||||||
|
binding.header.mainProfileImage.visibility = visibility
|
||||||
|
binding.header.mainProfileImage.setImageURI(if (it.isNullOrBlank()) null else it)
|
||||||
|
binding.header.mainProfileImage.setOnClickListener(if (it.isNullOrBlank()) null else onProfilePicClickListener)
|
||||||
|
}
|
||||||
|
viewModel.fullName.observe(viewLifecycleOwner) { binding.header.mainFullName.text = it ?: "" }
|
||||||
|
viewModel.biography.observe(viewLifecycleOwner, this::setupBiography)
|
||||||
|
viewModel.url.observe(viewLifecycleOwner, this::setupProfileURL)
|
||||||
|
viewModel.followersCount.observe(viewLifecycleOwner, this::setupFollowers)
|
||||||
|
viewModel.followingCount.observe(viewLifecycleOwner, this::setupFollowing)
|
||||||
|
viewModel.postCount.observe(viewLifecycleOwner, this::setupPostsCount)
|
||||||
|
viewModel.friendshipStatus.observe(viewLifecycleOwner) {
|
||||||
|
setupFollowButton(it)
|
||||||
|
setupMainStatus(it)
|
||||||
|
}
|
||||||
|
viewModel.isVerified.observe(viewLifecycleOwner) {
|
||||||
|
binding.header.isVerified.visibility = if (it == true) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
viewModel.isPrivate.observe(viewLifecycleOwner) {
|
||||||
|
binding.header.isPrivate.visibility = if (it == true) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
viewModel.isFavorite.observe(viewLifecycleOwner) {
|
||||||
|
if (!it) {
|
||||||
|
binding.header.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24)
|
||||||
|
binding.header.favChip.setText(R.string.add_to_favorites)
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
binding.header.favChip.setChipIconResource(R.drawable.ic_star_check_24)
|
||||||
|
binding.header.favChip.setText(R.string.favorite_short)
|
||||||
|
}
|
||||||
|
viewModel.profileContext.observe(viewLifecycleOwner, this::setupProfileContext)
|
||||||
|
viewModel.userHighlights.observe(viewLifecycleOwner) {
|
||||||
|
binding.header.highlightsList.visibility = if (it.data.isNullOrEmpty()) View.GONE else View.VISIBLE
|
||||||
|
highlightsAdapter?.submitList(it.data)
|
||||||
|
}
|
||||||
|
viewModel.userStories.observe(viewLifecycleOwner) {
|
||||||
|
binding.header.mainProfileImage.setStoriesBorder(if (it.data.isNullOrEmpty()) 0 else 1)
|
||||||
|
}
|
||||||
|
viewModel.eventLiveData.observe(viewLifecycleOwner) {
|
||||||
|
val event = it?.getContentIfNotHandled() ?: return@observe
|
||||||
|
when (event) {
|
||||||
|
ShowConfirmUnfollowDialog -> showConfirmUnfollowDialog()
|
||||||
|
is DMButtonState -> binding.header.btnDM.isEnabled = !event.disabled
|
||||||
|
is NavigateToThread -> mainActivity.navigateToThread(event.threadId, event.username)
|
||||||
|
is ShowTranslation -> showTranslationDialog(event.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPrivateAccountMessage() {
|
||||||
|
binding.header.mainFollowers.isClickable = false
|
||||||
|
binding.header.mainFollowing.isClickable = false
|
||||||
|
binding.privatePage1.setImageResource(R.drawable.lock)
|
||||||
|
binding.privatePage2.setText(R.string.priv_acc)
|
||||||
|
binding.privatePage.visibility = VISIBLE
|
||||||
|
binding.privatePage1.visibility = VISIBLE
|
||||||
|
binding.privatePage2.visibility = VISIBLE
|
||||||
|
binding.postsRecyclerView.visibility = GONE
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
root.getTransition(R.id.transition)?.setEnable(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupProfileContext(contextPair: Pair<String?, List<UserProfileContextLink>?>) {
|
||||||
|
val (profileContext, contextLinkList) = contextPair
|
||||||
|
if (profileContext == null || contextLinkList == null) {
|
||||||
|
binding.header.profileContext.visibility = GONE
|
||||||
|
binding.header.profileContext.clearOnMentionClickListeners()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updatedProfileContext: String = profileContext
|
||||||
|
contextLinkList.forEachIndexed { i, link ->
|
||||||
|
if (link.username == null) return@forEachIndexed
|
||||||
|
updatedProfileContext = updatedProfileContext.substring(0, link.start + i) + "@" + updatedProfileContext.substring(link.start + i)
|
||||||
|
}
|
||||||
|
binding.header.profileContext.visibility = VISIBLE
|
||||||
|
binding.header.profileContext.text = updatedProfileContext
|
||||||
|
binding.header.profileContext.addOnMentionClickListener(onMentionClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupProfileURL(url: String?) {
|
||||||
|
if (url.isNullOrBlank()) {
|
||||||
|
binding.header.mainUrl.visibility = GONE
|
||||||
|
binding.header.mainUrl.clearOnURLClickListeners()
|
||||||
|
binding.header.mainUrl.setOnLongClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainUrl.visibility = VISIBLE
|
||||||
|
binding.header.mainUrl.text = url
|
||||||
|
binding.header.mainUrl.addOnURLClickListener { Utils.openURL(context ?: return@addOnURLClickListener, it.originalText.trimAll()) }
|
||||||
|
binding.header.mainUrl.setOnLongClickListener {
|
||||||
|
Utils.copyText(context ?: return@setOnLongClickListener false, url.trimAll())
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showTranslationDialog(result: String) {
|
||||||
|
val dialog = ConfirmDialogFragment.newInstance(
|
||||||
|
translationDialogRequestCode,
|
||||||
|
0,
|
||||||
|
result,
|
||||||
|
R.string.ok,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
dialog.show(childFragmentManager, ConfirmDialogFragment::class.java.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBiography(bio: String?) {
|
||||||
|
if (bio.isNullOrBlank()) {
|
||||||
|
binding.header.mainBiography.visibility = View.GONE
|
||||||
|
binding.header.mainBiography.clearAllAutoLinkListeners()
|
||||||
|
binding.header.mainBiography.setOnLongClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainBiography.visibility = View.VISIBLE
|
||||||
|
binding.header.mainBiography.text = bio
|
||||||
|
setCommonAutoLinkListeners(binding.header.mainBiography)
|
||||||
|
binding.header.mainBiography.setOnLongClickListener {
|
||||||
|
val isLoggedIn = viewModel.isLoggedIn.value ?: false
|
||||||
|
val options = arrayListOf(Option(getString(R.string.bio_copy), "copy"))
|
||||||
|
if (isLoggedIn) {
|
||||||
|
options.add(Option(getString(R.string.bio_translate), "translate"))
|
||||||
|
}
|
||||||
|
val dialog = MultiOptionDialogFragment.newInstance(
|
||||||
|
bioDialogRequestCode,
|
||||||
|
0,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
dialog.show(childFragmentManager, MultiOptionDialogFragment::class.java.simpleName)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setCommonAutoLinkListeners(textView: RamboTextViewV2) {
|
||||||
|
textView.addOnEmailClickListener(onEmailClickListener)
|
||||||
|
textView.addOnHashtagListener(onHashtagClickListener)
|
||||||
|
textView.addOnMentionClickListener(onMentionClickListener)
|
||||||
|
textView.addOnURLClickListener(onURLClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupOptionsMenuItems(currentUser: User?, profile: User?) {
|
||||||
|
val isMe = currentUser?.pk == profile?.pk
|
||||||
|
if (profile == null || (currentUser != null && isMe)) {
|
||||||
|
hideAllOptionsMenuItems()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (currentUser == null) {
|
||||||
|
hideAllOptionsMenuItems()
|
||||||
|
shareLinkMenuItem?.isVisible = profile.username.isNotBlank()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
blockMenuItem?.isVisible = true
|
||||||
|
blockMenuItem?.setTitle(if (profile.friendshipStatus?.blocking == true) R.string.unblock else R.string.block)
|
||||||
|
|
||||||
|
restrictMenuItem?.isVisible = true
|
||||||
|
restrictMenuItem?.setTitle(if (profile.friendshipStatus?.isRestricted == true) R.string.unrestrict else R.string.restrict)
|
||||||
|
|
||||||
|
muteStoriesMenuItem?.isVisible = true
|
||||||
|
muteStoriesMenuItem?.setTitle(if (profile.friendshipStatus?.isMutingReel == true) R.string.mute_stories else R.string.unmute_stories)
|
||||||
|
|
||||||
|
mutePostsMenuItem?.isVisible = true
|
||||||
|
mutePostsMenuItem?.setTitle(if (profile.friendshipStatus?.muting == true) R.string.mute_posts else R.string.unmute_posts)
|
||||||
|
|
||||||
|
chainingMenuItem?.isVisible = profile.hasChaining
|
||||||
|
removeFollowerMenuItem?.isVisible = profile.friendshipStatus?.followedBy ?: false
|
||||||
|
shareLinkMenuItem?.isVisible = profile.username.isNotBlank()
|
||||||
|
shareDmMenuItem?.isVisible = profile.pk != 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideAllOptionsMenuItems() {
|
||||||
|
blockMenuItem?.isVisible = false
|
||||||
|
restrictMenuItem?.isVisible = false
|
||||||
|
muteStoriesMenuItem?.isVisible = false
|
||||||
|
mutePostsMenuItem?.isVisible = false
|
||||||
|
chainingMenuItem?.isVisible = false
|
||||||
|
removeFollowerMenuItem?.isVisible = false
|
||||||
|
shareLinkMenuItem?.isVisible = false
|
||||||
|
shareDmMenuItem?.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPostsCount(count: Long?) {
|
||||||
|
if (count == null) {
|
||||||
|
binding.header.mainPostCount.visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainPostCount.visibility = View.VISIBLE
|
||||||
|
binding.header.mainPostCount.text = getCountSpan(R.plurals.main_posts_count, abbreviate(count, null), count)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFollowing(count: Long?) {
|
||||||
|
if (count == null) {
|
||||||
|
binding.header.mainFollowing.visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val abbreviate = abbreviate(count, null)
|
||||||
|
val span = SpannableStringBuilder(getString(R.string.main_posts_following, abbreviate))
|
||||||
|
binding.header.mainFollowing.visibility = View.VISIBLE
|
||||||
|
binding.header.mainFollowing.text = getCountSpan(span, abbreviate)
|
||||||
|
if (count <= 0) {
|
||||||
|
binding.header.mainFollowing.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainFollowing.setOnClickListener(onFollowingClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFollowers(count: Long?) {
|
||||||
|
if (count == null) {
|
||||||
|
binding.header.mainFollowers.visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainFollowers.visibility = View.VISIBLE
|
||||||
|
binding.header.mainFollowers.text = getCountSpan(R.plurals.main_posts_followers, abbreviate(count, null), count)
|
||||||
|
if (count <= 0) {
|
||||||
|
binding.header.mainFollowers.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainFollowers.setOnClickListener(onFollowersClickListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDMButton(currentUser: User?, profile: User?) {
|
||||||
|
val visibility = if (disableDm || (currentUser != null && profile?.pk == currentUser.pk)) View.GONE else View.VISIBLE
|
||||||
|
binding.header.btnDM.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
binding.header.btnDM.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.btnDM.setOnClickListener { viewModel.sendDm() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupLikedButton(currentUser: User?, profile: User?) {
|
||||||
|
val visibility = if (currentUser != null && profile?.pk == currentUser.pk) View.VISIBLE else View.GONE
|
||||||
|
binding.header.btnLiked.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
binding.header.btnLiked.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.btnLiked.setOnClickListener {
|
||||||
|
try {
|
||||||
|
val action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(
|
||||||
|
viewModel.profile.value?.data?.username ?: return@setOnClickListener,
|
||||||
|
viewModel.profile.value?.data?.pk ?: return@setOnClickListener,
|
||||||
|
PostItemType.LIKED
|
||||||
|
)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "setupTaggedButton: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTaggedButton(currentUser: User?, profile: User?) {
|
||||||
|
val visibility = if (currentUser != null && profile?.pk == currentUser.pk) View.VISIBLE else View.GONE
|
||||||
|
binding.header.btnTagged.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
binding.header.btnTagged.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.btnTagged.setOnClickListener {
|
||||||
|
try {
|
||||||
|
val action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(
|
||||||
|
viewModel.profile.value?.data?.username ?: return@setOnClickListener,
|
||||||
|
viewModel.profile.value?.data?.pk ?: return@setOnClickListener,
|
||||||
|
PostItemType.TAGGED
|
||||||
|
)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "setupTaggedButton: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSavedButton(currentUser: User?, profile: User?) {
|
||||||
|
val visibility = if (currentUser != null && profile?.pk == currentUser.pk) View.VISIBLE else View.GONE
|
||||||
|
binding.header.btnSaved.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
binding.header.btnSaved.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.btnSaved.setOnClickListener {
|
||||||
|
try {
|
||||||
|
val action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(false)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "setupSavedButton: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFavButton(currentUser: User?, profile: User?) {
|
||||||
|
val visibility = if (currentUser != null && profile?.pk != currentUser.pk) View.VISIBLE else View.GONE
|
||||||
|
binding.header.btnFollow.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
binding.header.btnFollow.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.btnFollow.setOnClickListener { viewModel.toggleFollow(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFavChip(profile: User?, currentUser: User?) {
|
||||||
|
val visibility = if (profile?.pk != currentUser?.pk) View.VISIBLE else View.GONE
|
||||||
|
binding.header.favChip.visibility = visibility
|
||||||
|
if (visibility == View.GONE) {
|
||||||
|
binding.header.favChip.setOnClickListener(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.favChip.setOnClickListener { viewModel.toggleFavorite() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFollowButton(it: FriendshipStatus?) {
|
||||||
|
if (it == null) return
|
||||||
|
if (it.following) {
|
||||||
|
binding.header.btnFollow.setText(R.string.unfollow)
|
||||||
|
binding.header.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (it.outgoingRequest) {
|
||||||
|
binding.header.btnFollow.setText(R.string.cancel)
|
||||||
|
binding.header.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.btnFollow.setText(R.string.follow)
|
||||||
|
binding.header.btnFollow.setChipIconResource(R.drawable.ic_outline_person_add_24)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupMainStatus(it: FriendshipStatus?) {
|
||||||
|
if (it == null || (!it.following && !it.followedBy)) {
|
||||||
|
binding.header.mainStatus.visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.header.mainStatus.visibility = View.VISIBLE
|
||||||
|
if (it.following && it.followedBy) {
|
||||||
|
context?.let { ctx ->
|
||||||
|
binding.header.mainStatus.chipBackgroundColor = AppCompatResources.getColorStateList(ctx, R.color.green_800)
|
||||||
|
binding.header.mainStatus.setText(R.string.status_mutual)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (it.following) {
|
||||||
|
context?.let { ctx ->
|
||||||
|
binding.header.mainStatus.chipBackgroundColor = AppCompatResources.getColorStateList(ctx, R.color.deep_orange_800)
|
||||||
|
binding.header.mainStatus.setText(R.string.status_following)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
context?.let { ctx ->
|
||||||
|
binding.header.mainStatus.chipBackgroundColor = AppCompatResources.getColorStateList(ctx, R.color.blue_800)
|
||||||
|
binding.header.mainStatus.setText(R.string.status_follower)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCountSpan(pluralRes: Int, countString: String, count: Long): SpannableStringBuilder {
|
||||||
|
val span = SpannableStringBuilder(resources.getQuantityString(pluralRes, count.toInt(), countString))
|
||||||
|
return getCountSpan(span, countString)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCountSpan(span: SpannableStringBuilder, countString: String): SpannableStringBuilder {
|
||||||
|
span.setSpan(RelativeSizeSpan(1.2f), 0, countString.length, 0)
|
||||||
|
span.setSpan(StyleSpan(Typeface.BOLD), 0, countString.length, 0)
|
||||||
|
return span
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDefaultMessage() {
|
||||||
|
root.loadLayoutDescription(R.xml.profile_fragment_no_acc_layout)
|
||||||
|
binding.privatePage1.visibility = View.VISIBLE
|
||||||
|
binding.privatePage2.visibility = View.VISIBLE
|
||||||
|
binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24)
|
||||||
|
binding.privatePage2.setText(R.string.no_acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupHighlights() {
|
||||||
|
val context = context ?: return
|
||||||
|
highlightsAdapter = HighlightsAdapter { model, position ->
|
||||||
|
val options = StoryViewerOptions.forHighlight(model.title)
|
||||||
|
options.currentFeedStoryIndex = position
|
||||||
|
val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment(options)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
}
|
||||||
|
binding.header.highlightsList.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
|
||||||
|
binding.header.highlightsList.adapter = highlightsAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPosts(profile: User, currentUser: User?) {
|
||||||
|
binding.postsRecyclerView.setViewModelStoreOwner(this)
|
||||||
|
.setLifeCycleOwner(this)
|
||||||
|
.setPostFetchService(ProfilePostFetchService(profile, currentUser != null))
|
||||||
|
.setLayoutPreferences(layoutPreferences)
|
||||||
|
.addFetchStatusChangeListener { binding.swipeRefreshLayout.isRefreshing = it }
|
||||||
|
.setFeedItemCallback(feedItemCallback)
|
||||||
|
.setSelectionModeCallback(selectionModeCallback)
|
||||||
|
.init()
|
||||||
|
binding.postsRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
val canScrollVertically = recyclerView.canScrollVertically(-1)
|
||||||
|
root.getTransition(R.id.transition)?.setEnable(!canScrollVertically)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setupPostsDone = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateToProfile(username: String?) {
|
||||||
|
try {
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("username", username ?: return)
|
||||||
|
val navController = NavHostFragment.findNavController(this)
|
||||||
|
navController.navigate(R.id.action_global_profileFragment, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "navigateToProfile: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showConfirmUnfollowDialog() {
|
||||||
|
val isPrivate = viewModel.profile.value?.data?.isPrivate ?: return
|
||||||
|
val titleRes = if (isPrivate) R.string.priv_acc else 0
|
||||||
|
val messageRes = if (isPrivate) R.string.priv_acc_confirm else R.string.are_you_sure
|
||||||
|
val dialog = ConfirmDialogFragment.newInstance(
|
||||||
|
confirmDialogFragmentRequestCode,
|
||||||
|
titleRes,
|
||||||
|
messageRes,
|
||||||
|
R.string.confirm,
|
||||||
|
R.string.cancel,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
dialog.show(childFragmentManager, ConfirmDialogFragment::class.java.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositiveButtonClicked(requestCode: Int) {
|
||||||
|
when (requestCode) {
|
||||||
|
confirmDialogFragmentRequestCode -> {
|
||||||
|
viewModel.toggleFollow(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNegativeButtonClicked(requestCode: Int) {}
|
||||||
|
|
||||||
|
override fun onNeutralButtonClicked(requestCode: Int) {}
|
||||||
|
|
||||||
|
override fun onSelect(requestCode: Int, result: String?) {
|
||||||
|
val r = result ?: return
|
||||||
|
when (requestCode) {
|
||||||
|
ppOptsDialogRequestCode -> onPpOptionSelect(r)
|
||||||
|
bioDialogRequestCode -> onBioOptionSelect(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onBioOptionSelect(result: String) {
|
||||||
|
when (result) {
|
||||||
|
"copy" -> Utils.copyText(context ?: return, viewModel.biography.value ?: return)
|
||||||
|
"translate" -> viewModel.translateBio()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPpOptionSelect(result: String) {
|
||||||
|
when (result) {
|
||||||
|
"profile_pic" -> showProfilePicDialog()
|
||||||
|
"show_stories" -> {
|
||||||
|
try {
|
||||||
|
val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment(
|
||||||
|
StoryViewerOptions.forUser(
|
||||||
|
viewModel.profile.value?.data?.pk ?: return,
|
||||||
|
viewModel.profile.value?.data?.fullName ?: return,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
NavHostFragment.findNavController(this).navigate(action)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "omPpOptionSelect: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(requestCode: Int) {}
|
||||||
|
|
||||||
|
private fun showProfilePicDialog() {
|
||||||
|
val profile = viewModel.profile.value?.data ?: return
|
||||||
|
val fragment = ProfilePicDialogFragment.getInstance(
|
||||||
|
profile.pk,
|
||||||
|
profile.username,
|
||||||
|
profile.profilePicUrl ?: return
|
||||||
|
)
|
||||||
|
val ft = childFragmentManager.beginTransaction()
|
||||||
|
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
|
.add(fragment, ProfilePicDialogFragment::class.java.simpleName)
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPostsLayoutPreferences() {
|
||||||
|
val fragment = PostsLayoutPreferencesDialogFragment(Constants.PREF_PROFILE_POSTS_LAYOUT) { preferences ->
|
||||||
|
layoutPreferences = preferences
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed(
|
||||||
|
{ binding.postsRecyclerView.layoutPreferences = preferences },
|
||||||
|
200
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fragment.show(childFragmentManager, PostsLayoutPreferencesDialogFragment::class.java.simpleName)
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,11 @@ import android.content.ContentResolver
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import awais.instagrabber.models.enums.BroadcastItemType
|
|
||||||
import awais.instagrabber.models.Resource
|
import awais.instagrabber.models.Resource
|
||||||
import awais.instagrabber.models.Resource.Companion.error
|
import awais.instagrabber.models.Resource.Companion.error
|
||||||
import awais.instagrabber.models.Resource.Companion.loading
|
import awais.instagrabber.models.Resource.Companion.loading
|
||||||
import awais.instagrabber.models.Resource.Companion.success
|
import awais.instagrabber.models.Resource.Companion.success
|
||||||
|
import awais.instagrabber.models.enums.BroadcastItemType
|
||||||
import awais.instagrabber.repositories.requests.directmessages.ThreadIdsOrUserIds
|
import awais.instagrabber.repositories.requests.directmessages.ThreadIdsOrUserIds
|
||||||
import awais.instagrabber.repositories.responses.User
|
import awais.instagrabber.repositories.responses.User
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread
|
||||||
@ -17,7 +17,7 @@ import awais.instagrabber.utils.Constants
|
|||||||
import awais.instagrabber.utils.Utils
|
import awais.instagrabber.utils.Utils
|
||||||
import awais.instagrabber.utils.getCsrfTokenFromCookie
|
import awais.instagrabber.utils.getCsrfTokenFromCookie
|
||||||
import awais.instagrabber.utils.getUserIdFromCookie
|
import awais.instagrabber.utils.getUserIdFromCookie
|
||||||
import awais.instagrabber.webservices.DirectMessagesService
|
import awais.instagrabber.webservices.DirectMessagesRepository
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -31,6 +31,7 @@ object DirectMessagesManager {
|
|||||||
private val viewerId: Long
|
private val viewerId: Long
|
||||||
private val deviceUuid: String
|
private val deviceUuid: String
|
||||||
private val csrfToken: String
|
private val csrfToken: String
|
||||||
|
private val directMessagesRepository by lazy { DirectMessagesRepository.getInstance() }
|
||||||
|
|
||||||
fun moveThreadFromPending(threadId: String) {
|
fun moveThreadFromPending(threadId: String) {
|
||||||
val pendingThreads = pendingInboxManager.threads.value ?: return
|
val pendingThreads = pendingInboxManager.threads.value ?: return
|
||||||
@ -66,7 +67,8 @@ object DirectMessagesManager {
|
|||||||
return ThreadManager(threadId, pending, currentUser, contentResolver, viewerId, csrfToken, deviceUuid)
|
return ThreadManager(threadId, pending, currentUser, contentResolver, viewerId, csrfToken, deviceUuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createThread(userPk: Long): DirectThread = DirectMessagesService.createThread(csrfToken, viewerId, deviceUuid, listOf(userPk), null)
|
suspend fun createThread(userPk: Long): DirectThread =
|
||||||
|
directMessagesRepository.createThread(csrfToken, viewerId, deviceUuid, listOf(userPk), null)
|
||||||
|
|
||||||
fun sendMedia(recipient: RankedRecipient, mediaId: String, itemType: BroadcastItemType, scope: CoroutineScope) {
|
fun sendMedia(recipient: RankedRecipient, mediaId: String, itemType: BroadcastItemType, scope: CoroutineScope) {
|
||||||
sendMedia(setOf(recipient), mediaId, itemType, scope)
|
sendMedia(setOf(recipient), mediaId, itemType, scope)
|
||||||
@ -78,9 +80,9 @@ object DirectMessagesManager {
|
|||||||
itemType: BroadcastItemType,
|
itemType: BroadcastItemType,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
val threadIds = recipients.mapNotNull{ it.thread?.threadId }
|
val threadIds = recipients.mapNotNull { it.thread?.threadId }
|
||||||
val userIdsTemp = recipients.mapNotNull{ it.user?.pk }
|
val userIdsTemp = recipients.mapNotNull { it.user?.pk }
|
||||||
val userIds = userIdsTemp.map{ listOf(it.toString(10)) }
|
val userIds = userIdsTemp.map { listOf(it.toString(10)) }
|
||||||
sendMedia(threadIds, userIds, mediaId, itemType, scope) {
|
sendMedia(threadIds, userIds, mediaId, itemType, scope) {
|
||||||
inboxManager.refresh(scope)
|
inboxManager.refresh(scope)
|
||||||
}
|
}
|
||||||
@ -99,7 +101,7 @@ object DirectMessagesManager {
|
|||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
if (itemType == BroadcastItemType.MEDIA_SHARE)
|
if (itemType == BroadcastItemType.MEDIA_SHARE)
|
||||||
DirectMessagesService.broadcastMediaShare(
|
directMessagesRepository.broadcastMediaShare(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
viewerId,
|
viewerId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -108,7 +110,7 @@ object DirectMessagesManager {
|
|||||||
mediaId
|
mediaId
|
||||||
)
|
)
|
||||||
if (itemType == BroadcastItemType.PROFILE)
|
if (itemType == BroadcastItemType.PROFILE)
|
||||||
DirectMessagesService.broadcastProfile(
|
directMessagesRepository.broadcastProfile(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
viewerId,
|
viewerId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
|
@ -13,7 +13,7 @@ import awais.instagrabber.repositories.responses.User
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.*
|
import awais.instagrabber.repositories.responses.directmessages.*
|
||||||
import awais.instagrabber.utils.*
|
import awais.instagrabber.utils.*
|
||||||
import awais.instagrabber.utils.extensions.TAG
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
import awais.instagrabber.webservices.DirectMessagesService
|
import awais.instagrabber.webservices.DirectMessagesRepository
|
||||||
import com.google.common.cache.CacheBuilder
|
import com.google.common.cache.CacheBuilder
|
||||||
import com.google.common.cache.CacheLoader
|
import com.google.common.cache.CacheLoader
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
@ -25,8 +25,7 @@ import java.util.*
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class InboxManager(private val pending: Boolean) {
|
class InboxManager(private val pending: Boolean) {
|
||||||
// private val fetchInboxControlledRunner: ControlledRunner<Resource<DirectInbox>> = ControlledRunner()
|
private val directMessagesRepository by lazy { DirectMessagesRepository.getInstance() }
|
||||||
// private val fetchPendingInboxControlledRunner: ControlledRunner<Resource<DirectInbox>> = ControlledRunner()
|
|
||||||
private val inbox = MutableLiveData<Resource<DirectInbox?>>(success(null))
|
private val inbox = MutableLiveData<Resource<DirectInbox?>>(success(null))
|
||||||
private val unseenCount = MutableLiveData<Resource<Int?>>()
|
private val unseenCount = MutableLiveData<Resource<Int?>>()
|
||||||
private val pendingRequestsTotal = MutableLiveData(0)
|
private val pendingRequestsTotal = MutableLiveData(0)
|
||||||
@ -58,9 +57,9 @@ class InboxManager(private val pending: Boolean) {
|
|||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val inboxValue = if (pending) {
|
val inboxValue = if (pending) {
|
||||||
DirectMessagesService.fetchPendingInbox(cursor, seqId)
|
directMessagesRepository.fetchPendingInbox(cursor, seqId)
|
||||||
} else {
|
} else {
|
||||||
DirectMessagesService.fetchInbox(cursor, seqId)
|
directMessagesRepository.fetchInbox(cursor, seqId)
|
||||||
}
|
}
|
||||||
parseInboxResponse(inboxValue)
|
parseInboxResponse(inboxValue)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -77,7 +76,7 @@ class InboxManager(private val pending: Boolean) {
|
|||||||
unseenCount.postValue(loading(currentUnseenCount))
|
unseenCount.postValue(loading(currentUnseenCount))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val directBadgeCount = DirectMessagesService.fetchUnseenCount()
|
val directBadgeCount = directMessagesRepository.fetchUnseenCount()
|
||||||
unseenCount.postValue(success(directBadgeCount.badgeCount))
|
unseenCount.postValue(success(directBadgeCount.badgeCount))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed fetching unseen count", e)
|
Log.e(TAG, "Failed fetching unseen count", e)
|
||||||
@ -253,7 +252,7 @@ class InboxManager(private val pending: Boolean) {
|
|||||||
try {
|
try {
|
||||||
val clone = currentDirectInbox.clone() as DirectInbox
|
val clone = currentDirectInbox.clone() as DirectInbox
|
||||||
clone.threads = threadsCopy
|
clone.threads = threadsCopy
|
||||||
inbox.setValue(success(clone))
|
inbox.postValue(success(clone))
|
||||||
} catch (e: CloneNotSupportedException) {
|
} catch (e: CloneNotSupportedException) {
|
||||||
Log.e(TAG, "setThread: ", e)
|
Log.e(TAG, "setThread: ", e)
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import awais.instagrabber.utils.MediaUtils.OnInfoLoadListener
|
|||||||
import awais.instagrabber.utils.MediaUtils.VideoInfo
|
import awais.instagrabber.utils.MediaUtils.VideoInfo
|
||||||
import awais.instagrabber.utils.TextUtils.isEmpty
|
import awais.instagrabber.utils.TextUtils.isEmpty
|
||||||
import awais.instagrabber.utils.extensions.TAG
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
import awais.instagrabber.webservices.DirectMessagesService
|
import awais.instagrabber.webservices.DirectMessagesRepository
|
||||||
import awais.instagrabber.webservices.FriendshipRepository
|
import awais.instagrabber.webservices.FriendshipRepository
|
||||||
import awais.instagrabber.webservices.MediaRepository
|
import awais.instagrabber.webservices.MediaRepository
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
@ -64,6 +64,7 @@ class ThreadManager(
|
|||||||
private val threadIdsOrUserIds: ThreadIdsOrUserIds = of(threadId)
|
private val threadIdsOrUserIds: ThreadIdsOrUserIds = of(threadId)
|
||||||
private val friendshipRepository: FriendshipRepository by lazy { FriendshipRepository.getInstance() }
|
private val friendshipRepository: FriendshipRepository by lazy { FriendshipRepository.getInstance() }
|
||||||
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
|
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
|
||||||
|
private val directMessagesRepository by lazy { DirectMessagesRepository.getInstance() }
|
||||||
|
|
||||||
val thread: LiveData<DirectThread?> by lazy {
|
val thread: LiveData<DirectThread?> by lazy {
|
||||||
distinctUntilChanged(map(inboxManager.getInbox()) { inboxResource: Resource<DirectInbox?>? ->
|
distinctUntilChanged(map(inboxManager.getInbox()) { inboxResource: Resource<DirectInbox?>? ->
|
||||||
@ -128,7 +129,7 @@ class ThreadManager(
|
|||||||
_fetching.postValue(loading(null))
|
_fetching.postValue(loading(null))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val threadFeedResponse = DirectMessagesService.fetchThread(threadId, cursor)
|
val threadFeedResponse = directMessagesRepository.fetchThread(threadId, cursor)
|
||||||
if (threadFeedResponse.status != null && threadFeedResponse.status != "ok") {
|
if (threadFeedResponse.status != null && threadFeedResponse.status != "ok") {
|
||||||
_fetching.postValue(error(R.string.generic_not_ok_response, null))
|
_fetching.postValue(error(R.string.generic_not_ok_response, null))
|
||||||
return@launch
|
return@launch
|
||||||
@ -156,7 +157,7 @@ class ThreadManager(
|
|||||||
if (isGroup == null || !isGroup) return
|
if (isGroup == null || !isGroup) return
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.participantRequests(threadId, 1)
|
val response = directMessagesRepository.participantRequests(threadId, 1)
|
||||||
_pendingRequests.postValue(response)
|
_pendingRequests.postValue(response)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "fetchPendingRequests: ", e)
|
Log.e(TAG, "fetchPendingRequests: ", e)
|
||||||
@ -348,7 +349,7 @@ class ThreadManager(
|
|||||||
val repliedToClientContext = replyToItemValue?.clientContext
|
val repliedToClientContext = replyToItemValue?.clientContext
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.broadcastText(
|
val response = directMessagesRepository.broadcastText(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
viewerId,
|
viewerId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -406,7 +407,7 @@ class ThreadManager(
|
|||||||
data.postValue(loading(directItem))
|
data.postValue(loading(directItem))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val request = DirectMessagesService.broadcastAnimatedMedia(
|
val request = directMessagesRepository.broadcastAnimatedMedia(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
userId,
|
userId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -455,7 +456,7 @@ class ThreadManager(
|
|||||||
null
|
null
|
||||||
)
|
)
|
||||||
mediaRepository.uploadFinish(csrfToken, userId, deviceUuid, uploadFinishOptions)
|
mediaRepository.uploadFinish(csrfToken, userId, deviceUuid, uploadFinishOptions)
|
||||||
val broadcastResponse = DirectMessagesService.broadcastVoice(
|
val broadcastResponse = directMessagesRepository.broadcastVoice(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
viewerId,
|
viewerId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -499,7 +500,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.broadcastReaction(
|
directMessagesRepository.broadcastReaction(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
userId,
|
userId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -539,7 +540,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.broadcastReaction(
|
directMessagesRepository.broadcastReaction(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
viewerId,
|
viewerId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -567,7 +568,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.deleteItem(csrfToken, deviceUuid, threadId, itemId)
|
directMessagesRepository.deleteItem(csrfToken, deviceUuid, threadId, itemId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// add the item back if unsuccessful
|
// add the item back if unsuccessful
|
||||||
addItems(index, listOf(item))
|
addItems(index, listOf(item))
|
||||||
@ -643,7 +644,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.forward(
|
directMessagesRepository.forward(
|
||||||
thread.threadId,
|
thread.threadId,
|
||||||
itemTypeName,
|
itemTypeName,
|
||||||
threadId,
|
threadId,
|
||||||
@ -662,7 +663,7 @@ class ThreadManager(
|
|||||||
val data = MutableLiveData<Resource<Any?>>()
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.approveRequest(csrfToken, deviceUuid, threadId)
|
directMessagesRepository.approveRequest(csrfToken, deviceUuid, threadId)
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "acceptRequest: ", e)
|
Log.e(TAG, "acceptRequest: ", e)
|
||||||
@ -676,7 +677,7 @@ class ThreadManager(
|
|||||||
val data = MutableLiveData<Resource<Any?>>()
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.declineRequest(csrfToken, deviceUuid, threadId)
|
directMessagesRepository.declineRequest(csrfToken, deviceUuid, threadId)
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "declineRequest: ", e)
|
Log.e(TAG, "declineRequest: ", e)
|
||||||
@ -732,7 +733,7 @@ class ThreadManager(
|
|||||||
if (handleInvalidResponse(data, response)) return@launch
|
if (handleInvalidResponse(data, response)) return@launch
|
||||||
val response1 = response.response ?: return@launch
|
val response1 = response.response ?: return@launch
|
||||||
val uploadId = response1.optString("upload_id")
|
val uploadId = response1.optString("upload_id")
|
||||||
val response2 = DirectMessagesService.broadcastPhoto(csrfToken, viewerId, deviceUuid, clientContext, threadIdsOrUserIds, uploadId)
|
val response2 = directMessagesRepository.broadcastPhoto(csrfToken, viewerId, deviceUuid, clientContext, threadIdsOrUserIds, uploadId)
|
||||||
parseResponse(response2, data, directItem)
|
parseResponse(response2, data, directItem)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
data.postValue(error(e.message, null))
|
data.postValue(error(e.message, null))
|
||||||
@ -793,7 +794,7 @@ class ThreadManager(
|
|||||||
VideoOptions(duration / 1000f, emptyList(), 0, false)
|
VideoOptions(duration / 1000f, emptyList(), 0, false)
|
||||||
)
|
)
|
||||||
mediaRepository.uploadFinish(csrfToken, userId, deviceUuid, uploadFinishOptions)
|
mediaRepository.uploadFinish(csrfToken, userId, deviceUuid, uploadFinishOptions)
|
||||||
val broadcastResponse = DirectMessagesService.broadcastVideo(
|
val broadcastResponse = directMessagesRepository.broadcastVideo(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
viewerId,
|
viewerId,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
@ -923,7 +924,7 @@ class ThreadManager(
|
|||||||
val data = MutableLiveData<Resource<Any?>>()
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.updateTitle(csrfToken, deviceUuid, threadId, newTitle.trim())
|
val response = directMessagesRepository.updateTitle(csrfToken, deviceUuid, threadId, newTitle.trim())
|
||||||
handleDetailsChangeResponse(data, response)
|
handleDetailsChangeResponse(data, response)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
@ -935,7 +936,7 @@ class ThreadManager(
|
|||||||
val data = MutableLiveData<Resource<Any?>>()
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.addUsers(
|
val response = directMessagesRepository.addUsers(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
threadId,
|
threadId,
|
||||||
@ -954,7 +955,7 @@ class ThreadManager(
|
|||||||
val data = MutableLiveData<Resource<Any?>>()
|
val data = MutableLiveData<Resource<Any?>>()
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.removeUsers(csrfToken, deviceUuid, threadId, setOf(user.pk))
|
directMessagesRepository.removeUsers(csrfToken, deviceUuid, threadId, setOf(user.pk))
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
var activeUsers = users.value
|
var activeUsers = users.value
|
||||||
var leftUsersValue = leftUsers.value
|
var leftUsersValue = leftUsers.value
|
||||||
@ -989,7 +990,7 @@ class ThreadManager(
|
|||||||
if (isAdmin(user)) return data
|
if (isAdmin(user)) return data
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.addAdmins(csrfToken, deviceUuid, threadId, setOf(user.pk))
|
directMessagesRepository.addAdmins(csrfToken, deviceUuid, threadId, setOf(user.pk))
|
||||||
val currentAdminIds = adminUserIds.value
|
val currentAdminIds = adminUserIds.value
|
||||||
val updatedAdminIds = ImmutableList.builder<Long>()
|
val updatedAdminIds = ImmutableList.builder<Long>()
|
||||||
.addAll(currentAdminIds ?: emptyList())
|
.addAll(currentAdminIds ?: emptyList())
|
||||||
@ -1017,7 +1018,7 @@ class ThreadManager(
|
|||||||
if (!isAdmin(user)) return data
|
if (!isAdmin(user)) return data
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.removeAdmins(csrfToken, deviceUuid, threadId, setOf(user.pk))
|
directMessagesRepository.removeAdmins(csrfToken, deviceUuid, threadId, setOf(user.pk))
|
||||||
val currentAdmins = adminUserIds.value ?: return@launch
|
val currentAdmins = adminUserIds.value ?: return@launch
|
||||||
val updatedAdminUserIds = currentAdmins.filter { userId1: Long -> userId1 != user.pk }
|
val updatedAdminUserIds = currentAdmins.filter { userId1: Long -> userId1 != user.pk }
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
@ -1047,7 +1048,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.mute(csrfToken, deviceUuid, threadId)
|
directMessagesRepository.mute(csrfToken, deviceUuid, threadId)
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1075,7 +1076,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.unmute(csrfToken, deviceUuid, threadId)
|
directMessagesRepository.unmute(csrfToken, deviceUuid, threadId)
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1103,7 +1104,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.muteMentions(csrfToken, deviceUuid, threadId)
|
directMessagesRepository.muteMentions(csrfToken, deviceUuid, threadId)
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1131,7 +1132,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
DirectMessagesService.unmuteMentions(csrfToken, deviceUuid, threadId)
|
directMessagesRepository.unmuteMentions(csrfToken, deviceUuid, threadId)
|
||||||
data.postValue(success(Any()))
|
data.postValue(success(Any()))
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1210,7 +1211,7 @@ class ThreadManager(
|
|||||||
data.postValue(loading(null))
|
data.postValue(loading(null))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.approveParticipantRequests(
|
val response = directMessagesRepository.approveParticipantRequests(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
threadId,
|
threadId,
|
||||||
@ -1231,7 +1232,7 @@ class ThreadManager(
|
|||||||
data.postValue(loading(null))
|
data.postValue(loading(null))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.declineParticipantRequests(
|
val response = directMessagesRepository.declineParticipantRequests(
|
||||||
csrfToken,
|
csrfToken,
|
||||||
deviceUuid,
|
deviceUuid,
|
||||||
threadId,
|
threadId,
|
||||||
@ -1273,7 +1274,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.approvalRequired(csrfToken, deviceUuid, threadId)
|
val response = directMessagesRepository.approvalRequired(csrfToken, deviceUuid, threadId)
|
||||||
handleDetailsChangeResponse(data, response)
|
handleDetailsChangeResponse(data, response)
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1301,7 +1302,7 @@ class ThreadManager(
|
|||||||
}
|
}
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val request = DirectMessagesService.approvalNotRequired(csrfToken, deviceUuid, threadId)
|
val request = directMessagesRepository.approvalNotRequired(csrfToken, deviceUuid, threadId)
|
||||||
handleDetailsChangeResponse(data, request)
|
handleDetailsChangeResponse(data, request)
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1324,7 +1325,7 @@ class ThreadManager(
|
|||||||
data.postValue(loading(null))
|
data.postValue(loading(null))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val request = DirectMessagesService.leave(csrfToken, deviceUuid, threadId)
|
val request = directMessagesRepository.leave(csrfToken, deviceUuid, threadId)
|
||||||
handleDetailsChangeResponse(data, request)
|
handleDetailsChangeResponse(data, request)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "leave: ", e)
|
Log.e(TAG, "leave: ", e)
|
||||||
@ -1339,7 +1340,7 @@ class ThreadManager(
|
|||||||
data.postValue(loading(null))
|
data.postValue(loading(null))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val request = DirectMessagesService.end(csrfToken, deviceUuid, threadId)
|
val request = directMessagesRepository.end(csrfToken, deviceUuid, threadId)
|
||||||
handleDetailsChangeResponse(data, request)
|
handleDetailsChangeResponse(data, request)
|
||||||
val currentThread = thread.value ?: return@launch
|
val currentThread = thread.value ?: return@launch
|
||||||
try {
|
try {
|
||||||
@ -1376,7 +1377,7 @@ class ThreadManager(
|
|||||||
data.postValue(loading(null))
|
data.postValue(loading(null))
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val response = DirectMessagesService.markAsSeen(csrfToken, deviceUuid, threadId, directItem)
|
val response = directMessagesRepository.markAsSeen(csrfToken, deviceUuid, threadId, directItem)
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
data.postValue(error(R.string.generic_null_response, null))
|
data.postValue(error(R.string.generic_null_response, null))
|
||||||
return@launch
|
return@launch
|
||||||
|
@ -3,7 +3,7 @@ package awais.instagrabber.repositories
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.*
|
import awais.instagrabber.repositories.responses.directmessages.*
|
||||||
import retrofit2.http.*
|
import retrofit2.http.*
|
||||||
|
|
||||||
interface DirectMessagesRepository {
|
interface DirectMessagesService {
|
||||||
@GET("/api/v1/direct_v2/inbox/")
|
@GET("/api/v1/direct_v2/inbox/")
|
||||||
suspend fun fetchInbox(@QueryMap queryMap: Map<String, String>): DirectInboxResponse
|
suspend fun fetchInbox(@QueryMap queryMap: Map<String, String>): DirectInboxResponse
|
||||||
|
|
@ -1,21 +0,0 @@
|
|||||||
package awais.instagrabber.repositories.responses;
|
|
||||||
|
|
||||||
public class UserProfileContextLink {
|
|
||||||
private final String username;
|
|
||||||
private final int start;
|
|
||||||
private final int end;
|
|
||||||
|
|
||||||
public UserProfileContextLink(final String username, final int start, final int end) {
|
|
||||||
this.username = username;
|
|
||||||
this.start = start;
|
|
||||||
this.end = end;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStart() {
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package awais.instagrabber.repositories.responses
|
||||||
|
|
||||||
|
data class UserProfileContextLink(
|
||||||
|
val username: String? = null,
|
||||||
|
val start: Int = 0,
|
||||||
|
val end: Int = 0,
|
||||||
|
)
|
27
app/src/main/java/awais/instagrabber/utils/Event.kt
Normal file
27
app/src/main/java/awais/instagrabber/utils/Event.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package awais.instagrabber.utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
|
||||||
|
*/
|
||||||
|
open class Event<out T>(private val content: T) {
|
||||||
|
|
||||||
|
var hasBeenHandled = false
|
||||||
|
private set // Allow external read but not write
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content and prevents its use again.
|
||||||
|
*/
|
||||||
|
fun getContentIfNotHandled(): T? {
|
||||||
|
return if (hasBeenHandled) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
hasBeenHandled = true
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content, even if it's already been handled.
|
||||||
|
*/
|
||||||
|
fun peekContent(): T = content
|
||||||
|
}
|
@ -5,26 +5,26 @@ import android.content.SharedPreferences
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.StringDef
|
import androidx.annotation.StringDef
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
import awais.instagrabber.fragments.settings.PreferenceKeys
|
|
||||||
|
|
||||||
class SettingsHelper(context: Context) {
|
class SettingsHelper(context: Context) {
|
||||||
private val sharedPreferences: SharedPreferences?
|
private val sharedPreferences: SharedPreferences? = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun getString(@StringSettings key: String): String {
|
fun getString(@StringSettings key: String): String {
|
||||||
val stringDefault = getStringDefault(key)
|
val stringDefault = getStringDefault(key)
|
||||||
return if (sharedPreferences != null) sharedPreferences.getString(
|
return sharedPreferences?.getString(
|
||||||
key,
|
key,
|
||||||
stringDefault
|
stringDefault
|
||||||
)!! else stringDefault
|
) ?: stringDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStringSet(@StringSetSettings key: String?): Set<String>? {
|
fun getStringSet(@StringSetSettings key: String?): Set<String> {
|
||||||
val stringSetDefault: Set<String> = HashSet()
|
val stringSetDefault: Set<String> = HashSet()
|
||||||
return if (sharedPreferences != null) sharedPreferences.getStringSet(
|
return sharedPreferences?.getStringSet(
|
||||||
key,
|
key,
|
||||||
stringSetDefault
|
stringSetDefault
|
||||||
) else stringSetDefault
|
) ?: stringSetDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInteger(@IntegerSettings key: String): Int {
|
fun getInteger(@IntegerSettings key: String): Int {
|
||||||
@ -49,15 +49,16 @@ class SettingsHelper(context: Context) {
|
|||||||
fun getThemeCode(fromHelper: Boolean): Int {
|
fun getThemeCode(fromHelper: Boolean): Int {
|
||||||
var themeCode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
var themeCode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
if (!fromHelper && sharedPreferences != null) {
|
if (!fromHelper && sharedPreferences != null) {
|
||||||
themeCode = sharedPreferences.getString(PreferenceKeys.APP_THEME, themeCode.toString())!!.toInt()
|
themeCode = sharedPreferences.getString(PreferenceKeys.APP_THEME, themeCode.toString())?.toInt() ?: 0
|
||||||
when (themeCode) {
|
when (themeCode) {
|
||||||
1 -> themeCode = AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
|
1 -> themeCode = AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
|
||||||
3 -> themeCode = AppCompatDelegate.MODE_NIGHT_NO
|
3 -> themeCode = AppCompatDelegate.MODE_NIGHT_NO
|
||||||
0 -> themeCode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
0 -> themeCode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (themeCode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM && Build.VERSION.SDK_INT < 29) themeCode =
|
if (themeCode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM && Build.VERSION.SDK_INT < 29) {
|
||||||
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
|
themeCode = AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
|
||||||
|
}
|
||||||
return themeCode
|
return themeCode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ class SettingsHelper(context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasPreference(key: String?): Boolean {
|
fun hasPreference(key: String?): Boolean {
|
||||||
return sharedPreferences != null && sharedPreferences.contains(key)
|
return sharedPreferences?.contains(key) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@StringDef(
|
@StringDef(
|
||||||
@ -149,8 +150,4 @@ class SettingsHelper(context: Context) {
|
|||||||
@StringDef(PreferenceKeys.KEYWORD_FILTERS)
|
@StringDef(PreferenceKeys.KEYWORD_FILTERS)
|
||||||
annotation class StringSetSettings
|
annotation class StringSetSettings
|
||||||
|
|
||||||
init {
|
|
||||||
sharedPreferences =
|
|
||||||
context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Google Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package awais.instagrabber.utils
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
||||||
|
* navigation and Snackbar messages.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This avoids a common problem with events: on configuration change (like rotation) an update
|
||||||
|
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
|
||||||
|
* explicit call to setValue() or call().
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Note that only one observer is going to be notified of changes.
|
||||||
|
*/
|
||||||
|
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||||
|
private val pending = AtomicBoolean(false)
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||||
|
if (hasActiveObservers()) {
|
||||||
|
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
|
||||||
|
}
|
||||||
|
// Observe the internal MutableLiveData
|
||||||
|
super.observe(owner, { t ->
|
||||||
|
if (pending.compareAndSet(true, false)) {
|
||||||
|
observer.onChanged(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
override fun setValue(t: T?) {
|
||||||
|
pending.set(true)
|
||||||
|
super.setValue(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for cases where T is Void, to make calls cleaner.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
fun call() {
|
||||||
|
value = null
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
package awais.instagrabber.utils.extensions
|
||||||
|
|
||||||
|
fun String.trimAll() = this.trim { it <= ' ' }
|
@ -0,0 +1,9 @@
|
|||||||
|
package awais.instagrabber.utils.extensions
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.User
|
||||||
|
|
||||||
|
fun User.isReallyPrivate(currentUser: User? = null): Boolean {
|
||||||
|
if (currentUser == null) return this.isPrivate
|
||||||
|
if (this.pk == currentUser.pk) return false
|
||||||
|
return this.friendshipStatus?.following == false && this.isPrivate
|
||||||
|
}
|
@ -9,8 +9,10 @@ import androidx.lifecycle.AndroidViewModel;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
|
import awais.instagrabber.db.repositories.AccountRepository;
|
||||||
import awais.instagrabber.models.Resource;
|
import awais.instagrabber.models.Resource;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.CoroutineUtilsKt;
|
import awais.instagrabber.utils.CoroutineUtilsKt;
|
||||||
@ -26,6 +28,8 @@ public class AppStateViewModel extends AndroidViewModel {
|
|||||||
private final String cookie;
|
private final String cookie;
|
||||||
private final MutableLiveData<Resource<User>> currentUser = new MutableLiveData<>(Resource.loading(null));
|
private final MutableLiveData<Resource<User>> currentUser = new MutableLiveData<>(Resource.loading(null));
|
||||||
|
|
||||||
|
private AccountRepository accountRepository;
|
||||||
|
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
public AppStateViewModel(@NonNull final Application application) {
|
public AppStateViewModel(@NonNull final Application application) {
|
||||||
@ -38,7 +42,7 @@ public class AppStateViewModel extends AndroidViewModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
userRepository = UserRepository.Companion.getInstance();
|
userRepository = UserRepository.Companion.getInstance();
|
||||||
// final AccountRepository accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(application));
|
accountRepository = AccountRepository.Companion.getInstance(application);
|
||||||
fetchProfileDetails();
|
fetchProfileDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,13 +65,26 @@ public class AppStateViewModel extends AndroidViewModel {
|
|||||||
userRepository.getUserInfo(uid, CoroutineUtilsKt.getContinuation((user, throwable) -> {
|
userRepository.getUserInfo(uid, CoroutineUtilsKt.getContinuation((user, throwable) -> {
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
Log.e(TAG, "onFailure: ", throwable);
|
Log.e(TAG, "onFailure: ", throwable);
|
||||||
final User backup = currentUser.getValue().data != null ?
|
final Resource<User> userResource = currentUser.getValue();
|
||||||
currentUser.getValue().data :
|
final User backup = userResource != null && userResource.data != null ? userResource.data : new User(uid);
|
||||||
new User(uid);
|
|
||||||
currentUser.postValue(Resource.error(throwable.getMessage(), backup));
|
currentUser.postValue(Resource.error(throwable.getMessage(), backup));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentUser.postValue(Resource.success(user));
|
currentUser.postValue(Resource.success(user));
|
||||||
|
if (accountRepository != null && user != null) {
|
||||||
|
accountRepository.insertOrUpdateAccount(
|
||||||
|
user.getPk(),
|
||||||
|
user.getUsername(),
|
||||||
|
cookie,
|
||||||
|
user.getFullName() != null ? user.getFullName() : "",
|
||||||
|
user.getProfilePicUrl(),
|
||||||
|
CoroutineUtilsKt.getContinuation((account, throwable1) -> AppExecutors.INSTANCE.getMainThread().execute(() -> {
|
||||||
|
if (throwable1 != null) {
|
||||||
|
Log.e(TAG, "updateAccountInfo: ", throwable1);
|
||||||
|
}
|
||||||
|
}), Dispatchers.getIO())
|
||||||
|
);
|
||||||
|
}
|
||||||
}, Dispatchers.getIO()));
|
}, Dispatchers.getIO()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,11 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import awais.instagrabber.R
|
import awais.instagrabber.R
|
||||||
import awais.instagrabber.managers.DirectMessagesManager
|
import awais.instagrabber.managers.DirectMessagesManager
|
||||||
import awais.instagrabber.models.enums.BroadcastItemType
|
|
||||||
import awais.instagrabber.models.Resource
|
import awais.instagrabber.models.Resource
|
||||||
import awais.instagrabber.models.Resource.Companion.error
|
import awais.instagrabber.models.Resource.Companion.error
|
||||||
import awais.instagrabber.models.Resource.Companion.loading
|
import awais.instagrabber.models.Resource.Companion.loading
|
||||||
import awais.instagrabber.models.Resource.Companion.success
|
import awais.instagrabber.models.Resource.Companion.success
|
||||||
|
import awais.instagrabber.models.enums.BroadcastItemType
|
||||||
import awais.instagrabber.models.enums.MediaItemType
|
import awais.instagrabber.models.enums.MediaItemType
|
||||||
import awais.instagrabber.repositories.responses.Caption
|
import awais.instagrabber.repositories.responses.Caption
|
||||||
import awais.instagrabber.repositories.responses.Location
|
import awais.instagrabber.repositories.responses.Location
|
||||||
@ -280,9 +280,9 @@ class PostViewV2ViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val result = mediaRepository.translate(pk, "1")
|
val result = mediaRepository.translate(pk, "1") ?: return@launch
|
||||||
if (result.isBlank()) {
|
if (result.isBlank()) {
|
||||||
data.postValue(error("", null))
|
// data.postValue(error("", null))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
data.postValue(success(result))
|
data.postValue(success(result))
|
||||||
|
@ -14,58 +14,90 @@ import awais.instagrabber.models.StoryModel
|
|||||||
import awais.instagrabber.models.enums.BroadcastItemType
|
import awais.instagrabber.models.enums.BroadcastItemType
|
||||||
import awais.instagrabber.models.enums.FavoriteType
|
import awais.instagrabber.models.enums.FavoriteType
|
||||||
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
||||||
|
import awais.instagrabber.repositories.responses.FriendshipStatus
|
||||||
import awais.instagrabber.repositories.responses.User
|
import awais.instagrabber.repositories.responses.User
|
||||||
|
import awais.instagrabber.repositories.responses.UserProfileContextLink
|
||||||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
||||||
import awais.instagrabber.utils.ControlledRunner
|
import awais.instagrabber.utils.ControlledRunner
|
||||||
|
import awais.instagrabber.utils.Event
|
||||||
|
import awais.instagrabber.utils.SingleRunner
|
||||||
import awais.instagrabber.utils.extensions.TAG
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
|
import awais.instagrabber.utils.extensions.isReallyPrivate
|
||||||
|
import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileAction.*
|
||||||
|
import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileEvent.*
|
||||||
import awais.instagrabber.webservices.*
|
import awais.instagrabber.webservices.*
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
class ProfileFragmentViewModel(
|
class ProfileFragmentViewModel(
|
||||||
state: SavedStateHandle,
|
private val state: SavedStateHandle,
|
||||||
userRepository: UserRepository,
|
private val csrfToken: String?,
|
||||||
friendshipRepository: FriendshipRepository,
|
private val deviceUuid: String?,
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
private val friendshipRepository: FriendshipRepository,
|
||||||
private val storiesRepository: StoriesRepository,
|
private val storiesRepository: StoriesRepository,
|
||||||
mediaRepository: MediaRepository,
|
private val mediaRepository: MediaRepository,
|
||||||
graphQLRepository: GraphQLRepository,
|
private val graphQLRepository: GraphQLRepository,
|
||||||
accountRepository: AccountRepository,
|
|
||||||
private val favoriteRepository: FavoriteRepository,
|
private val favoriteRepository: FavoriteRepository,
|
||||||
|
private val directMessagesRepository: DirectMessagesRepository,
|
||||||
|
private val messageManager: DirectMessagesManager?,
|
||||||
ioDispatcher: CoroutineDispatcher,
|
ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _currentUser = MutableLiveData<Resource<User?>>(Resource.loading(null))
|
private val _currentUser = MutableLiveData<Resource<User?>>(Resource.loading(null))
|
||||||
private val _isFavorite = MutableLiveData(false)
|
private val _isFavorite = MutableLiveData(false)
|
||||||
private var messageManager: DirectMessagesManager? = null
|
private val profileAction = MutableLiveData(INIT)
|
||||||
|
private val _eventLiveData = MutableLiveData<Event<ProfileEvent>?>()
|
||||||
|
|
||||||
|
enum class ProfileAction {
|
||||||
|
INIT,
|
||||||
|
REFRESH,
|
||||||
|
REFRESH_FRIENDSHIP,
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ProfileEvent {
|
||||||
|
object ShowConfirmUnfollowDialog : ProfileEvent()
|
||||||
|
class DMButtonState(val disabled: Boolean) : ProfileEvent()
|
||||||
|
class NavigateToThread(val threadId: String, val username: String) : ProfileEvent()
|
||||||
|
class ShowTranslation(val result: String) : ProfileEvent()
|
||||||
|
}
|
||||||
|
|
||||||
val currentUser: LiveData<Resource<User?>> = _currentUser
|
val currentUser: LiveData<Resource<User?>> = _currentUser
|
||||||
val isLoggedIn: LiveData<Boolean> = currentUser.map { it.data != null }
|
val isLoggedIn: LiveData<Boolean> = currentUser.map { it.data != null }
|
||||||
val isFavorite: LiveData<Boolean> = _isFavorite
|
val isFavorite: LiveData<Boolean> = _isFavorite
|
||||||
|
val eventLiveData: LiveData<Event<ProfileEvent>?> = _eventLiveData
|
||||||
|
|
||||||
private val currentUserAndStateUsernameLiveData: LiveData<Pair<Resource<User?>, Resource<String?>>> =
|
private val currentUserStateUsernameActionLiveData: LiveData<Triple<Resource<User?>, Resource<String?>, ProfileAction>> =
|
||||||
object : MediatorLiveData<Pair<Resource<User?>, Resource<String?>>>() {
|
object : MediatorLiveData<Triple<Resource<User?>, Resource<String?>, ProfileAction>>() {
|
||||||
var user: Resource<User?> = Resource.loading(null)
|
var user: Resource<User?> = Resource.loading(null)
|
||||||
var stateUsername: Resource<String?> = Resource.loading(null)
|
var stateUsername: Resource<String?> = Resource.loading(null)
|
||||||
|
var action: ProfileAction = INIT
|
||||||
|
|
||||||
init {
|
init {
|
||||||
addSource(currentUser) { currentUser ->
|
addSource(currentUser) { currentUser ->
|
||||||
this.user = currentUser
|
this.user = currentUser
|
||||||
value = currentUser to stateUsername
|
value = Triple(currentUser, stateUsername, action)
|
||||||
}
|
}
|
||||||
addSource(state.getLiveData<String?>("username")) { username ->
|
addSource(state.getLiveData<String?>("username")) { username ->
|
||||||
this.stateUsername = Resource.success(username.substringAfter('@'))
|
this.stateUsername = Resource.success(username.substringAfter('@'))
|
||||||
value = user to this.stateUsername
|
value = Triple(user, this.stateUsername, action)
|
||||||
}
|
}
|
||||||
// trigger currentUserAndStateUsernameLiveData switch map with a state username success resource
|
addSource(profileAction) { action ->
|
||||||
|
this.action = action
|
||||||
|
value = Triple(user, stateUsername, action)
|
||||||
|
}
|
||||||
|
// trigger currentUserStateUsernameActionLiveData switch map with a state username success resource
|
||||||
if (!state.contains("username")) {
|
if (!state.contains("username")) {
|
||||||
this.stateUsername = Resource.success(null)
|
this.stateUsername = Resource.success(null)
|
||||||
value = user to this.stateUsername
|
value = Triple(user, this.stateUsername, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val profileFetchControlledRunner = ControlledRunner<User?>()
|
private val profileFetchControlledRunner = ControlledRunner<User?>()
|
||||||
val profile: LiveData<Resource<User?>> = currentUserAndStateUsernameLiveData.switchMap {
|
val profile: LiveData<Resource<User?>> = currentUserStateUsernameActionLiveData.switchMap {
|
||||||
val (currentUserResource, stateUsernameResource) = it
|
val (currentUserResource, stateUsernameResource, action) = it
|
||||||
liveData<Resource<User?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
liveData<Resource<User?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
if (currentUserResource.status == Resource.Status.LOADING || stateUsernameResource.status == Resource.Status.LOADING) {
|
if (currentUserResource.status == Resource.Status.LOADING || stateUsernameResource.status == Resource.Status.LOADING) {
|
||||||
emit(Resource.loading(null))
|
emit(Resource.loading(null))
|
||||||
@ -78,33 +110,67 @@ class ProfileFragmentViewModel(
|
|||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val fetchedUser = profileFetchControlledRunner.cancelPreviousThenRun {
|
when (action) {
|
||||||
return@cancelPreviousThenRun fetchUser(currentUser, userRepository, stateUsername, graphQLRepository)
|
INIT, REFRESH -> {
|
||||||
}
|
val fetchedUser = profileFetchControlledRunner.cancelPreviousThenRun { fetchUser(currentUser, stateUsername) }
|
||||||
emit(Resource.success(fetchedUser))
|
emit(Resource.success(fetchedUser))
|
||||||
if (fetchedUser != null) {
|
if (fetchedUser != null) {
|
||||||
checkAndInsertFavorite(fetchedUser)
|
checkAndUpdateFavorite(fetchedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
REFRESH_FRIENDSHIP -> {
|
||||||
|
var profile = profileCopy.value?.data ?: return@liveData
|
||||||
|
profile = profile.copy(friendshipStatus = userRepository.getUserFriendship(profile.pk))
|
||||||
|
emit(Resource.success(profile))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emit(Resource.error(e.message, null))
|
emit(Resource.error(e.message, profileCopy.value?.data))
|
||||||
Log.e(TAG, "fetching user: ", e)
|
Log.e(TAG, "fetching user: ", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val profileCopy = profile
|
||||||
|
|
||||||
|
val currentUserProfileActionLiveData: LiveData<Triple<Resource<User?>, Resource<User?>, ProfileAction>> =
|
||||||
|
object : MediatorLiveData<Triple<Resource<User?>, Resource<User?>, ProfileAction>>() {
|
||||||
|
var currentUser: Resource<User?> = Resource.loading(null)
|
||||||
|
var profile: Resource<User?> = Resource.loading(null)
|
||||||
|
var action: ProfileAction = INIT
|
||||||
|
|
||||||
|
init {
|
||||||
|
addSource(this@ProfileFragmentViewModel.currentUser) { currentUser ->
|
||||||
|
this.currentUser = currentUser
|
||||||
|
value = Triple(currentUser, profile, action)
|
||||||
|
}
|
||||||
|
addSource(this@ProfileFragmentViewModel.profile) { profile ->
|
||||||
|
this.profile = profile
|
||||||
|
value = Triple(currentUser, this.profile, action)
|
||||||
|
}
|
||||||
|
addSource(profileAction) { action ->
|
||||||
|
this.action = action
|
||||||
|
value = Triple(currentUser, this.profile, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val storyFetchControlledRunner = ControlledRunner<List<StoryModel>?>()
|
private val storyFetchControlledRunner = ControlledRunner<List<StoryModel>?>()
|
||||||
val userStories: LiveData<Resource<List<StoryModel>?>> = profile.switchMap { userResource ->
|
val userStories: LiveData<Resource<List<StoryModel>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
|
||||||
liveData<Resource<List<StoryModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
liveData<Resource<List<StoryModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
|
val (currentUserResource, profileResource, action) = currentUserAndProfilePair
|
||||||
|
if (action != INIT && action != REFRESH) {
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
// don't fetch if not logged in
|
// don't fetch if not logged in
|
||||||
if (isLoggedIn.value != true) {
|
if (currentUserResource.data == null) {
|
||||||
emit(Resource.success(null))
|
emit(Resource.success(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
if (userResource.status == Resource.Status.LOADING) {
|
if (currentUserResource.status == Resource.Status.LOADING || profileResource.status == Resource.Status.LOADING) {
|
||||||
emit(Resource.loading(null))
|
emit(Resource.loading(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
val user = userResource.data
|
val user = profileResource.data
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
emit(Resource.success(null))
|
emit(Resource.success(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
@ -120,18 +186,22 @@ class ProfileFragmentViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val highlightsFetchControlledRunner = ControlledRunner<List<HighlightModel>?>()
|
private val highlightsFetchControlledRunner = ControlledRunner<List<HighlightModel>?>()
|
||||||
val userHighlights: LiveData<Resource<List<HighlightModel>?>> = profile.switchMap { userResource ->
|
val userHighlights: LiveData<Resource<List<HighlightModel>?>> = currentUserProfileActionLiveData.switchMap { currentUserAndProfilePair ->
|
||||||
liveData<Resource<List<HighlightModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
liveData<Resource<List<HighlightModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
|
val (currentUserResource, profileResource, action) = currentUserAndProfilePair
|
||||||
|
if (action != INIT && action != REFRESH) {
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
// don't fetch if not logged in
|
// don't fetch if not logged in
|
||||||
if (isLoggedIn.value != true) {
|
if (currentUserResource.data == null) {
|
||||||
emit(Resource.success(null))
|
emit(Resource.success(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
if (userResource.status == Resource.Status.LOADING) {
|
if (currentUserResource.status == Resource.Status.LOADING || profileResource.status == Resource.Status.LOADING) {
|
||||||
emit(Resource.loading(null))
|
emit(Resource.loading(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
val user = userResource.data
|
val user = profileResource.data
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
emit(Resource.success(null))
|
emit(Resource.success(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
@ -141,24 +211,25 @@ class ProfileFragmentViewModel(
|
|||||||
emit(Resource.success(fetchedHighlights))
|
emit(Resource.success(fetchedHighlights))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emit(Resource.error(e.message, null))
|
emit(Resource.error(e.message, null))
|
||||||
Log.e(TAG, "fetching story: ", e)
|
Log.e(TAG, "fetching highlights: ", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchUser(
|
private suspend fun fetchUser(
|
||||||
currentUser: User?,
|
currentUser: User?,
|
||||||
userRepository: UserRepository,
|
|
||||||
stateUsername: String,
|
stateUsername: String,
|
||||||
graphQLRepository: GraphQLRepository
|
): User {
|
||||||
) = if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
// logged in
|
// logged in
|
||||||
val tempUser = userRepository.getUsernameInfo(stateUsername)
|
val tempUser = userRepository.getUsernameInfo(stateUsername)
|
||||||
tempUser.friendshipStatus = userRepository.getUserFriendship(tempUser.pk)
|
if (!tempUser.isReallyPrivate(currentUser)) {
|
||||||
tempUser
|
tempUser.friendshipStatus = userRepository.getUserFriendship(tempUser.pk)
|
||||||
} else {
|
}
|
||||||
|
return tempUser
|
||||||
|
}
|
||||||
// anonymous
|
// anonymous
|
||||||
graphQLRepository.fetchUser(stateUsername)
|
return graphQLRepository.fetchUser(stateUsername)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchUserStory(fetchedUser: User): List<StoryModel> = storiesRepository.getUserStory(
|
private suspend fun fetchUserStory(fetchedUser: User): List<StoryModel> = storiesRepository.getUserStory(
|
||||||
@ -167,7 +238,7 @@ class ProfileFragmentViewModel(
|
|||||||
|
|
||||||
private suspend fun fetchUserHighlights(fetchedUser: User): List<HighlightModel> = storiesRepository.fetchHighlights(fetchedUser.pk)
|
private suspend fun fetchUserHighlights(fetchedUser: User): List<HighlightModel> = storiesRepository.fetchHighlights(fetchedUser.pk)
|
||||||
|
|
||||||
private suspend fun checkAndInsertFavorite(fetchedUser: User) {
|
private suspend fun checkAndUpdateFavorite(fetchedUser: User) {
|
||||||
try {
|
try {
|
||||||
val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER)
|
val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER)
|
||||||
if (favorite == null) {
|
if (favorite == null) {
|
||||||
@ -187,7 +258,260 @@ class ProfileFragmentViewModel(
|
|||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_isFavorite.postValue(false)
|
_isFavorite.postValue(false)
|
||||||
Log.e(TAG, "checkAndInsertFavorite: ", e)
|
Log.e(TAG, "checkAndUpdateFavorite: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCurrentUser(currentUser: Resource<User?>) {
|
||||||
|
_currentUser.postValue(currentUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareDm(result: RankedRecipient) {
|
||||||
|
val mediaId = profile.value?.data?.pk ?: return
|
||||||
|
messageManager?.sendMedia(result, mediaId.toString(10), BroadcastItemType.PROFILE, viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareDm(recipients: Set<RankedRecipient>) {
|
||||||
|
val mediaId = profile.value?.data?.pk ?: return
|
||||||
|
messageManager?.sendMedia(recipients, mediaId.toString(10), BroadcastItemType.PROFILE, viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
profileAction.postValue(REFRESH)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val toggleFavoriteControlledRunner = SingleRunner()
|
||||||
|
fun toggleFavorite() {
|
||||||
|
val username = profile.value?.data?.username ?: return
|
||||||
|
val fullName = profile.value?.data?.fullName ?: return
|
||||||
|
val profilePicUrl = profile.value?.data?.profilePicUrl ?: return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
toggleFavoriteControlledRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val favorite = favoriteRepository.getFavorite(username, FavoriteType.USER)
|
||||||
|
if (favorite == null) {
|
||||||
|
// insert
|
||||||
|
favoriteRepository.insertOrUpdateFavorite(
|
||||||
|
Favorite(
|
||||||
|
0,
|
||||||
|
username,
|
||||||
|
FavoriteType.USER,
|
||||||
|
fullName,
|
||||||
|
profilePicUrl,
|
||||||
|
LocalDateTime.now()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_isFavorite.postValue(true)
|
||||||
|
return@afterPrevious
|
||||||
|
}
|
||||||
|
// delete
|
||||||
|
favoriteRepository.deleteFavorite(username, FavoriteType.USER)
|
||||||
|
_isFavorite.postValue(false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "checkAndUpdateFavorite: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val toggleFollowSingleRunner = SingleRunner()
|
||||||
|
fun toggleFollow(confirmed: Boolean) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
toggleFollowSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val following = profile.value?.data?.friendshipStatus?.following ?: false
|
||||||
|
val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious
|
||||||
|
val targetUserId = profile.value?.data?.pk ?: return@afterPrevious
|
||||||
|
val csrfToken = csrfToken ?: return@afterPrevious
|
||||||
|
val deviceUuid = deviceUuid ?: return@afterPrevious
|
||||||
|
if (following) {
|
||||||
|
if (!confirmed) {
|
||||||
|
_eventLiveData.postValue(Event(ShowConfirmUnfollowDialog))
|
||||||
|
return@afterPrevious
|
||||||
|
}
|
||||||
|
// unfollow
|
||||||
|
friendshipRepository.unfollow(
|
||||||
|
csrfToken,
|
||||||
|
currentUserId,
|
||||||
|
deviceUuid,
|
||||||
|
targetUserId
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
return@afterPrevious
|
||||||
|
}
|
||||||
|
friendshipRepository.follow(
|
||||||
|
csrfToken,
|
||||||
|
currentUserId,
|
||||||
|
deviceUuid,
|
||||||
|
targetUserId
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "toggleFollow: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sendDmSingleRunner = SingleRunner()
|
||||||
|
fun sendDm() {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
sendDmSingleRunner.afterPrevious {
|
||||||
|
_eventLiveData.postValue(Event(DMButtonState(true)))
|
||||||
|
try {
|
||||||
|
val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious
|
||||||
|
val targetUserId = profile.value?.data?.pk ?: return@afterPrevious
|
||||||
|
val csrfToken = csrfToken ?: return@afterPrevious
|
||||||
|
val deviceUuid = deviceUuid ?: return@afterPrevious
|
||||||
|
val username = profile.value?.data?.username ?: return@afterPrevious
|
||||||
|
val thread = directMessagesRepository.createThread(
|
||||||
|
csrfToken,
|
||||||
|
currentUserId,
|
||||||
|
deviceUuid,
|
||||||
|
listOf(targetUserId),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
val inboxManager = DirectMessagesManager.inboxManager
|
||||||
|
if (!inboxManager.containsThread(thread.threadId)) {
|
||||||
|
thread.isTemp = true
|
||||||
|
inboxManager.addThread(thread, 0)
|
||||||
|
}
|
||||||
|
val threadId = thread.threadId ?: return@afterPrevious
|
||||||
|
_eventLiveData.postValue(Event(NavigateToThread(threadId, username)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "sendDm: ", e)
|
||||||
|
} finally {
|
||||||
|
_eventLiveData.postValue(Event(DMButtonState(false)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val restrictUserSingleRunner = SingleRunner()
|
||||||
|
fun restrictUser() {
|
||||||
|
if (isLoggedIn.value == false) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
restrictUserSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val profile = profile.value?.data ?: return@afterPrevious
|
||||||
|
friendshipRepository.toggleRestrict(
|
||||||
|
csrfToken ?: return@afterPrevious,
|
||||||
|
deviceUuid ?: return@afterPrevious,
|
||||||
|
profile.pk,
|
||||||
|
!(profile.friendshipStatus?.isRestricted ?: false),
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "restrictUser: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val blockUserSingleRunner = SingleRunner()
|
||||||
|
fun blockUser() {
|
||||||
|
if (isLoggedIn.value == false) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
blockUserSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val profile = profile.value?.data ?: return@afterPrevious
|
||||||
|
friendshipRepository.changeBlock(
|
||||||
|
csrfToken ?: return@afterPrevious,
|
||||||
|
currentUser.value?.data?.pk ?: return@afterPrevious,
|
||||||
|
deviceUuid ?: return@afterPrevious,
|
||||||
|
profile.friendshipStatus?.blocking ?: return@afterPrevious,
|
||||||
|
profile.pk
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "blockUser: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val muteStoriesSingleRunner = SingleRunner()
|
||||||
|
fun muteStories() {
|
||||||
|
if (isLoggedIn.value == false) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
muteStoriesSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val profile = profile.value?.data ?: return@afterPrevious
|
||||||
|
friendshipRepository.changeMute(
|
||||||
|
csrfToken ?: return@afterPrevious,
|
||||||
|
currentUser.value?.data?.pk ?: return@afterPrevious,
|
||||||
|
deviceUuid ?: return@afterPrevious,
|
||||||
|
profile.friendshipStatus?.isMutingReel ?: return@afterPrevious,
|
||||||
|
profile.pk,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "muteStories: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mutePostsSingleRunner = SingleRunner()
|
||||||
|
fun mutePosts() {
|
||||||
|
if (isLoggedIn.value == false) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
mutePostsSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val profile = profile.value?.data ?: return@afterPrevious
|
||||||
|
friendshipRepository.changeMute(
|
||||||
|
csrfToken ?: return@afterPrevious,
|
||||||
|
currentUser.value?.data?.pk ?: return@afterPrevious,
|
||||||
|
deviceUuid ?: return@afterPrevious,
|
||||||
|
profile.friendshipStatus?.muting ?: return@afterPrevious,
|
||||||
|
profile.pk,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "mutePosts: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val removeFollowerSingleRunner = SingleRunner()
|
||||||
|
fun removeFollower() {
|
||||||
|
if (isLoggedIn.value == false) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
removeFollowerSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
friendshipRepository.removeFollower(
|
||||||
|
csrfToken ?: return@afterPrevious,
|
||||||
|
currentUser.value?.data?.pk ?: return@afterPrevious,
|
||||||
|
deviceUuid ?: return@afterPrevious,
|
||||||
|
profile.value?.data?.pk ?: return@afterPrevious
|
||||||
|
)
|
||||||
|
profileAction.postValue(REFRESH_FRIENDSHIP)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "removeFollower: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val translateBioSingleRunner = SingleRunner()
|
||||||
|
fun translateBio() {
|
||||||
|
if (isLoggedIn.value == false) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
translateBioSingleRunner.afterPrevious {
|
||||||
|
try {
|
||||||
|
val result = mediaRepository.translate(
|
||||||
|
profile.value?.data?.pk?.toString() ?: return@afterPrevious,
|
||||||
|
"3"
|
||||||
|
)
|
||||||
|
if (result.isNullOrBlank()) return@afterPrevious
|
||||||
|
_eventLiveData.postValue(Event(ShowTranslation(result)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "translateBio: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,38 +520,82 @@ class ProfileFragmentViewModel(
|
|||||||
*/
|
*/
|
||||||
val username: LiveData<String> = Transformations.map(profile) {
|
val username: LiveData<String> = Transformations.map(profile) {
|
||||||
return@map when (it.status) {
|
return@map when (it.status) {
|
||||||
Resource.Status.LOADING, Resource.Status.ERROR -> ""
|
Resource.Status.ERROR -> ""
|
||||||
Resource.Status.SUCCESS -> it.data?.username ?: ""
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.username ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val profilePicUrl: LiveData<String?> = Transformations.map(profile) {
|
||||||
init {
|
return@map when (it.status) {
|
||||||
// Log.d(TAG, "${state.keys()} $userRepository $friendshipRepository $storiesRepository $mediaRepository")
|
Resource.Status.ERROR -> null
|
||||||
}
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.profilePicUrl
|
||||||
|
|
||||||
fun setCurrentUser(currentUser: Resource<User?>) {
|
|
||||||
_currentUser.postValue(currentUser)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shareDm(result: RankedRecipient) {
|
|
||||||
if (messageManager == null) {
|
|
||||||
messageManager = DirectMessagesManager
|
|
||||||
}
|
}
|
||||||
val mediaId = profile.value?.data?.pk ?: return
|
|
||||||
messageManager?.sendMedia(result, mediaId.toString(10), BroadcastItemType.PROFILE, viewModelScope)
|
|
||||||
}
|
}
|
||||||
|
val fullName: LiveData<String?> = Transformations.map(profile) {
|
||||||
fun shareDm(recipients: Set<RankedRecipient>) {
|
return@map when (it.status) {
|
||||||
if (messageManager == null) {
|
Resource.Status.ERROR -> ""
|
||||||
messageManager = DirectMessagesManager
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.fullName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val biography: LiveData<String?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> ""
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.biography
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val url: LiveData<String?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> ""
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.externalUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val followersCount: LiveData<Long?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.followerCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val followingCount: LiveData<Long?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.followingCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val postCount: LiveData<Long?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.mediaCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val isPrivate: LiveData<Boolean?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.isPrivate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val isVerified: LiveData<Boolean?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.isVerified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val friendshipStatus: LiveData<FriendshipStatus?> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.friendshipStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val profileContext: LiveData<Pair<String?, List<UserProfileContextLink>?>> = Transformations.map(profile) {
|
||||||
|
return@map when (it.status) {
|
||||||
|
Resource.Status.ERROR -> null to null
|
||||||
|
Resource.Status.LOADING, Resource.Status.SUCCESS -> it.data?.profileContext to it.data?.profileContextLinksWithUserIds
|
||||||
}
|
}
|
||||||
val mediaId = profile.value?.data?.pk ?: return
|
|
||||||
messageManager?.sendMedia(recipients, mediaId.toString(10), BroadcastItemType.PROFILE, viewModelScope)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class ProfileFragmentViewModelFactory(
|
class ProfileFragmentViewModelFactory(
|
||||||
|
private val csrfToken: String?,
|
||||||
|
private val deviceUuid: String?,
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val friendshipRepository: FriendshipRepository,
|
private val friendshipRepository: FriendshipRepository,
|
||||||
private val storiesRepository: StoriesRepository,
|
private val storiesRepository: StoriesRepository,
|
||||||
@ -235,6 +603,8 @@ class ProfileFragmentViewModelFactory(
|
|||||||
private val graphQLRepository: GraphQLRepository,
|
private val graphQLRepository: GraphQLRepository,
|
||||||
private val accountRepository: AccountRepository,
|
private val accountRepository: AccountRepository,
|
||||||
private val favoriteRepository: FavoriteRepository,
|
private val favoriteRepository: FavoriteRepository,
|
||||||
|
private val directMessagesRepository: DirectMessagesRepository,
|
||||||
|
private val messageManager: DirectMessagesManager?,
|
||||||
owner: SavedStateRegistryOwner,
|
owner: SavedStateRegistryOwner,
|
||||||
defaultArgs: Bundle? = null,
|
defaultArgs: Bundle? = null,
|
||||||
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
|
) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
|
||||||
@ -245,13 +615,16 @@ class ProfileFragmentViewModelFactory(
|
|||||||
): T {
|
): T {
|
||||||
return ProfileFragmentViewModel(
|
return ProfileFragmentViewModel(
|
||||||
handle,
|
handle,
|
||||||
|
csrfToken,
|
||||||
|
deviceUuid,
|
||||||
userRepository,
|
userRepository,
|
||||||
friendshipRepository,
|
friendshipRepository,
|
||||||
storiesRepository,
|
storiesRepository,
|
||||||
mediaRepository,
|
mediaRepository,
|
||||||
graphQLRepository,
|
graphQLRepository,
|
||||||
accountRepository,
|
|
||||||
favoriteRepository,
|
favoriteRepository,
|
||||||
|
directMessagesRepository,
|
||||||
|
messageManager,
|
||||||
Dispatchers.IO,
|
Dispatchers.IO,
|
||||||
) as T
|
) as T
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import awais.instagrabber.utils.CoroutineUtilsKt;
|
|||||||
import awais.instagrabber.utils.Debouncer;
|
import awais.instagrabber.utils.Debouncer;
|
||||||
import awais.instagrabber.utils.RankedRecipientsCache;
|
import awais.instagrabber.utils.RankedRecipientsCache;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.webservices.DirectMessagesService;
|
import awais.instagrabber.webservices.DirectMessagesRepository;
|
||||||
import awais.instagrabber.webservices.UserRepository;
|
import awais.instagrabber.webservices.UserRepository;
|
||||||
import kotlinx.coroutines.Dispatchers;
|
import kotlinx.coroutines.Dispatchers;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
@ -59,7 +59,7 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
private final Debouncer<String> searchDebouncer;
|
private final Debouncer<String> searchDebouncer;
|
||||||
private final Set<RankedRecipient> selectedRecipients = new HashSet<>();
|
private final Set<RankedRecipient> selectedRecipients = new HashSet<>();
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final DirectMessagesService directMessagesService;
|
private final DirectMessagesRepository directMessagesRepository;
|
||||||
private final RankedRecipientsCache rankedRecipientsCache;
|
private final RankedRecipientsCache rankedRecipientsCache;
|
||||||
|
|
||||||
public UserSearchViewModel() {
|
public UserSearchViewModel() {
|
||||||
@ -71,7 +71,7 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
throw new IllegalArgumentException("User is not logged in!");
|
throw new IllegalArgumentException("User is not logged in!");
|
||||||
}
|
}
|
||||||
userRepository = UserRepository.Companion.getInstance();
|
userRepository = UserRepository.Companion.getInstance();
|
||||||
directMessagesService = DirectMessagesService.INSTANCE;
|
directMessagesRepository = DirectMessagesRepository.Companion.getInstance();
|
||||||
rankedRecipientsCache = RankedRecipientsCache.INSTANCE;
|
rankedRecipientsCache = RankedRecipientsCache.INSTANCE;
|
||||||
if ((rankedRecipientsCache.isFailed() || rankedRecipientsCache.isExpired()) && !rankedRecipientsCache.isUpdateInitiated()) {
|
if ((rankedRecipientsCache.isFailed() || rankedRecipientsCache.isExpired()) && !rankedRecipientsCache.isUpdateInitiated()) {
|
||||||
updateRankedRecipientCache();
|
updateRankedRecipientCache();
|
||||||
@ -94,7 +94,7 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
|
|
||||||
private void updateRankedRecipientCache() {
|
private void updateRankedRecipientCache() {
|
||||||
rankedRecipientsCache.setUpdateInitiated(true);
|
rankedRecipientsCache.setUpdateInitiated(true);
|
||||||
directMessagesService.rankedRecipients(
|
directMessagesRepository.rankedRecipients(
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -191,7 +191,7 @@ public class UserSearchViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void rankedRecipientSearch() {
|
private void rankedRecipientSearch() {
|
||||||
directMessagesService.rankedRecipients(
|
directMessagesRepository.rankedRecipients(
|
||||||
searchMode.getName(),
|
searchMode.getName(),
|
||||||
showGroups,
|
showGroups,
|
||||||
currentQuery,
|
currentQuery,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package awais.instagrabber.webservices
|
package awais.instagrabber.webservices
|
||||||
|
|
||||||
import awais.instagrabber.repositories.DirectMessagesRepository
|
import awais.instagrabber.repositories.DirectMessagesService
|
||||||
import awais.instagrabber.repositories.requests.directmessages.*
|
import awais.instagrabber.repositories.requests.directmessages.*
|
||||||
import awais.instagrabber.repositories.responses.directmessages.*
|
import awais.instagrabber.repositories.responses.directmessages.*
|
||||||
import awais.instagrabber.repositories.responses.giphy.GiphyGif
|
import awais.instagrabber.repositories.responses.giphy.GiphyGif
|
||||||
@ -9,8 +9,7 @@ import awais.instagrabber.utils.Utils
|
|||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object DirectMessagesService {
|
open class DirectMessagesRepository(private val service: DirectMessagesService) {
|
||||||
private val repository: DirectMessagesRepository = RetrofitFactory.retrofit.create(DirectMessagesRepository::class.java)
|
|
||||||
|
|
||||||
suspend fun fetchInbox(
|
suspend fun fetchInbox(
|
||||||
cursor: String?,
|
cursor: String?,
|
||||||
@ -29,7 +28,7 @@ object DirectMessagesService {
|
|||||||
if (seqId != 0L) {
|
if (seqId != 0L) {
|
||||||
queryMap["seq_id"] = seqId.toString()
|
queryMap["seq_id"] = seqId.toString()
|
||||||
}
|
}
|
||||||
return repository.fetchInbox(queryMap)
|
return service.fetchInbox(queryMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchThread(
|
suspend fun fetchThread(
|
||||||
@ -44,10 +43,10 @@ object DirectMessagesService {
|
|||||||
if (!cursor.isNullOrBlank()) {
|
if (!cursor.isNullOrBlank()) {
|
||||||
queryMap["cursor"] = cursor
|
queryMap["cursor"] = cursor
|
||||||
}
|
}
|
||||||
return repository.fetchThread(threadId, queryMap)
|
return service.fetchThread(threadId, queryMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchUnseenCount(): DirectBadgeCount = repository.fetchUnseenCount()
|
suspend fun fetchUnseenCount(): DirectBadgeCount = service.fetchUnseenCount()
|
||||||
|
|
||||||
suspend fun broadcastText(
|
suspend fun broadcastText(
|
||||||
csrfToken: String,
|
csrfToken: String,
|
||||||
@ -61,7 +60,17 @@ object DirectMessagesService {
|
|||||||
): DirectThreadBroadcastResponse {
|
): DirectThreadBroadcastResponse {
|
||||||
val urls = extractUrls(text)
|
val urls = extractUrls(text)
|
||||||
if (urls.isNotEmpty()) {
|
if (urls.isNotEmpty()) {
|
||||||
return broadcastLink(csrfToken, userId, deviceUuid, clientContext, threadIdsOrUserIds, text, urls, repliedToItemId, repliedToClientContext)
|
return broadcastLink(
|
||||||
|
csrfToken,
|
||||||
|
userId,
|
||||||
|
deviceUuid,
|
||||||
|
clientContext,
|
||||||
|
threadIdsOrUserIds,
|
||||||
|
text,
|
||||||
|
urls,
|
||||||
|
repliedToItemId,
|
||||||
|
repliedToClientContext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val broadcastOptions = TextBroadcastOptions(clientContext, threadIdsOrUserIds, text)
|
val broadcastOptions = TextBroadcastOptions(clientContext, threadIdsOrUserIds, text)
|
||||||
if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) {
|
if (!repliedToItemId.isNullOrBlank() && !repliedToClientContext.isNullOrBlank()) {
|
||||||
@ -211,7 +220,7 @@ object DirectMessagesService {
|
|||||||
form.putAll(broadcastOptions.formMap)
|
form.putAll(broadcastOptions.formMap)
|
||||||
form["action"] = "send_item"
|
form["action"] = "send_item"
|
||||||
// val signedForm = Utils.sign(form)
|
// val signedForm = Utils.sign(form)
|
||||||
return repository.broadcast(broadcastOptions.itemType.value, form)
|
return service.broadcast(broadcastOptions.itemType.value, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addUsers(
|
suspend fun addUsers(
|
||||||
@ -225,7 +234,7 @@ object DirectMessagesService {
|
|||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
"user_ids" to JSONArray(userIds).toString(),
|
"user_ids" to JSONArray(userIds).toString(),
|
||||||
)
|
)
|
||||||
return repository.addUsers(threadId, form)
|
return service.addUsers(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeUsers(
|
suspend fun removeUsers(
|
||||||
@ -239,7 +248,7 @@ object DirectMessagesService {
|
|||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
"user_ids" to JSONArray(userIds).toString(),
|
"user_ids" to JSONArray(userIds).toString(),
|
||||||
)
|
)
|
||||||
return repository.removeUsers(threadId, form)
|
return service.removeUsers(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateTitle(
|
suspend fun updateTitle(
|
||||||
@ -253,7 +262,7 @@ object DirectMessagesService {
|
|||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
"title" to title,
|
"title" to title,
|
||||||
)
|
)
|
||||||
return repository.updateTitle(threadId, form)
|
return service.updateTitle(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addAdmins(
|
suspend fun addAdmins(
|
||||||
@ -267,7 +276,7 @@ object DirectMessagesService {
|
|||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
"user_ids" to JSONArray(userIds).toString(),
|
"user_ids" to JSONArray(userIds).toString(),
|
||||||
)
|
)
|
||||||
return repository.addAdmins(threadId, form)
|
return service.addAdmins(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun removeAdmins(
|
suspend fun removeAdmins(
|
||||||
@ -281,7 +290,7 @@ object DirectMessagesService {
|
|||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
"user_ids" to JSONArray(userIds).toString(),
|
"user_ids" to JSONArray(userIds).toString(),
|
||||||
)
|
)
|
||||||
return repository.removeAdmins(threadId, form)
|
return service.removeAdmins(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteItem(
|
suspend fun deleteItem(
|
||||||
@ -294,7 +303,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.deleteItem(threadId, itemId, form)
|
return service.deleteItem(threadId, itemId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun rankedRecipients(
|
suspend fun rankedRecipients(
|
||||||
@ -316,7 +325,7 @@ object DirectMessagesService {
|
|||||||
if (showThreads != null) {
|
if (showThreads != null) {
|
||||||
queryMap["showThreads"] = showThreads.toString()
|
queryMap["showThreads"] = showThreads.toString()
|
||||||
}
|
}
|
||||||
return repository.rankedRecipients(queryMap)
|
return service.rankedRecipients(queryMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun forward(
|
suspend fun forward(
|
||||||
@ -332,7 +341,7 @@ object DirectMessagesService {
|
|||||||
"forwarded_from_thread_id" to fromThreadId,
|
"forwarded_from_thread_id" to fromThreadId,
|
||||||
"forwarded_from_thread_item_id" to itemId,
|
"forwarded_from_thread_item_id" to itemId,
|
||||||
)
|
)
|
||||||
return repository.forward(form)
|
return service.forward(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createThread(
|
suspend fun createThread(
|
||||||
@ -353,7 +362,7 @@ object DirectMessagesService {
|
|||||||
form["thread_title"] = threadTitle
|
form["thread_title"] = threadTitle
|
||||||
}
|
}
|
||||||
val signedForm = Utils.sign(form)
|
val signedForm = Utils.sign(form)
|
||||||
return repository.createThread(signedForm)
|
return service.createThread(signedForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun mute(
|
suspend fun mute(
|
||||||
@ -365,7 +374,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid
|
"_uuid" to deviceUuid
|
||||||
)
|
)
|
||||||
return repository.mute(threadId, form)
|
return service.mute(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unmute(
|
suspend fun unmute(
|
||||||
@ -377,7 +386,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.unmute(threadId, form)
|
return service.unmute(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun muteMentions(
|
suspend fun muteMentions(
|
||||||
@ -389,7 +398,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.muteMentions(threadId, form)
|
return service.muteMentions(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unmuteMentions(
|
suspend fun unmuteMentions(
|
||||||
@ -401,7 +410,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.unmuteMentions(threadId, form)
|
return service.unmuteMentions(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun participantRequests(
|
suspend fun participantRequests(
|
||||||
@ -409,7 +418,7 @@ object DirectMessagesService {
|
|||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
cursor: String? = null,
|
cursor: String? = null,
|
||||||
): DirectThreadParticipantRequestsResponse {
|
): DirectThreadParticipantRequestsResponse {
|
||||||
return repository.participantRequests(threadId, pageSize, cursor)
|
return service.participantRequests(threadId, pageSize, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun approveParticipantRequests(
|
suspend fun approveParticipantRequests(
|
||||||
@ -424,7 +433,7 @@ object DirectMessagesService {
|
|||||||
"user_ids" to JSONArray(userIds).toString(),
|
"user_ids" to JSONArray(userIds).toString(),
|
||||||
// "share_join_chat_story" to String.valueOf(true)
|
// "share_join_chat_story" to String.valueOf(true)
|
||||||
)
|
)
|
||||||
return repository.approveParticipantRequests(threadId, form)
|
return service.approveParticipantRequests(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun declineParticipantRequests(
|
suspend fun declineParticipantRequests(
|
||||||
@ -438,7 +447,7 @@ object DirectMessagesService {
|
|||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
"user_ids" to JSONArray(userIds).toString(),
|
"user_ids" to JSONArray(userIds).toString(),
|
||||||
)
|
)
|
||||||
return repository.declineParticipantRequests(threadId, form)
|
return service.declineParticipantRequests(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun approvalRequired(
|
suspend fun approvalRequired(
|
||||||
@ -450,7 +459,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.approvalRequired(threadId, form)
|
return service.approvalRequired(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun approvalNotRequired(
|
suspend fun approvalNotRequired(
|
||||||
@ -462,7 +471,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.approvalNotRequired(threadId, form)
|
return service.approvalNotRequired(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun leave(
|
suspend fun leave(
|
||||||
@ -474,7 +483,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.leave(threadId, form)
|
return service.leave(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun end(
|
suspend fun end(
|
||||||
@ -486,7 +495,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.end(threadId, form)
|
return service.end(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchPendingInbox(cursor: String?, seqId: Long): DirectInboxResponse {
|
suspend fun fetchPendingInbox(cursor: String?, seqId: Long): DirectInboxResponse {
|
||||||
@ -503,7 +512,7 @@ object DirectMessagesService {
|
|||||||
if (seqId != 0L) {
|
if (seqId != 0L) {
|
||||||
queryMap["seq_id"] = seqId.toString()
|
queryMap["seq_id"] = seqId.toString()
|
||||||
}
|
}
|
||||||
return repository.fetchPendingInbox(queryMap)
|
return service.fetchPendingInbox(queryMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun approveRequest(
|
suspend fun approveRequest(
|
||||||
@ -515,7 +524,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.approveRequest(threadId, form)
|
return service.approveRequest(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun declineRequest(
|
suspend fun declineRequest(
|
||||||
@ -527,7 +536,7 @@ object DirectMessagesService {
|
|||||||
"_csrftoken" to csrfToken,
|
"_csrftoken" to csrfToken,
|
||||||
"_uuid" to deviceUuid,
|
"_uuid" to deviceUuid,
|
||||||
)
|
)
|
||||||
return repository.declineRequest(threadId, form)
|
return service.declineRequest(threadId, form)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsSeen(
|
suspend fun markAsSeen(
|
||||||
@ -545,6 +554,18 @@ object DirectMessagesService {
|
|||||||
"thread_id" to threadId,
|
"thread_id" to threadId,
|
||||||
"item_id" to itemId,
|
"item_id" to itemId,
|
||||||
)
|
)
|
||||||
return repository.markItemSeen(threadId, itemId, form)
|
return service.markItemSeen(threadId, itemId, form)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: DirectMessagesRepository? = null
|
||||||
|
|
||||||
|
fun getInstance(): DirectMessagesRepository {
|
||||||
|
return INSTANCE ?: synchronized(this) {
|
||||||
|
val service: DirectMessagesService = RetrofitFactory.retrofit.create(DirectMessagesService::class.java)
|
||||||
|
DirectMessagesRepository(service).also { INSTANCE = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -110,14 +110,17 @@ class MediaRepository(private val service: MediaService) {
|
|||||||
suspend fun translate(
|
suspend fun translate(
|
||||||
id: String,
|
id: String,
|
||||||
type: String, // 1 caption 2 comment 3 bio
|
type: String, // 1 caption 2 comment 3 bio
|
||||||
): String {
|
): String? {
|
||||||
val form = mapOf(
|
val form = mapOf(
|
||||||
"id" to id,
|
"id" to id,
|
||||||
"type" to type,
|
"type" to type,
|
||||||
)
|
)
|
||||||
val response = service.translate(form)
|
val response = service.translate(form)
|
||||||
val jsonObject = JSONObject(response)
|
val jsonObject = JSONObject(response)
|
||||||
return jsonObject.optString("translation")
|
if (!jsonObject.has("translation") || jsonObject.isNull("translation")) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return jsonObject.getString("translation")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun uploadFinish(
|
suspend fun uploadFinish(
|
||||||
|
@ -28,7 +28,7 @@ object RetrofitFactory {
|
|||||||
addInterceptor(AddCookiesInterceptor())
|
addInterceptor(AddCookiesInterceptor())
|
||||||
addInterceptor(igErrorsInterceptor)
|
addInterceptor(igErrorsInterceptor)
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
// addInterceptor(new LoggingInterceptor());
|
// addInterceptor(LoggingInterceptor())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val gson = GsonBuilder().apply {
|
val gson = GsonBuilder().apply {
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
app:layoutDescription="@xml/header_list_scene">
|
app:layoutDescription="@xml/header_list_scene"
|
||||||
|
tools:layoutDescription="@xml/profile_fragment_no_acc_layout">
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/header"
|
android:id="@+id/header"
|
||||||
|
@ -92,6 +92,7 @@
|
|||||||
<string name="story_mentions">Mentions</string>
|
<string name="story_mentions">Mentions</string>
|
||||||
<string name="priv_acc">This Account is Private</string>
|
<string name="priv_acc">This Account is Private</string>
|
||||||
<string name="priv_acc_confirm">You won\'t be able to access posts after unfollowing! Are you sure?</string>
|
<string name="priv_acc_confirm">You won\'t be able to access posts after unfollowing! Are you sure?</string>
|
||||||
|
<string name="are_you_sure">Are you sure?</string>
|
||||||
<string name="no_acc">You can log in via More -> Account on the bottom-right corner or you can view public accounts without login!</string>
|
<string name="no_acc">You can log in via More -> Account on the bottom-right corner or you can view public accounts without login!</string>
|
||||||
<string name="empty_acc">This Account has No Posts</string>
|
<string name="empty_acc">This Account has No Posts</string>
|
||||||
<string name="empty_list">No Such Posts!</string>
|
<string name="empty_list">No Such Posts!</string>
|
||||||
|
28
app/src/main/res/xml/profile_fragment_no_acc_layout.xml
Normal file
28
app/src/main/res/xml/profile_fragment_no_acc_layout.xml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:motion="http://schemas.android.com/apk/res-auto">
|
||||||
|
<ConstraintSet android:id="@+id/start">
|
||||||
|
<Constraint
|
||||||
|
android:id="@+id/header"
|
||||||
|
android:visibility="gone" />
|
||||||
|
<Constraint
|
||||||
|
android:id="@+id/privatePage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="visible" />
|
||||||
|
<Constraint
|
||||||
|
android:id="@+id/swipe_refresh_layout"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</ConstraintSet>
|
||||||
|
<ConstraintSet
|
||||||
|
android:id="@+id/end"
|
||||||
|
motion:deriveConstraintsFrom="@id/start">
|
||||||
|
<Constraint android:id="@+id/header" />
|
||||||
|
<Constraint android:id="@+id/privatePage" />
|
||||||
|
<Constraint android:id="@+id/swipe_refresh_layout" />
|
||||||
|
</ConstraintSet>
|
||||||
|
<Transition
|
||||||
|
android:id="@+id/transition"
|
||||||
|
motion:constraintSetEnd="@+id/end"
|
||||||
|
motion:constraintSetStart="@+id/start" />
|
||||||
|
</MotionScene>
|
@ -7,6 +7,7 @@ import awais.instagrabber.db.entities.Favorite
|
|||||||
import awais.instagrabber.models.enums.FavoriteType
|
import awais.instagrabber.models.enums.FavoriteType
|
||||||
import awais.instagrabber.repositories.*
|
import awais.instagrabber.repositories.*
|
||||||
import awais.instagrabber.repositories.responses.*
|
import awais.instagrabber.repositories.responses.*
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.*
|
||||||
|
|
||||||
open class UserServiceAdapter : UserService {
|
open class UserServiceAdapter : UserService {
|
||||||
override suspend fun getUserInfo(uid: Long): WrappedUser {
|
override suspend fun getUserInfo(uid: Long): WrappedUser {
|
||||||
@ -167,3 +168,118 @@ open class FavoriteDaoAdapter : FavoriteDao {
|
|||||||
|
|
||||||
override suspend fun deleteAllFavorites() {}
|
override suspend fun deleteAllFavorites() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class DirectMessagesServiceAdapter: DirectMessagesService {
|
||||||
|
override suspend fun fetchInbox(queryMap: Map<String, String>): DirectInboxResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchPendingInbox(queryMap: Map<String, String>): DirectInboxResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchThread(threadId: String, queryMap: Map<String, String>): DirectThreadFeedResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchUnseenCount(): DirectBadgeCount {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun broadcast(item: String, signedForm: Map<String, String>): DirectThreadBroadcastResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addUsers(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeUsers(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateTitle(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun addAdmins(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeAdmins(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteItem(threadId: String, itemId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rankedRecipients(queryMap: Map<String, String>): RankedRecipientsResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun forward(form: Map<String, String>): DirectThreadBroadcastResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createThread(signedForm: Map<String, String>): DirectThread {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun mute(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unmute(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun muteMentions(threadId: String, form: Map<String, String?>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unmuteMentions(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun participantRequests(threadId: String, pageSize: Int, cursor: String?): DirectThreadParticipantRequestsResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun approveParticipantRequests(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun declineParticipantRequests(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun approvalRequired(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun approvalNotRequired(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun leave(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun end(threadId: String, form: Map<String, String>): DirectThreadDetailsChangeResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun approveRequest(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun declineRequest(threadId: String, form: Map<String, String>): String {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun markItemSeen(threadId: String, itemId: String, form: Map<String, String>): DirectItemSeenResponse {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,10 +5,8 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import awais.instagrabber.MainCoroutineScopeRule
|
import awais.instagrabber.MainCoroutineScopeRule
|
||||||
import awais.instagrabber.common.*
|
import awais.instagrabber.common.*
|
||||||
import awais.instagrabber.db.datasources.AccountDataSource
|
|
||||||
import awais.instagrabber.db.datasources.FavoriteDataSource
|
import awais.instagrabber.db.datasources.FavoriteDataSource
|
||||||
import awais.instagrabber.db.entities.Favorite
|
import awais.instagrabber.db.entities.Favorite
|
||||||
import awais.instagrabber.db.repositories.AccountRepository
|
|
||||||
import awais.instagrabber.db.repositories.FavoriteRepository
|
import awais.instagrabber.db.repositories.FavoriteRepository
|
||||||
import awais.instagrabber.getOrAwaitValue
|
import awais.instagrabber.getOrAwaitValue
|
||||||
import awais.instagrabber.models.HighlightModel
|
import awais.instagrabber.models.HighlightModel
|
||||||
@ -21,6 +19,7 @@ import awais.instagrabber.repositories.responses.User
|
|||||||
import awais.instagrabber.webservices.*
|
import awais.instagrabber.webservices.*
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.jupiter.api.Assertions.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
@ -37,30 +36,41 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val coroutineScope = MainCoroutineScopeRule()
|
val coroutineScope = MainCoroutineScopeRule()
|
||||||
|
|
||||||
private val testPublicUser = User(
|
private lateinit var testPublicUser: User
|
||||||
pk = 100,
|
private lateinit var testPublicUser1: User
|
||||||
username = "test",
|
|
||||||
fullName = "Test user"
|
|
||||||
)
|
|
||||||
|
|
||||||
private val testPublicUser1 = User(
|
private val csrfToken = "csrfToken"
|
||||||
pk = 101,
|
private val deviceUuid = "deviceUuid"
|
||||||
username = "test1",
|
|
||||||
fullName = "Test1 user1"
|
@Before
|
||||||
)
|
fun setup() {
|
||||||
|
testPublicUser = User(
|
||||||
|
pk = 100,
|
||||||
|
username = "test",
|
||||||
|
fullName = "Test user"
|
||||||
|
)
|
||||||
|
testPublicUser1 = User(
|
||||||
|
pk = 101,
|
||||||
|
username = "test1",
|
||||||
|
fullName = "Test1 user1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
@Test
|
@Test
|
||||||
fun `no state username and null current user`() {
|
fun `no state username and null current user`() {
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
SavedStateHandle(),
|
SavedStateHandle(),
|
||||||
|
null,
|
||||||
|
deviceUuid,
|
||||||
UserRepository(UserServiceAdapter()),
|
UserRepository(UserServiceAdapter()),
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
StoriesRepository(StoriesServiceAdapter()),
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
GraphQLRepository(GraphQLServiceAdapter()),
|
GraphQLRepository(GraphQLServiceAdapter()),
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
|
assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
|
||||||
@ -76,13 +86,16 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
fun `no state username with current user provided`() {
|
fun `no state username with current user provided`() {
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
SavedStateHandle(),
|
SavedStateHandle(),
|
||||||
|
csrfToken,
|
||||||
|
deviceUuid,
|
||||||
UserRepository(UserServiceAdapter()),
|
UserRepository(UserServiceAdapter()),
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
StoriesRepository(StoriesServiceAdapter()),
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
GraphQLRepository(GraphQLServiceAdapter()),
|
GraphQLRepository(GraphQLServiceAdapter()),
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
|
assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
|
||||||
@ -128,13 +141,16 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
}
|
}
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
state,
|
state,
|
||||||
|
null,
|
||||||
|
deviceUuid,
|
||||||
UserRepository(UserServiceAdapter()),
|
UserRepository(UserServiceAdapter()),
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
StoriesRepository(StoriesServiceAdapter()),
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
graphQLRepository,
|
graphQLRepository,
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
viewModel.setCurrentUser(Resource.success(null))
|
viewModel.setCurrentUser(Resource.success(null))
|
||||||
@ -179,13 +195,16 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
}
|
}
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
state,
|
state,
|
||||||
|
csrfToken,
|
||||||
|
deviceUuid,
|
||||||
userRepository,
|
userRepository,
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
StoriesRepository(StoriesServiceAdapter()),
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
GraphQLRepository(GraphQLServiceAdapter()),
|
GraphQLRepository(GraphQLServiceAdapter()),
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
viewModel.setCurrentUser(Resource.success(User()))
|
viewModel.setCurrentUser(Resource.success(User()))
|
||||||
@ -215,13 +234,16 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
}
|
}
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
state,
|
state,
|
||||||
|
null,
|
||||||
|
deviceUuid,
|
||||||
UserRepository(UserServiceAdapter()),
|
UserRepository(UserServiceAdapter()),
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
StoriesRepository(StoriesServiceAdapter()),
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
graphQLRepository,
|
graphQLRepository,
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
viewModel.setCurrentUser(Resource.success(null))
|
viewModel.setCurrentUser(Resource.success(null))
|
||||||
@ -267,13 +289,16 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
}))
|
}))
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
state,
|
state,
|
||||||
|
null,
|
||||||
|
deviceUuid,
|
||||||
UserRepository(UserServiceAdapter()),
|
UserRepository(UserServiceAdapter()),
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
StoriesRepository(StoriesServiceAdapter()),
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
graphQLRepository,
|
graphQLRepository,
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
favoriteRepository,
|
favoriteRepository,
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
viewModel.setCurrentUser(Resource.success(null))
|
viewModel.setCurrentUser(Resource.success(null))
|
||||||
@ -306,13 +331,16 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
}
|
}
|
||||||
val viewModel = ProfileFragmentViewModel(
|
val viewModel = ProfileFragmentViewModel(
|
||||||
state,
|
state,
|
||||||
|
csrfToken,
|
||||||
|
deviceUuid,
|
||||||
userRepository,
|
userRepository,
|
||||||
FriendshipRepository(FriendshipServiceAdapter()),
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
storiesRepository,
|
storiesRepository,
|
||||||
MediaRepository(MediaServiceAdapter()),
|
MediaRepository(MediaServiceAdapter()),
|
||||||
GraphQLRepository(GraphQLServiceAdapter()),
|
GraphQLRepository(GraphQLServiceAdapter()),
|
||||||
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
|
||||||
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
coroutineScope.dispatcher,
|
coroutineScope.dispatcher,
|
||||||
)
|
)
|
||||||
viewModel.setCurrentUser(Resource.success(User()))
|
viewModel.setCurrentUser(Resource.success(User()))
|
||||||
@ -332,4 +360,45 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
}
|
}
|
||||||
assertEquals(testUserHighlights, userHighlights.data)
|
assertEquals(testUserHighlights, userHighlights.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
@Test
|
||||||
|
fun `should refresh correctly`() {
|
||||||
|
val state = SavedStateHandle(
|
||||||
|
mutableMapOf<String, Any?>(
|
||||||
|
"username" to testPublicUser.username
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val graphQLRepository = object : GraphQLRepository(GraphQLServiceAdapter()) {
|
||||||
|
override suspend fun fetchUser(username: String): User = testPublicUser
|
||||||
|
}
|
||||||
|
val viewModel = ProfileFragmentViewModel(
|
||||||
|
state,
|
||||||
|
null,
|
||||||
|
deviceUuid,
|
||||||
|
UserRepository(UserServiceAdapter()),
|
||||||
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
|
StoriesRepository(StoriesServiceAdapter()),
|
||||||
|
MediaRepository(MediaServiceAdapter()),
|
||||||
|
graphQLRepository,
|
||||||
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
DirectMessagesRepository(DirectMessagesServiceAdapter()),
|
||||||
|
null,
|
||||||
|
coroutineScope.dispatcher,
|
||||||
|
)
|
||||||
|
viewModel.setCurrentUser(Resource.success(null))
|
||||||
|
assertEquals(false, viewModel.isLoggedIn.getOrAwaitValue())
|
||||||
|
var profile = viewModel.profile.getOrAwaitValue()
|
||||||
|
while (profile.status == Resource.Status.LOADING) {
|
||||||
|
profile = viewModel.profile.getOrAwaitValue()
|
||||||
|
}
|
||||||
|
assertEquals(testPublicUser, profile.data)
|
||||||
|
testPublicUser = testPublicUser.copy(biography = "new bio")
|
||||||
|
viewModel.refresh()
|
||||||
|
profile = viewModel.profile.getOrAwaitValue()
|
||||||
|
while (profile.status == Resource.Status.LOADING) {
|
||||||
|
profile = viewModel.profile.getOrAwaitValue()
|
||||||
|
}
|
||||||
|
assertEquals(testPublicUser, profile.data)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user