Skip to content

Commit 8b79d0e

Browse files
toliuweijingProfpatsch
authored andcommitted
Migrate empty_state_view to Jetpack Compose
1 parent 295f719 commit 8b79d0e

22 files changed

Lines changed: 356 additions & 121 deletions

app/src/main/java/org/schabi/newpipe/fragments/EmptyFragment.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import android.view.ViewGroup;
77

88
import androidx.annotation.Nullable;
9+
import androidx.compose.ui.platform.ComposeView;
910

1011
import org.schabi.newpipe.BaseFragment;
1112
import org.schabi.newpipe.R;
13+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
1214

1315
public class EmptyFragment extends BaseFragment {
1416
private static final String SHOW_MESSAGE = "SHOW_MESSAGE";
@@ -26,8 +28,10 @@ public View onCreateView(final LayoutInflater inflater, @Nullable final ViewGrou
2628
final Bundle savedInstanceState) {
2729
final boolean showMessage = getArguments().getBoolean(SHOW_MESSAGE);
2830
final View view = inflater.inflate(R.layout.fragment_empty, container, false);
29-
view.findViewById(R.id.empty_state_view).setVisibility(
30-
showMessage ? View.VISIBLE : View.GONE);
31+
32+
final ComposeView composeView = view.findViewById(R.id.empty_state_view);
33+
EmptyStateUtil.setEmptyStateComposable(composeView);
34+
composeView.setVisibility(showMessage ? View.VISIBLE : View.GONE);
3135
return view;
3236
}
3337
}

app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import android.os.Bundle;
1111
import android.text.TextUtils;
1212
import android.util.Log;
13-
import android.util.TypedValue;
1413
import android.view.LayoutInflater;
1514
import android.view.Menu;
1615
import android.view.MenuInflater;
@@ -20,6 +19,7 @@
2019

2120
import androidx.annotation.NonNull;
2221
import androidx.annotation.Nullable;
22+
import androidx.compose.runtime.MutableState;
2323
import androidx.core.content.ContextCompat;
2424
import androidx.core.graphics.ColorUtils;
2525
import androidx.core.view.MenuProvider;
@@ -45,6 +45,9 @@
4545
import org.schabi.newpipe.ktx.AnimationType;
4646
import org.schabi.newpipe.local.feed.notifications.NotificationHelper;
4747
import org.schabi.newpipe.local.subscription.SubscriptionManager;
48+
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec;
49+
import org.schabi.newpipe.ui.emptystate.EmptyStateSpecBuilder;
50+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
4851
import org.schabi.newpipe.util.ChannelTabHelper;
4952
import org.schabi.newpipe.util.Constants;
5053
import org.schabi.newpipe.util.ExtractorHelper;
@@ -102,6 +105,8 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
102105
private SubscriptionEntity channelSubscription;
103106
private MenuProvider menuProvider;
104107

