diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java deleted file mode 100644 index d5c316c1..00000000 --- a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java +++ /dev/null @@ -1,129 +0,0 @@ -package awais.instagrabber.activities; - -import android.annotation.SuppressLint; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.DocumentsContract; -import android.util.Log; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.ViewModelProvider; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; - -import awais.instagrabber.R; -import awais.instagrabber.databinding.ActivityDirectorySelectBinding; -import awais.instagrabber.dialogs.ConfirmDialogFragment; -import awais.instagrabber.utils.AppExecutors; -import awais.instagrabber.viewmodels.DirectorySelectActivityViewModel; - -public class DirectorySelectActivity extends BaseLanguageActivity { - private static final String TAG = DirectorySelectActivity.class.getSimpleName(); - public static final int SELECT_DIR_REQUEST_CODE = 0x01; - private static final int ERROR_REQUEST_CODE = 0x02; - - private Uri initialUri; - private ActivityDirectorySelectBinding binding; - private DirectorySelectActivityViewModel viewModel; - - @Override - protected void onCreate(@Nullable final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = ActivityDirectorySelectBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - viewModel = new ViewModelProvider(this).get(DirectorySelectActivityViewModel.class); - setupObservers(); - binding.selectDir.setOnClickListener(v -> openDirectoryChooser()); - AppExecutors.INSTANCE.getMainThread().execute(() -> viewModel.setInitialUri(getIntent())); - } - - private void setupObservers() { - viewModel.getMessage().observe(this, message -> binding.message.setText(message)); - viewModel.getPrevUri().observe(this, prevUri -> { - if (prevUri == null) { - binding.prevUri.setVisibility(View.GONE); - binding.message2.setVisibility(View.GONE); - return; - } - binding.prevUri.setText(prevUri); - binding.prevUri.setVisibility(View.VISIBLE); - binding.message2.setVisibility(View.VISIBLE); - }); - viewModel.getDirSuccess().observe(this, success -> binding.selectDir.setVisibility(success ? View.GONE : View.VISIBLE)); - viewModel.isLoading().observe(this, loading -> { - binding.message.setVisibility(loading ? View.GONE : View.VISIBLE); - binding.loadingIndicator.setVisibility(loading ? View.VISIBLE : View.GONE); - }); - } - - private void openDirectoryChooser() { - final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); - } - try { - startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "openDirectoryChooser: ", e); - showErrorDialog(getString(R.string.no_directory_picker_activity)); - } catch (Exception e) { - Log.e(TAG, "openDirectoryChooser: ", e); - } - } - - @SuppressLint("StringFormatInvalid") - @Override - protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode != SELECT_DIR_REQUEST_CODE) return; - if (resultCode != RESULT_OK) { - showErrorDialog(getString(R.string.select_a_folder)); - return; - } - if (data == null || data.getData() == null) { - showErrorDialog(getString(R.string.select_a_folder)); - return; - } - if (!"com.android.externalstorage.documents".equals(data.getData().getAuthority())) { - showErrorDialog(getString(R.string.dir_select_no_download_folder, data.getData().getAuthority())); - return; - } - AppExecutors.INSTANCE.getMainThread().execute(() -> { - try { - viewModel.setupSelectedDir(data); - final Intent intent = new Intent(this, MainActivity.class); - startActivity(intent); - finish(); - } catch (Exception e) { - // Should not come to this point. - // If it does, we have to show this error to the user so that they can report it. - try (final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw)) { - e.printStackTrace(pw); - showErrorDialog("Please report this error to the developers:\n\n" + sw.toString()); - } catch (IOException ioException) { - Log.e(TAG, "onActivityResult: ", ioException); - } - } - }, 500); - } - - private void showErrorDialog(@NonNull final String message) { - final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( - ERROR_REQUEST_CODE, - R.string.error, - message, - R.string.ok, - 0, - 0 - ); - dialogFragment.show(getSupportFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); - } -} diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.kt b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.kt new file mode 100644 index 00000000..d8c36d62 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.kt @@ -0,0 +1,132 @@ +package awais.instagrabber.activities + +import android.annotation.SuppressLint +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.DocumentsContract +import android.util.Log +import android.view.View +import androidx.activity.viewModels +import awais.instagrabber.R +import awais.instagrabber.databinding.ActivityDirectorySelectBinding +import awais.instagrabber.dialogs.ConfirmDialogFragment +import awais.instagrabber.utils.AppExecutors.mainThread +import awais.instagrabber.utils.Constants +import awais.instagrabber.utils.extensions.TAG +import awais.instagrabber.viewmodels.DirectorySelectActivityViewModel +import java.io.IOException +import java.io.PrintWriter +import java.io.StringWriter + +class DirectorySelectActivity : BaseLanguageActivity() { + private var initialUri: Uri? = null + + private lateinit var binding: ActivityDirectorySelectBinding + + private val viewModel: DirectorySelectActivityViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityDirectorySelectBinding.inflate(layoutInflater) + setContentView(binding.root) + val intent = intent + viewModel.setInitialUri(intent) + setupObservers() + binding.selectDir.setOnClickListener { openDirectoryChooser() } + initialUri = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI) + } + + private fun setupObservers() { + viewModel.message.observe(this, { message: String? -> binding.message.text = message }) + viewModel.prevUri.observe(this, { prevUri: String? -> + if (prevUri == null) { + binding.prevUri.visibility = View.GONE + binding.message2.visibility = View.GONE + return@observe + } + binding.prevUri.text = prevUri + binding.prevUri.visibility = View.VISIBLE + binding.message2.visibility = View.VISIBLE + }) + viewModel.dirSuccess.observe(this, { success: Boolean -> binding.selectDir.visibility = if (success) View.GONE else View.VISIBLE }) + viewModel.loading.observe(this, { loading: Boolean -> + binding.message.visibility = if (loading) View.GONE else View.VISIBLE + binding.loadingIndicator.visibility = if (loading) View.VISIBLE else View.GONE + }) + } + + private fun openDirectoryChooser() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri) + } + try { + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, "openDirectoryChooser: ", e) + showErrorDialog(getString(R.string.no_directory_picker_activity)) + } catch (e: Exception) { + Log.e(TAG, "openDirectoryChooser: ", e) + } + } + + @SuppressLint("StringFormatInvalid") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode != SELECT_DIR_REQUEST_CODE) return + if (resultCode != RESULT_OK) { + showErrorDialog(getString(R.string.select_a_folder)) + return + } + if (data == null || data.data == null) { + showErrorDialog(getString(R.string.select_a_folder)) + return + } + val authority = data.data?.authority + if ("com.android.externalstorage.documents" != authority) { + showErrorDialog(getString(R.string.dir_select_no_download_folder, authority)) + return + } + mainThread.execute({ + try { + viewModel.setupSelectedDir(data) + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + finish() + } catch (e: Exception) { + // Should not come to this point. + // If it does, we have to show this error to the user so that they can report it. + try { + StringWriter().use { sw -> + PrintWriter(sw).use { pw -> + e.printStackTrace(pw) + showErrorDialog("Please report this error to the developers:\n\n$sw") + } + } + } catch (ioException: IOException) { + Log.e(TAG, "onActivityResult: ", ioException) + } + } + }, 500) + } + + private fun showErrorDialog(message: String) { + val dialogFragment = ConfirmDialogFragment.newInstance( + ERROR_REQUEST_CODE, + R.string.error, + message, + R.string.ok, + 0, + 0 + ) + dialogFragment.show(supportFragmentManager, ConfirmDialogFragment::class.java.simpleName) + } + + companion object { + const val SELECT_DIR_REQUEST_CODE = 0x01 + private const val ERROR_REQUEST_CODE = 0x02 + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java index 55fd2b19..22d11000 100644 --- a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.ListAdapter; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import awais.instagrabber.adapters.viewholder.NotificationViewHolder; @@ -24,12 +25,12 @@ public final class NotificationsAdapter extends ListAdapter DIFF_CALLBACK = new DiffUtil.ItemCallback() { @Override public boolean areItemsTheSame(final Notification oldItem, final Notification newItem) { - return oldItem.getPk().equals(newItem.getPk()); + return Objects.requireNonNull(oldItem.getPk()).equals(newItem.getPk()); } @Override public boolean areContentsTheSame(@NonNull final Notification oldItem, @NonNull final Notification newItem) { - return oldItem.getPk().equals(newItem.getPk()) && oldItem.getType() == newItem.getType(); + return Objects.requireNonNull(oldItem.getPk()).equals(newItem.getPk()) && Objects.equals(oldItem.getType(), newItem.getType()); } }; @@ -72,8 +73,8 @@ public final class NotificationsAdapter extends ListAdapter sort(final List list) { final List listCopy = new ArrayList<>(list).stream() - .filter(i -> i.getType() != null) - .collect(Collectors.toList()); + .filter(i -> i.getType() != null) + .collect(Collectors.toList()); Collections.sort(listCopy, (o1, o2) -> { // keep requests at top if (o1.getType() == o2.getType() diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java index 98c9fb14..eef9c45f 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java @@ -43,7 +43,9 @@ public class DirectItemLinkViewHolder extends DirectItemViewHolder { @Override public void bindItem(final DirectItem item, final MessageDirection messageDirection) { final DirectItemLink link = item.getLink(); + if (link == null) return; final DirectItemLinkContext linkContext = link.getLinkContext(); + if (linkContext == null) return; final String linkImageUrl = linkContext.getLinkImageUrl(); if (TextUtils.isEmpty(linkImageUrl)) { binding.preview.setVisibility(View.GONE); diff --git a/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java deleted file mode 100644 index 9e35ae54..00000000 --- a/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java +++ /dev/null @@ -1,216 +0,0 @@ -package awais.instagrabber.dialogs; - -import android.app.Dialog; -import android.content.Context; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.Window; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import awais.instagrabber.R; -import awais.instagrabber.databinding.DialogPostLayoutPreferencesBinding; -import awais.instagrabber.models.PostsLayoutPreferences; - -import static awais.instagrabber.utils.Utils.settingsHelper; - -public class PostsLayoutPreferencesDialogFragment extends DialogFragment { - - private final OnApplyListener onApplyListener; - private final PostsLayoutPreferences.Builder preferencesBuilder; - private final String layoutPreferenceKey; - private DialogPostLayoutPreferencesBinding binding; - private Context context; - - public PostsLayoutPreferencesDialogFragment(final String layoutPreferenceKey, - @NonNull final OnApplyListener onApplyListener) { - this.layoutPreferenceKey = layoutPreferenceKey; - final PostsLayoutPreferences preferences = PostsLayoutPreferences.fromJson(settingsHelper.getString(layoutPreferenceKey)); - this.preferencesBuilder = PostsLayoutPreferences.builder().mergeFrom(preferences); - this.onApplyListener = onApplyListener; - } - - @Override - public void onAttach(@NonNull final Context context) { - super.onAttach(context); - this.context = context; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { - binding = DialogPostLayoutPreferencesBinding.inflate(LayoutInflater.from(context), null, false); - init(); - return new MaterialAlertDialogBuilder(context) - .setView(binding.getRoot()) - .setPositiveButton(R.string.apply, (dialog, which) -> { - final PostsLayoutPreferences preferences = preferencesBuilder.build(); - final String json = preferences.getJson(); - settingsHelper.putString(layoutPreferenceKey, json); - onApplyListener.onApply(preferences); - }) - .create(); - } - - @Override - public void onStart() { - super.onStart(); - final Dialog dialog = getDialog(); - if (dialog == null) return; - final Window window = dialog.getWindow(); - if (window == null) return; - window.setWindowAnimations(R.style.dialog_window_animation); - } - - private void init() { - initLayoutToggle(); - if (preferencesBuilder.getType() != PostsLayoutPreferences.PostsLayoutType.LINEAR) { - initStaggeredOrGridOptions(); - } - } - - private void initStaggeredOrGridOptions() { - initColCountToggle(); - initNamesToggle(); - initAvatarsToggle(); - initCornersToggle(); - initGapToggle(); - } - - private void initLayoutToggle() { - final int selectedLayoutId = getSelectedLayoutId(); - binding.layoutToggle.check(selectedLayoutId); - if (selectedLayoutId == R.id.layout_linear) { - binding.staggeredOrGridOptions.setVisibility(View.GONE); - } - binding.layoutToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { - if (isChecked) { - if (checkedId == R.id.layout_linear) { - preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.LINEAR); - preferencesBuilder.setColCount(1); - binding.staggeredOrGridOptions.setVisibility(View.GONE); - } else if (checkedId == R.id.layout_staggered) { - preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.STAGGERED_GRID); - if (preferencesBuilder.getColCount() == 1) { - preferencesBuilder.setColCount(2); - } - binding.staggeredOrGridOptions.setVisibility(View.VISIBLE); - initStaggeredOrGridOptions(); - } else { - preferencesBuilder.setType(PostsLayoutPreferences.PostsLayoutType.GRID); - if (preferencesBuilder.getColCount() == 1) { - preferencesBuilder.setColCount(2); - } - binding.staggeredOrGridOptions.setVisibility(View.VISIBLE); - initStaggeredOrGridOptions(); - } - } - }); - } - - private void initColCountToggle() { - binding.colCountToggle.check(getSelectedColCountId()); - binding.colCountToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { - if (!isChecked) return; - if (checkedId == R.id.col_count_two) { - preferencesBuilder.setColCount(2); - } else { - preferencesBuilder.setColCount(3); - } - }); - } - - private void initAvatarsToggle() { - binding.showAvatarToggle.setChecked(preferencesBuilder.isAvatarVisible()); - binding.avatarSizeToggle.check(getSelectedAvatarSizeId()); - binding.showAvatarToggle.setOnCheckedChangeListener((buttonView, isChecked) -> { - preferencesBuilder.setAvatarVisible(isChecked); - binding.labelAvatarSize.setVisibility(isChecked ? View.VISIBLE : View.GONE); - binding.avatarSizeToggle.setVisibility(isChecked ? View.VISIBLE : View.GONE); - }); - binding.labelAvatarSize.setVisibility(preferencesBuilder.isAvatarVisible() ? View.VISIBLE : View.GONE); - binding.avatarSizeToggle.setVisibility(preferencesBuilder.isAvatarVisible() ? View.VISIBLE : View.GONE); - binding.avatarSizeToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { - if (!isChecked) return; - if (checkedId == R.id.avatar_size_tiny) { - preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.TINY); - } else if (checkedId == R.id.avatar_size_small) { - preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.SMALL); - } else { - preferencesBuilder.setProfilePicSize(PostsLayoutPreferences.ProfilePicSize.REGULAR); - } - }); - } - - private void initNamesToggle() { - binding.showNamesToggle.setChecked(preferencesBuilder.isNameVisible()); - binding.showNamesToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setNameVisible(isChecked)); - } - - private void initCornersToggle() { - binding.cornersToggle.check(getSelectedCornersId()); - binding.cornersToggle.addOnButtonCheckedListener((group, checkedId, isChecked) -> { - if (!isChecked) return; - if (checkedId == R.id.corners_round) { - preferencesBuilder.setHasRoundedCorners(true); - return; - } - preferencesBuilder.setHasRoundedCorners(false); - }); - } - - private void initGapToggle() { - binding.showGapToggle.setChecked(preferencesBuilder.getHasGap()); - binding.showGapToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setHasGap(isChecked)); - } - - private int getSelectedLayoutId() { - switch (preferencesBuilder.getType()) { - case STAGGERED_GRID: - return R.id.layout_staggered; - case LINEAR: - return R.id.layout_linear; - default: - case GRID: - return R.id.layout_grid; - } - } - - private int getSelectedColCountId() { - switch (preferencesBuilder.getColCount()) { - case 2: - return R.id.col_count_two; - case 3: - default: - return R.id.col_count_three; - } - } - - private int getSelectedCornersId() { - if (preferencesBuilder.getHasRoundedCorners()) { - return R.id.corners_round; - } - return R.id.corners_square; - } - - private int getSelectedAvatarSizeId() { - switch (preferencesBuilder.getProfilePicSize()) { - case TINY: - return R.id.avatar_size_tiny; - case SMALL: - return R.id.avatar_size_small; - case REGULAR: - default: - return R.id.avatar_size_regular; - } - } - - public interface OnApplyListener { - void onApply(final PostsLayoutPreferences preferences); - } -} diff --git a/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.kt b/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.kt new file mode 100644 index 00000000..5002df37 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.kt @@ -0,0 +1,180 @@ +package awais.instagrabber.dialogs + +import android.app.Dialog +import android.content.DialogInterface +import android.os.Bundle +import android.view.View +import android.widget.CompoundButton +import androidx.fragment.app.DialogFragment +import awais.instagrabber.R +import awais.instagrabber.databinding.DialogPostLayoutPreferencesBinding +import awais.instagrabber.models.PostsLayoutPreferences +import awais.instagrabber.models.PostsLayoutPreferences.PostsLayoutType +import awais.instagrabber.models.PostsLayoutPreferences.ProfilePicSize +import awais.instagrabber.utils.Utils +import com.google.android.material.button.MaterialButtonToggleGroup +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class PostsLayoutPreferencesDialogFragment( + private val layoutPreferenceKey: String, + private val onApplyListener: OnApplyListener +) : DialogFragment() { + + private lateinit var binding: DialogPostLayoutPreferencesBinding + + private val preferencesBuilder: PostsLayoutPreferences.Builder + + init { + val preferences = PostsLayoutPreferences.fromJson(Utils.settingsHelper.getString(layoutPreferenceKey)) + preferencesBuilder = PostsLayoutPreferences.builder().mergeFrom(preferences) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + binding = DialogPostLayoutPreferencesBinding.inflate(layoutInflater) + init() + return MaterialAlertDialogBuilder(requireContext()) + .setView(binding.getRoot()) + .setPositiveButton(R.string.apply) { _: DialogInterface?, _: Int -> + val preferences = preferencesBuilder.build() + val json = preferences.json + Utils.settingsHelper.putString(layoutPreferenceKey, json) + onApplyListener.onApply(preferences) + } + .create() + } + + private fun init() { + initLayoutToggle() + if (preferencesBuilder.type != PostsLayoutType.LINEAR) { + initStaggeredOrGridOptions() + } + } + + private fun initStaggeredOrGridOptions() { + initColCountToggle() + initNamesToggle() + initAvatarsToggle() + initCornersToggle() + initGapToggle() + } + + private fun initLayoutToggle() { + val selectedLayoutId = selectedLayoutId + binding.layoutToggle.check(selectedLayoutId) + if (selectedLayoutId == R.id.layout_linear) { + binding.staggeredOrGridOptions.visibility = View.GONE + } + binding.layoutToggle.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean -> + if (!isChecked) return@addOnButtonCheckedListener + when (checkedId) { + R.id.layout_linear -> { + preferencesBuilder.type = PostsLayoutType.LINEAR + preferencesBuilder.colCount = 1 + binding.staggeredOrGridOptions.visibility = View.GONE + } + R.id.layout_staggered -> { + preferencesBuilder.type = PostsLayoutType.STAGGERED_GRID + if (preferencesBuilder.colCount == 1) { + preferencesBuilder.colCount = 2 + } + binding.staggeredOrGridOptions.visibility = View.VISIBLE + initStaggeredOrGridOptions() + } + else -> { + preferencesBuilder.type = PostsLayoutType.GRID + if (preferencesBuilder.colCount == 1) { + preferencesBuilder.colCount = 2 + } + binding.staggeredOrGridOptions.visibility = View.VISIBLE + initStaggeredOrGridOptions() + } + } + } + } + + private fun initColCountToggle() { + binding.colCountToggle.check(selectedColCountId) + binding.colCountToggle.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean -> + if (!isChecked) return@addOnButtonCheckedListener + preferencesBuilder.colCount = (if (checkedId == R.id.col_count_two) 2 else 3) + } + } + + private fun initAvatarsToggle() { + binding.showAvatarToggle.isChecked = preferencesBuilder.isAvatarVisible + binding.avatarSizeToggle.check(selectedAvatarSizeId) + binding.showAvatarToggle.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + preferencesBuilder.isAvatarVisible = isChecked + binding.labelAvatarSize.visibility = if (isChecked) View.VISIBLE else View.GONE + binding.avatarSizeToggle.visibility = if (isChecked) View.VISIBLE else View.GONE + } + binding.labelAvatarSize.visibility = if (preferencesBuilder.isAvatarVisible) View.VISIBLE else View.GONE + binding.avatarSizeToggle.visibility = if (preferencesBuilder.isAvatarVisible) View.VISIBLE else View.GONE + binding.avatarSizeToggle.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean -> + if (!isChecked) return@addOnButtonCheckedListener + preferencesBuilder.profilePicSize = when (checkedId) { + R.id.avatar_size_tiny -> ProfilePicSize.TINY + R.id.avatar_size_small -> ProfilePicSize.SMALL + else -> ProfilePicSize.REGULAR + } + } + } + + private fun initNamesToggle() { + binding.showNamesToggle.isChecked = preferencesBuilder.isNameVisible + binding.showNamesToggle.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + preferencesBuilder.isNameVisible = isChecked + } + } + + private fun initCornersToggle() { + binding.cornersToggle.check(selectedCornersId) + binding.cornersToggle.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean -> + if (!isChecked) return@addOnButtonCheckedListener + if (checkedId == R.id.corners_round) { + preferencesBuilder.hasRoundedCorners = true + return@addOnButtonCheckedListener + } + preferencesBuilder.hasRoundedCorners = false + } + } + + private fun initGapToggle() { + binding.showGapToggle.isChecked = preferencesBuilder.hasGap + binding.showGapToggle.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> + preferencesBuilder.hasGap = isChecked + } + } + + private val selectedLayoutId: Int + get() = when (preferencesBuilder.type) { + PostsLayoutType.STAGGERED_GRID -> R.id.layout_staggered + PostsLayoutType.LINEAR -> R.id.layout_linear + PostsLayoutType.GRID -> R.id.layout_grid + else -> R.id.layout_grid + } + + private val selectedColCountId: Int + get() = when (preferencesBuilder.colCount) { + 2 -> R.id.col_count_two + 3 -> R.id.col_count_three + else -> R.id.col_count_three + } + + private val selectedCornersId: Int + get() = if (preferencesBuilder.hasRoundedCorners) { + R.id.corners_round + } else R.id.corners_square + + private val selectedAvatarSizeId: Int + get() = when (preferencesBuilder.profilePicSize) { + ProfilePicSize.TINY -> R.id.avatar_size_tiny + ProfilePicSize.SMALL -> R.id.avatar_size_small + ProfilePicSize.REGULAR -> R.id.avatar_size_regular + else -> R.id.avatar_size_regular + } + + fun interface OnApplyListener { + fun onApply(preferences: PostsLayoutPreferences) + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index e5c165a6..4df1675f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -34,13 +34,10 @@ import awais.instagrabber.customviews.RamboTextViewV2 import awais.instagrabber.customviews.RamboTextViewV2.* import awais.instagrabber.databinding.FragmentProfileBinding import awais.instagrabber.db.repositories.FavoriteRepository -import awais.instagrabber.dialogs.ConfirmDialogFragment +import awais.instagrabber.dialogs.* 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.UserSearchMode import awais.instagrabber.managers.DirectMessagesManager import awais.instagrabber.models.Resource @@ -1002,10 +999,10 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } private fun showPostsLayoutPreferences() { - val fragment = PostsLayoutPreferencesDialogFragment(Constants.PREF_PROFILE_POSTS_LAYOUT) { preferences -> - layoutPreferences = preferences + val fragment = PostsLayoutPreferencesDialogFragment(Constants.PREF_PROFILE_POSTS_LAYOUT) { + layoutPreferences = it Handler(Looper.getMainLooper()).postDelayed( - { binding.postsRecyclerView.layoutPreferences = preferences }, + { binding.postsRecyclerView.layoutPreferences = it }, 200 ) } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/notification/Notification.kt b/app/src/main/java/awais/instagrabber/repositories/responses/notification/Notification.kt index 0cd35777..f6ad44e2 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/notification/Notification.kt +++ b/app/src/main/java/awais/instagrabber/repositories/responses/notification/Notification.kt @@ -5,7 +5,7 @@ import awais.instagrabber.models.enums.NotificationType.Companion.valueOfType class Notification(val args: NotificationArgs, private val storyType: Int, - val pk: String) { + val pk: String?) { val type: NotificationType? get() = valueOfType(storyType) } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 78f30eef..a2a4e19b 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -195,10 +195,15 @@ public final class Utils { if (context == null || TextUtils.isEmpty(url)) { return; } - final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - i.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - i.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); try { + String url1 = url; + // add http:// if string doesn't have http:// or https:// + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url1 = "http://" + url; + } + final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url1)); + i.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + i.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true); context.startActivity(i); } catch (ActivityNotFoundException e) { Log.e(TAG, "openURL: No activity found to handle URLs", e); diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java deleted file mode 100644 index 03e0420f..00000000 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.java +++ /dev/null @@ -1,113 +0,0 @@ -package awais.instagrabber.viewmodels; - -import android.app.Application; -import android.content.Intent; -import android.content.UriPermission; -import android.net.Uri; -import android.os.Parcelable; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.documentfile.provider.DocumentFile; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import awais.instagrabber.R; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.DownloadUtils; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; - -import static awais.instagrabber.fragments.settings.PreferenceKeys.FOLDER_PATH; - -public class DirectorySelectActivityViewModel extends AndroidViewModel { - private static final String TAG = DirectorySelectActivityViewModel.class.getSimpleName(); - - private final MutableLiveData message = new MutableLiveData<>(); - private final MutableLiveData prevUri = new MutableLiveData<>(); - private final MutableLiveData loading = new MutableLiveData<>(false); - private final MutableLiveData dirSuccess = new MutableLiveData<>(false); - - public DirectorySelectActivityViewModel(final Application application) { - super(application); - } - - public LiveData getMessage() { - return message; - } - - public LiveData getPrevUri() { - return prevUri; - } - - public LiveData isLoading() { - return loading; - } - - public LiveData getDirSuccess() { - return dirSuccess; - } - - public void setInitialUri(final Intent intent) { - if (intent == null) { - setMessage(null); - return; - } - final Parcelable initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI); - if (!(initialUriParcelable instanceof Uri)) { - setMessage(null); - return; - } - setMessage((Uri) initialUriParcelable); - } - - private void setMessage(@Nullable final Uri initialUri) { - if (initialUri == null) { - final String prevVersionFolderPath = Utils.settingsHelper.getString(FOLDER_PATH); - if (TextUtils.isEmpty(prevVersionFolderPath)) { - // default message - message.postValue(getApplication().getString(R.string.dir_select_default_message)); - prevUri.postValue(null); - return; - } - message.postValue(getApplication().getString(R.string.dir_select_reselect_message)); - prevUri.postValue(prevVersionFolderPath); - return; - } - final List existingPermissions = getApplication().getContentResolver().getPersistedUriPermissions(); - final boolean anyMatch = existingPermissions.stream().anyMatch(uriPermission -> uriPermission.getUri().equals(initialUri)); - final DocumentFile documentFile = DocumentFile.fromSingleUri(getApplication(), initialUri); - String path; - try { - path = URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString()); - } catch (UnsupportedEncodingException e) { - path = initialUri.toString(); - } - if (!anyMatch) { - message.postValue(getApplication().getString(R.string.dir_select_permission_revoked_message)); - prevUri.postValue(path); - return; - } - if (documentFile == null || !documentFile.exists() || documentFile.lastModified() == 0) { - message.postValue(getApplication().getString(R.string.dir_select_folder_not_exist)); - prevUri.postValue(path); - } - } - - public void setupSelectedDir(@NonNull final Intent data) throws DownloadUtils.ReselectDocumentTreeException { - loading.postValue(true); - try { - Utils.setupSelectedDir(getApplication(), data); - message.postValue(getApplication().getString(R.string.dir_select_success_message)); - dirSuccess.postValue(true); - } finally { - loading.postValue(false); - } - } -} diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.kt new file mode 100644 index 00000000..732c2d9e --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectorySelectActivityViewModel.kt @@ -0,0 +1,89 @@ +package awais.instagrabber.viewmodels + +import android.app.Application +import android.content.Intent +import android.content.UriPermission +import android.net.Uri +import android.os.Parcelable +import androidx.documentfile.provider.DocumentFile +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import awais.instagrabber.R +import awais.instagrabber.fragments.settings.PreferenceKeys +import awais.instagrabber.utils.Constants +import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException +import awais.instagrabber.utils.TextUtils.isEmpty +import awais.instagrabber.utils.Utils +import java.io.UnsupportedEncodingException +import java.net.URLDecoder +import java.nio.charset.StandardCharsets + +class DirectorySelectActivityViewModel(application: Application) : AndroidViewModel(application) { + private val _message = MutableLiveData() + private val _prevUri = MutableLiveData() + private val _loading = MutableLiveData(false) + private val _dirSuccess = MutableLiveData(false) + + val message: LiveData = _message + val prevUri: LiveData = _prevUri + val loading: LiveData = _loading + val dirSuccess: LiveData = _dirSuccess + + fun setInitialUri(intent: Intent?) { + if (intent == null) { + setMessage(null) + return + } + val initialUriParcelable = intent.getParcelableExtra(Constants.EXTRA_INITIAL_URI) + if (initialUriParcelable !is Uri) { + setMessage(null) + return + } + setMessage(initialUriParcelable as Uri?) + } + + private fun setMessage(initialUri: Uri?) { + if (initialUri == null) { + val prevVersionFolderPath = Utils.settingsHelper.getString(PreferenceKeys.FOLDER_PATH) + if (isEmpty(prevVersionFolderPath)) { + // default message + _message.postValue(getApplication().getString(R.string.dir_select_default_message)) + _prevUri.postValue(null) + return + } + _message.postValue(getApplication().getString(R.string.dir_select_reselect_message)) + _prevUri.postValue(prevVersionFolderPath) + return + } + val existingPermissions = getApplication().contentResolver.persistedUriPermissions + val anyMatch = existingPermissions.stream().anyMatch { uriPermission: UriPermission -> uriPermission.uri == initialUri } + val documentFile = DocumentFile.fromSingleUri(getApplication(), initialUri) + val path: String = try { + URLDecoder.decode(initialUri.toString(), StandardCharsets.UTF_8.toString()) + } catch (e: UnsupportedEncodingException) { + initialUri.toString() + } + if (!anyMatch) { + _message.postValue(getApplication().getString(R.string.dir_select_permission_revoked_message)) + _prevUri.postValue(path) + return + } + if (documentFile == null || !documentFile.exists() || documentFile.lastModified() == 0L) { + _message.postValue(getApplication().getString(R.string.dir_select_folder_not_exist)) + _prevUri.postValue(path) + } + } + + @Throws(ReselectDocumentTreeException::class) + fun setupSelectedDir(data: Intent) { + _loading.postValue(true) + try { + Utils.setupSelectedDir(getApplication(), data) + _message.postValue(getApplication().getString(R.string.dir_select_success_message)) + _dirSuccess.postValue(true) + } finally { + _loading.postValue(false) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml index 42235145..cd3e72f4 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -542,4 +542,13 @@ android:id="@+id/action_to_profile" app:destination="@id/profile_non_top" /> + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/settings_nav_graph.xml b/app/src/main/res/navigation/settings_nav_graph.xml index 1a1fcfcb..df889305 100644 --- a/app/src/main/res/navigation/settings_nav_graph.xml +++ b/app/src/main/res/navigation/settings_nav_graph.xml @@ -33,18 +33,10 @@ android:id="@+id/action_settings_to_post" app:destination="@id/postPreferencesFragment" /> - -