-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate description fragment to Jetpack Compose
- Loading branch information
1 parent
99996d9
commit de6285b
Showing
9 changed files
with
541 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 30 additions & 134 deletions
164
app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,140 +1,36 @@ | ||
package org.schabi.newpipe.fragments.detail; | ||
|
||
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; | ||
import static org.schabi.newpipe.util.Localization.getAppLocale; | ||
|
||
import android.view.LayoutInflater; | ||
import android.view.View; | ||
import android.widget.LinearLayout; | ||
|
||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.annotation.StringRes; | ||
|
||
import org.schabi.newpipe.R; | ||
import org.schabi.newpipe.extractor.StreamingService; | ||
import org.schabi.newpipe.extractor.stream.Description; | ||
import org.schabi.newpipe.extractor.stream.StreamInfo; | ||
import org.schabi.newpipe.util.Localization; | ||
|
||
import java.util.List; | ||
|
||
import icepick.State; | ||
|
||
public class DescriptionFragment extends BaseDescriptionFragment { | ||
|
||
@State | ||
StreamInfo streamInfo; | ||
|
||
public DescriptionFragment(final StreamInfo streamInfo) { | ||
this.streamInfo = streamInfo; | ||
} | ||
|
||
public DescriptionFragment() { | ||
// keep empty constructor for IcePick when resuming fragment from memory | ||
} | ||
|
||
|
||
@Nullable | ||
@Override | ||
protected Description getDescription() { | ||
return streamInfo.getDescription(); | ||
} | ||
|
||
@NonNull | ||
@Override | ||
protected StreamingService getService() { | ||
return streamInfo.getService(); | ||
} | ||
|
||
@Override | ||
protected int getServiceId() { | ||
return streamInfo.getServiceId(); | ||
} | ||
|
||
@NonNull | ||
@Override | ||
protected String getStreamUrl() { | ||
return streamInfo.getUrl(); | ||
} | ||
|
||
@NonNull | ||
@Override | ||
public List<String> getTags() { | ||
return streamInfo.getTags(); | ||
} | ||
|
||
@Override | ||
protected void setupMetadata(final LayoutInflater inflater, | ||
final LinearLayout layout) { | ||
if (streamInfo != null && streamInfo.getUploadDate() != null) { | ||
binding.detailUploadDateView.setText(Localization | ||
.localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime())); | ||
} else { | ||
binding.detailUploadDateView.setVisibility(View.GONE); | ||
} | ||
|
||
if (streamInfo == null) { | ||
return; | ||
} | ||
|
||
addMetadataItem(inflater, layout, false, R.string.metadata_category, | ||
streamInfo.getCategory()); | ||
|
||
addMetadataItem(inflater, layout, false, R.string.metadata_licence, | ||
streamInfo.getLicence()); | ||
|
||
addPrivacyMetadataItem(inflater, layout); | ||
|
||
if (streamInfo.getAgeLimit() != NO_AGE_LIMIT) { | ||
addMetadataItem(inflater, layout, false, R.string.metadata_age_limit, | ||
String.valueOf(streamInfo.getAgeLimit())); | ||
} | ||
|
||
if (streamInfo.getLanguageInfo() != null) { | ||
addMetadataItem(inflater, layout, false, R.string.metadata_language, | ||
streamInfo.getLanguageInfo().getDisplayLanguage(getAppLocale(getContext()))); | ||
package org.schabi.newpipe.fragments.detail | ||
|
||
import android.os.Bundle | ||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.core.os.bundleOf | ||
import androidx.fragment.app.Fragment | ||
import androidx.fragment.compose.content | ||
import org.schabi.newpipe.extractor.stream.StreamInfo | ||
import org.schabi.newpipe.ktx.serializable | ||
import org.schabi.newpipe.ui.components.video.VideoDescriptionSection | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
import org.schabi.newpipe.util.KEY_INFO | ||
|
||
class DescriptionFragment : Fragment() { | ||
override fun onCreateView( | ||
inflater: LayoutInflater, | ||
container: ViewGroup?, | ||
savedInstanceState: Bundle? | ||
) = content { | ||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
VideoDescriptionSection(requireArguments().serializable(KEY_INFO)!!) | ||
} | ||
} | ||
|
||
addMetadataItem(inflater, layout, true, R.string.metadata_support, | ||
streamInfo.getSupportInfo()); | ||
addMetadataItem(inflater, layout, true, R.string.metadata_host, | ||
streamInfo.getHost()); | ||
|
||
addImagesMetadataItem(inflater, layout, R.string.metadata_thumbnails, | ||
streamInfo.getThumbnails()); | ||
addImagesMetadataItem(inflater, layout, R.string.metadata_uploader_avatars, | ||
streamInfo.getUploaderAvatars()); | ||
addImagesMetadataItem(inflater, layout, R.string.metadata_subchannel_avatars, | ||
streamInfo.getSubChannelAvatars()); | ||
} | ||
|
||
private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { | ||
if (streamInfo.getPrivacy() != null) { | ||
@StringRes final int contentRes; | ||
switch (streamInfo.getPrivacy()) { | ||
case PUBLIC: | ||
contentRes = R.string.metadata_privacy_public; | ||
break; | ||
case UNLISTED: | ||
contentRes = R.string.metadata_privacy_unlisted; | ||
break; | ||
case PRIVATE: | ||
contentRes = R.string.metadata_privacy_private; | ||
break; | ||
case INTERNAL: | ||
contentRes = R.string.metadata_privacy_internal; | ||
break; | ||
case OTHER: | ||
default: | ||
contentRes = 0; | ||
break; | ||
} | ||
|
||
if (contentRes != 0) { | ||
addMetadataItem(inflater, layout, false, R.string.metadata_privacy, | ||
getString(contentRes)); | ||
} | ||
companion object { | ||
@JvmStatic | ||
fun getInstance(streamInfo: StreamInfo) = DescriptionFragment().apply { | ||
arguments = bundleOf(KEY_INFO to streamInfo) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
app/src/main/java/org/schabi/newpipe/ui/components/common/DescriptionText.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.schabi.newpipe.ui.components.common | ||
|
||
import androidx.compose.material3.LocalTextStyle | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.text.AnnotatedString | ||
import androidx.compose.ui.text.SpanStyle | ||
import androidx.compose.ui.text.TextLayoutResult | ||
import androidx.compose.ui.text.TextLinkStyles | ||
import androidx.compose.ui.text.TextStyle | ||
import androidx.compose.ui.text.fromHtml | ||
import androidx.compose.ui.text.style.TextDecoration | ||
import androidx.compose.ui.text.style.TextOverflow | ||
import org.schabi.newpipe.extractor.stream.Description | ||
|
||
@Composable | ||
fun DescriptionText( | ||
description: Description, | ||
modifier: Modifier = Modifier, | ||
overflow: TextOverflow = TextOverflow.Clip, | ||
maxLines: Int = Int.MAX_VALUE, | ||
onTextLayout: (TextLayoutResult) -> Unit = {}, | ||
style: TextStyle = LocalTextStyle.current | ||
) { | ||
// TODO: Handle links and hashtags, Markdown. | ||
val parsedDescription = remember(description) { | ||
if (description.type == Description.HTML) { | ||
val styles = TextLinkStyles(SpanStyle(textDecoration = TextDecoration.Underline)) | ||
AnnotatedString.fromHtml(description.content, styles) | ||
} else { | ||
AnnotatedString(description.content) | ||
} | ||
} | ||
|
||
Text( | ||
modifier = modifier, | ||
text = parsedDescription, | ||
maxLines = maxLines, | ||
style = style, | ||
overflow = overflow, | ||
onTextLayout = onTextLayout | ||
) | ||
} |
106 changes: 106 additions & 0 deletions
106
app/src/main/java/org/schabi/newpipe/ui/components/metadata/ImageMetadataItem.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package org.schabi.newpipe.ui.components.metadata | ||
|
||
import android.content.Context | ||
import android.content.res.Configuration | ||
import androidx.annotation.StringRes | ||
import androidx.compose.foundation.lazy.LazyListScope | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Surface | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.platform.LocalContext | ||
import androidx.compose.ui.text.AnnotatedString | ||
import androidx.compose.ui.text.LinkAnnotation | ||
import androidx.compose.ui.text.SpanStyle | ||
import androidx.compose.ui.text.TextLinkStyles | ||
import androidx.compose.ui.text.buildAnnotatedString | ||
import androidx.compose.ui.text.font.FontWeight | ||
import androidx.compose.ui.text.style.TextDecoration | ||
import androidx.compose.ui.text.withLink | ||
import androidx.compose.ui.text.withStyle | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import org.schabi.newpipe.R | ||
import org.schabi.newpipe.extractor.Image | ||
import org.schabi.newpipe.extractor.Image.ResolutionLevel | ||
import org.schabi.newpipe.ui.theme.AppTheme | ||
import org.schabi.newpipe.util.image.ImageStrategy | ||
import org.schabi.newpipe.util.image.PreferredImageQuality | ||
|
||
@Composable | ||
fun ImageMetadataItem( | ||
@StringRes title: Int, | ||
images: List<Image>, | ||
preferredUrl: String? = ImageStrategy.choosePreferredImage(images) | ||
) { | ||
val context = LocalContext.current | ||
val imageLinks = remember { convertImagesToLinks(context, images, preferredUrl) } | ||
|
||
MetadataItem(title = title, value = imageLinks) | ||
} | ||
|
||
fun LazyListScope.imageMetadataItem(@StringRes title: Int, images: List<Image>) { | ||
ImageStrategy.choosePreferredImage(images)?.let { | ||
item { | ||
ImageMetadataItem(title, images, it) | ||
} | ||
} | ||
} | ||
|
||
private fun convertImagesToLinks( | ||
context: Context, | ||
images: List<Image>, | ||
preferredUrl: String? | ||
): AnnotatedString { | ||
fun imageSizeToText(size: Int): String { | ||
return if (size == Image.HEIGHT_UNKNOWN) context.getString(R.string.question_mark) | ||
else size.toString() | ||
} | ||
|
||
return buildAnnotatedString { | ||
for (image in images) { | ||
if (length != 0) { | ||
append(", ") | ||
} | ||
|
||
val linkStyle = TextLinkStyles(SpanStyle(textDecoration = TextDecoration.Underline)) | ||
withLink(LinkAnnotation.Url(image.url, linkStyle)) { | ||
val weight = if (image.url == preferredUrl) FontWeight.Bold else FontWeight.Normal | ||
|
||
withStyle(SpanStyle(fontWeight = weight)) { | ||
// if even the resolution level is unknown, ?x? will be shown | ||
if (image.height != Image.HEIGHT_UNKNOWN || image.width != Image.WIDTH_UNKNOWN || | ||
image.estimatedResolutionLevel == ResolutionLevel.UNKNOWN | ||
) { | ||
append("${imageSizeToText(image.width)}x${imageSizeToText(image.height)}") | ||
} else if (image.estimatedResolutionLevel == ResolutionLevel.LOW) { | ||
append(context.getString(R.string.image_quality_low)) | ||
} else if (image.estimatedResolutionLevel == ResolutionLevel.MEDIUM) { | ||
append(context.getString(R.string.image_quality_medium)) | ||
} else if (image.estimatedResolutionLevel == ResolutionLevel.HIGH) { | ||
append(context.getString(R.string.image_quality_high)) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) | ||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) | ||
@Composable | ||
private fun ImageMetadataItemPreview() { | ||
ImageStrategy.setPreferredImageQuality(PreferredImageQuality.MEDIUM) | ||
val images = listOf( | ||
Image("https://example.com/image_low.png", 16, 16, ResolutionLevel.LOW), | ||
Image("https://example.com/image_mid.png", 32, 32, ResolutionLevel.MEDIUM) | ||
) | ||
|
||
AppTheme { | ||
Surface(color = MaterialTheme.colorScheme.background) { | ||
ImageMetadataItem( | ||
title = R.string.metadata_uploader_avatars, | ||
images = images | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.