108+
private MutableState<EmptyStateSpec> emptyStateSpec;
109+
105110
public static ChannelFragment getInstance(final int serviceId, final String url,
106111
final String name) {
107112
final ChannelFragment instance = new ChannelFragment();
@@ -199,6 +204,10 @@ public View onCreateView(@NonNull final LayoutInflater inflater,
199204
protected void initViews(final View rootView, final Bundle savedInstanceState) {
200205
super.initViews(rootView, savedInstanceState);
201206

207+
emptyStateSpec = EmptyStateUtil.mutableStateOf(
208+
EmptyStateSpec.Companion.getContentNotSupported());
209+
EmptyStateUtil.setEmptyStateComposable(binding.emptyStateView, emptyStateSpec);
210+
202211
tabAdapter = new TabAdapter(getChildFragmentManager());
203212
binding.viewPager.setAdapter(tabAdapter);
204213
binding.tabLayout.setupWithViewPager(binding.viewPager);
@@ -645,8 +654,10 @@ private void showContentNotSupportedIfNeeded() {
645654
return;
646655
}
647656

648-
binding.errorContentNotSupported.setVisibility(View.VISIBLE);
649-
binding.channelKaomoji.setText("(︶︹︺)");
650-
binding.channelKaomoji.setTextSize(TypedValue.COMPLEX_UNIT_SP, 45f);
657+
emptyStateSpec.setValue(
658+
new EmptyStateSpecBuilder(emptyStateSpec.getValue())
659+
.descriptionVisibility(true)
660+
.build()
661+
);
651662
}
652663
}

app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelTabFragment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.schabi.newpipe.fragments.list.playlist.PlaylistControlViewHolder;
2727
import org.schabi.newpipe.player.playqueue.ChannelTabPlayQueue;
2828
import org.schabi.newpipe.player.playqueue.PlayQueue;
29+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
2930
import org.schabi.newpipe.util.ChannelTabHelper;
3031
import org.schabi.newpipe.util.ExtractorHelper;
3132
import org.schabi.newpipe.util.PlayButtonHelper;
@@ -79,6 +80,12 @@ public View onCreateView(@NonNull final LayoutInflater inflater,
7980
return inflater.inflate(R.layout.fragment_channel_tab, container, false);
8081
}
8182

83+
@Override
84+
public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) {
85+
super.onViewCreated(rootView, savedInstanceState);
86+
EmptyStateUtil.setEmptyStateComposable(rootView.findViewById(R.id.empty_state_view));
87+
}
88+
8289
@Override
8390
public void onDestroyView() {
8491
super.onDestroyView();

app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
import org.schabi.newpipe.ktx.ExceptionUtils;
6565
import org.schabi.newpipe.local.history.HistoryRecordManager;
6666
import org.schabi.newpipe.settings.NewPipeSettings;
67+
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec;
68+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
6769
import org.schabi.newpipe.util.Constants;
6870
import org.schabi.newpipe.util.DeviceUtils;
6971
import org.schabi.newpipe.util.ExtractorHelper;
@@ -344,6 +346,10 @@ public void onActivityResult(final int requestCode, final int resultCode, final
344346
protected void initViews(final View rootView, final Bundle savedInstanceState) {
345347
super.initViews(rootView, savedInstanceState);
346348

349+
EmptyStateUtil.setEmptyStateComposable(
350+
searchBinding.emptyStateView,
351+
EmptyStateSpec.Companion.getNoSearchResult());
352+
347353
searchBinding.suggestionsList.setAdapter(suggestionListAdapter);
348354
// animations are just strange and useless, since the suggestions keep changing too much
349355
searchBinding.suggestionsList.setItemAnimator(null);

app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
3939
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
4040
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
41+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
4142
import org.schabi.newpipe.util.NavigationHelper;
4243
import org.schabi.newpipe.util.OnClickGesture;
4344
import org.schabi.newpipe.util.debounce.DebounceSavable;
@@ -123,6 +124,7 @@ protected void initViews(final View rootView, final Bundle savedInstanceState) {
123124
super.initViews(rootView, savedInstanceState);
124125

125126
itemListAdapter.setUseItemHandle(true);
127+
EmptyStateUtil.setEmptyStateComposable(rootView.findViewById(R.id.empty_state_view));
126128
}
127129

128130
@Override

app/src/main/java/org/schabi/newpipe/local/feed/FeedFragment.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import org.schabi.newpipe.ktx.slideUp
7474
import org.schabi.newpipe.local.feed.item.StreamItem
7575
import org.schabi.newpipe.local.feed.service.FeedLoadService
7676
import org.schabi.newpipe.local.subscription.SubscriptionManager
77+
import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable
7778
import org.schabi.newpipe.util.DeviceUtils
7879
import org.schabi.newpipe.util.Localization
7980
import org.schabi.newpipe.util.NavigationHelper
@@ -132,6 +133,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
132133
override fun onViewCreated(rootView: View, savedInstanceState: Bundle?) {
133134
// super.onViewCreated() calls initListeners() which require the binding to be initialized
134135
_feedBinding = FragmentFeedBinding.bind(rootView)
136+
feedBinding.emptyStateView.setEmptyStateComposable()
135137
super.onViewCreated(rootView, savedInstanceState)
136138

137139
val factory = FeedViewModel.getFactory(requireContext(), groupId)

app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
5656
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
5757
import org.schabi.newpipe.streams.io.NoFileManagerSafeGuard
5858
import org.schabi.newpipe.streams.io.StoredFileHelper
59+
import org.schabi.newpipe.ui.emptystate.setEmptyStateComposable
5960
import org.schabi.newpipe.util.NavigationHelper
6061
import org.schabi.newpipe.util.OnClickGesture
6162
import org.schabi.newpipe.util.ServiceHelper
@@ -257,6 +258,8 @@ class SubscriptionFragment : BaseStateFragment<SubscriptionState>() {
257258
binding.itemsList.adapter = groupAdapter
258259
binding.itemsList.itemAnimator = null
259260

261+
binding.emptyStateView.setEmptyStateComposable()
262+
260263
viewModel = ViewModelProvider(this)[SubscriptionViewModel::class.java]
261264
viewModel.stateLiveData.observe(viewLifecycleOwner) { it?.let(this::handleResult) }
262265
viewModel.feedGroupsLiveData.observe(viewLifecycleOwner) {

app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import android.widget.TextView;
1212

1313
import androidx.annotation.NonNull;
14+
import androidx.compose.ui.platform.ComposeView;
1415
import androidx.fragment.app.DialogFragment;
1516
import androidx.recyclerview.widget.LinearLayoutManager;
1617
import androidx.recyclerview.widget.RecyclerView;
@@ -27,6 +28,7 @@
2728
import org.schabi.newpipe.error.UserAction;
2829
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
2930
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
31+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
3032
import org.schabi.newpipe.util.image.CoilHelper;
3133

3234
import java.util.List;
@@ -40,7 +42,7 @@ public class SelectPlaylistFragment extends DialogFragment {
4042
private OnSelectedListener onSelectedListener = null;
4143

4244
private ProgressBar progressBar;
43-
private TextView emptyView;
45+
private ComposeView emptyView;
4446
private RecyclerView recyclerView;
4547
private Disposable disposable = null;
4648

@@ -62,6 +64,7 @@ public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup
6264
recyclerView = v.findViewById(R.id.items_list);
6365
emptyView = v.findViewById(R.id.empty_state_view);
6466

67+
EmptyStateUtil.setEmptyStateText(emptyView, R.string.no_playlist_bookmarked_yet);
6568
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
6669
final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
6770
recyclerView.setAdapter(playlistAdapter);

app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceSearchFragment.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import androidx.recyclerview.widget.LinearLayoutManager;
1212

1313
import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding;
14+
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec;
15+
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
1416

1517
import java.util.List;
1618

@@ -39,6 +41,9 @@ public View onCreateView(
3941
binding = SettingsPreferencesearchFragmentBinding.inflate(inflater, container, false);
4042

4143
binding.searchResults.setLayoutManager(new LinearLayoutManager(getContext()));
44+
EmptyStateUtil.setEmptyStateComposable(
45+
binding.emptyStateView,
46+
EmptyStateSpec.Companion.getNoSearchMaxSizeResult());
4247

4348
adapter = new PreferenceSearchAdapter();
4449
adapter.setOnItemClickListener(this::onItemClicked);
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package org.schabi.newpipe.ui.emptystate
2+
3+
import android.graphics.Color
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.heightIn
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material3.LocalContentColor
11+
import androidx.compose.material3.LocalTextStyle
12+
import androidx.compose.material3.MaterialTheme
13+
import androidx.compose.material3.Text
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.CompositionLocalProvider
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.res.stringResource
19+
import androidx.compose.ui.text.TextStyle
20+
import androidx.compose.ui.text.font.FontFamily
21+
import androidx.compose.ui.tooling.preview.Preview
22+
import androidx.compose.ui.unit.dp
23+
import androidx.compose.ui.unit.sp
24+
import org.schabi.newpipe.R
25+
import org.schabi.newpipe.ui.theme.AppTheme
26+
import org.schabi.newpipe.ui.theme.errorHint
27+
28+
@Composable
29+
fun EmptyStateComposable(
30+
spec: EmptyStateSpec,
31+
modifier: Modifier = Modifier,
32+
) = EmptyStateComposable(
33+
modifier = spec.modifier(modifier),
34+
emojiModifier = spec.emojiModifier(),
35+
emojiText = spec.emojiText(),
36+
emojiTextStyle = spec.emojiTextStyle(),
37+
descriptionModifier = spec.descriptionModifier(),
38+
descriptionText = spec.descriptionText(),
39+
descriptionTextStyle = spec.descriptionTextStyle(),
40+
descriptionTextVisibility = spec.descriptionVisibility(),
41+
)
42+
43+
@Composable
44+
private fun EmptyStateComposable(
45+
modifier: Modifier,
46+
emojiModifier: Modifier,
47+
emojiText: String,
48+
emojiTextStyle: TextStyle,
49+
descriptionModifier: Modifier,
50+
descriptionText: String,
51+
descriptionTextStyle: TextStyle,
52+
descriptionTextVisibility: Boolean,
53+
) {
54+
CompositionLocalProvider(
55+
LocalContentColor provides MaterialTheme.colorScheme.errorHint
56+
) {
57+
Column(
58+
modifier = modifier,
59+
horizontalAlignment = Alignment.CenterHorizontally,
60+
verticalArrangement = Arrangement.Center
61+
) {
62+
Text(
63+
modifier = emojiModifier,
64+
text = emojiText,
65+
style = emojiTextStyle,
66+
)
67+
68+
if (descriptionTextVisibility) {
69+
Text(
70+
modifier = descriptionModifier,
71+
text = descriptionText,
72+
style = descriptionTextStyle,
73+
)
74+
}
75+
}
76+
}
77+
}
78+
79+
@Preview(showBackground = true, backgroundColor = Color.WHITE.toLong())
80+
@Composable
81+
fun EmptyStateComposableGenericErrorPreview() {
82+
AppTheme {
83+
EmptyStateComposable(EmptyStateSpec.GenericError)
84+
}
85+
}
86+
87+
@Preview(showBackground = true, backgroundColor = Color.WHITE.toLong())
88+
@Composable
89+
fun EmptyStateComposableNoCommentPreview() {
90+
AppTheme {
91+
EmptyStateComposable(EmptyStateSpec.NoComment)
92+
}
93+
}
94+
95+
data class EmptyStateSpec(
96+
val modifier: (Modifier) -> Modifier,
97+
val emojiModifier: () -> Modifier,
98+
val emojiText: @Composable () -> String,
99+
val emojiTextStyle: @Composable () -> TextStyle,
100+
val descriptionText: @Composable () -> String,
101+
val descriptionModifier: () -> Modifier,
102+
val descriptionTextStyle: @Composable () -> TextStyle,
103+
val descriptionVisibility: () -> Boolean = { true },
104+
) {
105+
106+
companion object {
107+
108+
val GenericError =
109+
EmptyStateSpec(
110+
modifier = {
111+
it
112+
.fillMaxWidth()
113+
.heightIn(min = 128.dp)
114+
},
115+
emojiModifier = { Modifier },
116+
emojiText = { "¯\\_(ツ)_/¯" },
117+
emojiTextStyle = { MaterialTheme.typography.titleLarge },
118+
descriptionModifier = {
119+
Modifier
120+
.padding(top = 6.dp)
121+
.padding(horizontal = 16.dp)
122+
},
123+
descriptionText = { stringResource(id = R.string.empty_list_subtitle) },
124+
descriptionTextStyle = { MaterialTheme.typography.bodyMedium }
125+
)
126+
127+
val NoComment =
128+
EmptyStateSpec(
129+
modifier = { it.padding(top = 85.dp) },
130+
emojiModifier = { Modifier.padding(bottom = 10.dp) },
131+
emojiText = { "(╯°-°)╯" },
132+
emojiTextStyle = {
133+
LocalTextStyle.current.merge(
134+
fontFamily = FontFamily.Monospace,
135+
fontSize = 35.sp,
136+
)
137+
},
138+
descriptionModifier = { Modifier },
139+
descriptionText = { stringResource(id = R.string.no_comments) },
140+
descriptionTextStyle = {
141+
LocalTextStyle.current.merge(fontSize = 24.sp)
142+
}
143+
)
144+
145+
val NoSearchResult =
146+
NoComment.copy(
147+
modifier = { it },
148+
emojiText = { "╰(°●°╰)" },
149+
descriptionText = { stringResource(id = R.string.search_no_results) }
150+
)
151+
152+
val NoSearchMaxSizeResult =
153+
NoSearchResult.copy(
154+
modifier = { it.fillMaxSize() },
155+
)
156+
157+
val ContentNotSupported =
158+
NoComment.copy(
159+
modifier = { it.padding(top = 90.dp) },
160+
emojiText = { "(︶︹︺)" },
161+
emojiTextStyle = { LocalTextStyle.current.merge(fontSize = 45.sp) },
162+
descriptionModifier = { Modifier.padding(top = 20.dp) },
163+
descriptionText = { stringResource(id = R.string.content_not_supported) },
164+
descriptionTextStyle = { LocalTextStyle.current.merge(fontSize = 15.sp) },
165+
descriptionVisibility = { false },
166+
)
167+
}
168+
}

0 commit comments

Comments
 (0)