Skip to content

Commit

Permalink
Migrate related items fragment to Jetpack Compose
Browse files Browse the repository at this point in the history
  • Loading branch information
Isira-Seneviratne committed Aug 1, 2024
1 parent e3c1a3c commit aac96cc
Show file tree
Hide file tree
Showing 19 changed files with 648 additions and 282 deletions.
16 changes: 9 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
id "kotlin-parcelize"
id "checkstyle"
id "org.sonarqube" version "4.0.0.2929"
id "org.jetbrains.kotlin.plugin.compose" version "${kotlin_version}"
}

android {
Expand Down Expand Up @@ -104,10 +105,6 @@ android {
'META-INF/COPYRIGHT']
}
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
}

ext {
Expand Down Expand Up @@ -267,7 +264,7 @@ dependencies {
implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"

// Image loading
implementation 'io.coil-kt:coil:2.7.0'
implementation 'io.coil-kt:coil-compose:2.7.0'

// Markdown library for Android
implementation "io.noties.markwon:core:${markwonVersion}"
Expand All @@ -289,10 +286,15 @@ dependencies {
implementation "org.ocpsoft.prettytime:prettytime:5.0.8.Final"

// Jetpack Compose
implementation(platform('androidx.compose:compose-bom:2024.02.01'))
implementation 'androidx.compose.material3:material3'
implementation(platform('androidx.compose:compose-bom:2024.06.00'))
implementation 'androidx.compose.material3:material3:1.3.0-beta05'
implementation 'androidx.compose.material3.adaptive:adaptive:1.0.0-beta04'
implementation 'androidx.activity:activity-compose'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.ui:ui-text:1.7.0-beta06' // Needed for parsing HTML to AnnotatedString
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose'
implementation 'androidx.paging:paging-compose:3.3.1'
implementation 'com.github.nanihadesuka:LazyColumnScrollbar:2.2.0'

/** Debugging **/
// Memory leak detection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,176 +1,41 @@
package org.schabi.newpipe.fragments.list.videos;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;

import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.RelatedItemsHeaderBinding;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment;
import org.schabi.newpipe.info_list.ItemViewMode;
import org.schabi.newpipe.ktx.ViewUtils;

import java.io.Serializable;
import java.util.function.Supplier;

import io.reactivex.rxjava3.core.Single;

public class RelatedItemsFragment extends BaseListInfoFragment<InfoItem, RelatedItemsInfo>
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String INFO_KEY = "related_info_key";

private RelatedItemsInfo relatedItemsInfo;

/*//////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private RelatedItemsHeaderBinding headerBinding;
public static RelatedItemsFragment getInstance(final StreamInfo info) {
final RelatedItemsFragment instance = new RelatedItemsFragment();
instance.setInitialData(info);
return instance;
}
public RelatedItemsFragment() {
super(UserAction.REQUESTED_STREAM);
}
/*//////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_related_items, container, false);
}
@Override
public void onDestroyView() {
headerBinding = null;
super.onDestroyView();
}
@Override
protected Supplier<View> getListHeaderSupplier() {
if (relatedItemsInfo == null || relatedItemsInfo.getRelatedItems() == null) {
return null;
}
headerBinding = RelatedItemsHeaderBinding
.inflate(activity.getLayoutInflater(), itemsList, false);
final SharedPreferences pref = PreferenceManager
.getDefaultSharedPreferences(requireContext());
final boolean autoplay = pref.getBoolean(getString(R.string.auto_queue_key), false);
headerBinding.autoplaySwitch.setChecked(autoplay);
headerBinding.autoplaySwitch.setOnCheckedChangeListener((compoundButton, b) ->
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit()
.putBoolean(getString(R.string.auto_queue_key), b).apply());
return headerBinding::getRoot;
}
@Override
protected Single<ListExtractor.InfoItemsPage<InfoItem>> loadMoreItemsLogic() {
return Single.fromCallable(ListExtractor.InfoItemsPage::emptyPage);
}
/*//////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single<RelatedItemsInfo> loadResult(final boolean forceLoad) {
return Single.fromCallable(() -> relatedItemsInfo);
}
@Override
public void showLoading() {
super.showLoading();
if (headerBinding != null) {
headerBinding.getRoot().setVisibility(View.INVISIBLE);
}
}
@Override
public void handleResult(@NonNull final RelatedItemsInfo result) {
super.handleResult(result);
if (headerBinding != null) {
headerBinding.getRoot().setVisibility(View.VISIBLE);
}
ViewUtils.slideUp(requireView(), 120, 96, 0.06f);
}
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle(final String title) {
// Nothing to do - override parent
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
// Nothing to do - override parent
}
private void setInitialData(final StreamInfo info) {
super.setInitialData(info.getServiceId(), info.getUrl(), info.getName());
if (this.relatedItemsInfo == null) {
this.relatedItemsInfo = new RelatedItemsInfo(info);
}
}
@Override
public void onSaveInstanceState(@NonNull final Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable(INFO_KEY, relatedItemsInfo);
}
@Override
protected void onRestoreInstanceState(@NonNull final Bundle savedState) {
super.onRestoreInstanceState(savedState);
final Serializable serializable = savedState.getSerializable(INFO_KEY);
if (serializable instanceof RelatedItemsInfo) {
this.relatedItemsInfo = (RelatedItemsInfo) serializable;
}
}
@Override
public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
final String key) {
if (headerBinding != null && getString(R.string.auto_queue_key).equals(key)) {
headerBinding.autoplaySwitch.setChecked(sharedPreferences.getBoolean(key, false));
package org.schabi.newpipe.fragments.list.videos

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.ui.platform.ComposeView
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import org.schabi.newpipe.extractor.stream.StreamInfo
import org.schabi.newpipe.ktx.serializable
import org.schabi.newpipe.ui.components.video.RelatedItems
import org.schabi.newpipe.ui.theme.AppTheme
import org.schabi.newpipe.util.KEY_INFO

class RelatedItemsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
AppTheme {
Surface(color = MaterialTheme.colorScheme.background) {
RelatedItems(requireArguments().serializable<StreamInfo>(KEY_INFO)!!)
}
}
}
}
}

@Override
protected ItemViewMode getItemViewMode() {
ItemViewMode mode = super.getItemViewMode();
// Only list mode is supported. Either List or card will be used.
if (mode != ItemViewMode.LIST && mode != ItemViewMode.CARD) {
mode = ItemViewMode.LIST;
companion object {
@JvmStatic
fun getInstance(info: StreamInfo) = RelatedItemsFragment().apply {
arguments = bundleOf(KEY_INFO to info)
}
return mode;
}
}
5 changes: 5 additions & 0 deletions app/src/main/java/org/schabi/newpipe/ktx/Bundle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ package org.schabi.newpipe.ktx
import android.os.Bundle
import android.os.Parcelable
import androidx.core.os.BundleCompat
import java.io.Serializable

inline fun <reified T : Parcelable> Bundle.parcelableArrayList(key: String?): ArrayList<T>? {
return BundleCompat.getParcelableArrayList(this, key, T::class.java)
}

inline fun <reified T : Serializable> Bundle.serializable(key: String?): T? {
return BundleCompat.getSerializable(this, key, T::class.java)
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
// Menu
// /////////////////////////////////////////////////////////////////////////

@Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)

Expand All @@ -212,6 +213,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
inflater.inflate(R.menu.menu_feed_fragment, menu)
}

@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.menu_item_feed_help) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
Expand Down Expand Up @@ -253,7 +255,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
viewModel.getShowFutureItemsFromPreferences()
)

AlertDialog.Builder(context!!)
AlertDialog.Builder(requireContext())
.setTitle(R.string.feed_hide_streams_title)
.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
checkedDialogItems[which] = isChecked
Expand All @@ -267,6 +269,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
.show()
}

@Deprecated("Deprecated in Java")
override fun onDestroyOptionsMenu() {
super.onDestroyOptionsMenu()
activity?.supportActionBar?.subtitle = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
// Menu
// ////////////////////////////////////////////////////////////////////////

@Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return object : Dialog(requireActivity(), theme) {
@Deprecated("Deprecated in Java")
override fun onBackPressed() {
if (!this@FeedGroupDialog.onBackPressed()) {
super.onBackPressed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ class NotificationModeConfigFragment : Fragment() {
super.onDestroy()
}

@Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_notifications_channels, menu)
}

@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_toggle_all -> {
Expand Down
Loading

0 comments on commit aac96cc

Please sign in to comment.