Skip to content

Commit 295b7f0

Browse files
committed
feat(app): add multi states view for topics list
Signed-off-by: Trumeet <[email protected]>
1 parent bbacfd7 commit 295b7f0

File tree

8 files changed

+212
-21
lines changed

8 files changed

+212
-21
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,16 @@ If your push (message) is not received or display an error, you can feedback [he
3131
Don't forget to attach your logs by sharing logs zip (Main → Menu → Share logs) and your steps.
3232

3333
# Licenses
34-
GPL v3.0
34+
## The license for this project
35+
GPL v3.0
36+
## Licenses for third-party resources
37+
Licenses of libraries are used in Android client is attached into the app, you can go to Main Menu Open Source Licenses to view them.
38+
39+
Some icons and pictures comes from [icons8.com](https://icons8.com/license), which are free to use for Open Source (Established projects should get the icons for free.)
40+
41+
Licenses of libraries are used in the server:
42+
43+
* Vertx - Eclipse Public License 2.0 and Apache License 2.0
44+
* JUnit - EPL 1.0
45+
* Mockito - MIT
46+
* Power Mockito - Apache 2.0

app/src/main/java/moe/yuuta/mipushtester/topic/TopicSubscriptionFragment.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@
99
import com.annimon.stream.Stream;
1010
import com.elvishew.xlog.Logger;
1111
import com.elvishew.xlog.XLog;
12-
import com.google.android.material.snackbar.Snackbar;
1312
import com.xiaomi.mipush.sdk.MiPushClient;
1413

1514
import java.util.List;
1615

1716
import androidx.annotation.NonNull;
1817
import androidx.annotation.Nullable;
18+
import androidx.core.content.ContextCompat;
19+
import androidx.databinding.DataBindingUtil;
1920
import androidx.fragment.app.Fragment;
2021
import androidx.recyclerview.widget.DiffUtil;
2122
import androidx.recyclerview.widget.RecyclerView;
2223
import moe.yuuta.mipushtester.R;
2324
import moe.yuuta.mipushtester.api.APIManager;
25+
import moe.yuuta.mipushtester.databinding.FragmentTopicSubscriptionBinding;
26+
import moe.yuuta.mipushtester.widgets.multi_state.State;
2427
import retrofit2.Call;
2528
import retrofit2.Callback;
2629
import retrofit2.Response;
@@ -30,6 +33,7 @@ public class TopicSubscriptionFragment extends Fragment {
3033

3134
private TopicListAdapter mAdapter;
3235
private Call<List<Topic>> mGetTopicListCall;
36+
private State mLoadingState;
3337

3438
@Override
3539
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -48,20 +52,34 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
4852
@Nullable
4953
@Override
5054
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
51-
final View view = inflater.inflate(R.layout.fragment_topic_subscription, container, false);
52-
RecyclerView recyclerView = view.findViewById(R.id.recycler_topic);
55+
final FragmentTopicSubscriptionBinding binding =
56+
DataBindingUtil.inflate(inflater, R.layout.fragment_topic_subscription, container, false);
57+
RecyclerView recyclerView = binding.recyclerTopic;
5358
recyclerView.setAdapter(mAdapter);
54-
return view;
59+
mLoadingState = new State();
60+
mLoadingState.onRetryListener = (v -> call());
61+
binding.setState(mLoadingState);
62+
return binding.getRoot();
5563
}
5664

5765
@Override
5866
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
5967
super.onViewCreated(view, savedInstanceState);
68+
call();
69+
}
70+
71+
private void call () {
72+
if (mGetTopicListCall != null) {
73+
mGetTopicListCall.cancel();
74+
mGetTopicListCall = null;
75+
}
6076
mGetTopicListCall = APIManager.getInstance().getAvailableTopics();
77+
mLoadingState.showProgress();
6178
mGetTopicListCall.enqueue(new Callback<List<Topic>>() {
6279
@Override
6380
public void onResponse(@NonNull Call<List<Topic>> call, @NonNull Response<List<Topic>> response) {
6481
if (call.isCanceled()) return;
82+
mLoadingState.hideProgress();
6583
if (!response.isSuccessful()) {
6684
onFailure(call, new Exception("Unsuccessful code " + response.code()));
6785
return;
@@ -73,12 +91,23 @@ public void onResponse(@NonNull Call<List<Topic>> call, @NonNull Response<List<T
7391
public void onFailure(@NonNull Call<List<Topic>> call, @NonNull Throwable t) {
7492
logger.e("Cannot retain topics", t);
7593
if (call.isCanceled()) return;
76-
Snackbar.make(getView(), R.string.error_load_topics, Snackbar.LENGTH_INDEFINITE).show();
94+
mLoadingState.hideProgress();
95+
mLoadingState.icon.set(ContextCompat.getDrawable(requireContext(), R.mipmap.illustration_fetal_error));
96+
mLoadingState.text.set(getString(R.string.error_load_topics));
97+
mLoadingState.description.set(getString(R.string.error_description_global));
7798
}
7899
});
79100
}
80101

81102
private void displayTopicsToUI (List<Topic> originalList) {
103+
if (originalList == null || originalList.size() <= 0) {
104+
mLoadingState.icon.set(ContextCompat.getDrawable(requireContext(), R.mipmap.illustration_list_is_empty));
105+
mLoadingState.text.set(getString(R.string.topic_empty_title));
106+
mLoadingState.showRetry.set(false);
107+
mLoadingState.description.set(getString(R.string.topic_empty_description));
108+
return;
109+
}
110+
mLoadingState.hideAll();
82111
List<String> localSubscribedTopics = MiPushClient.getAllTopic(requireContext());
83112
List<Topic> list = Stream.of(originalList)
84113
.peek(topic -> topic.setSubscribed(localSubscribedTopics.contains(topic.getId())))
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package moe.yuuta.mipushtester.widgets.multi_state;
2+
3+
import android.graphics.drawable.Drawable;
4+
import android.view.View;
5+
6+
import androidx.databinding.ObservableBoolean;
7+
import androidx.databinding.ObservableField;
8+
9+
public class State {
10+
public final ObservableBoolean showProgress;
11+
public final ObservableField<Drawable> icon;
12+
public final ObservableField<CharSequence> text;
13+
public final ObservableField<CharSequence> description;
14+
public final ObservableBoolean showTitle;
15+
public final ObservableBoolean showIcon;
16+
public final ObservableBoolean showDescription;
17+
public View.OnClickListener onRetryListener;
18+
public final ObservableBoolean showRetry;
19+
public final ObservableField<CharSequence> contentDescription;
20+
21+
public State() {
22+
showProgress = new ObservableBoolean(false);
23+
icon = new ObservableField<>();
24+
text = new ObservableField<>();
25+
description = new ObservableField<>();
26+
showRetry = new ObservableBoolean(false);
27+
showTitle = new ObservableBoolean(true);
28+
showDescription = new ObservableBoolean(true);
29+
contentDescription = new ObservableField<>();
30+
showIcon = new ObservableBoolean(true);
31+
}
32+
33+
public void showProgress () {
34+
this.showProgress.set(true);
35+
this.showTitle.set(false);
36+
this.showDescription.set(false);
37+
this.showRetry.set(false);
38+
this.showIcon.set(false);
39+
}
40+
41+
public void hideProgress () {
42+
this.showProgress.set(false);
43+
this.showTitle.set(true);
44+
this.showDescription.set(true);
45+
this.showRetry.set(true);
46+
this.showIcon.set(true);
47+
}
48+
49+
public void hideAll () {
50+
this.showProgress.set(false);
51+
this.showTitle.set(false);
52+
this.showDescription.set(false);
53+
this.showRetry.set(false);
54+
this.showIcon.set(false);
55+
}
56+
}
Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<androidx.constraintlayout.widget.ConstraintLayout
3-
xmlns:android="http://schemas.android.com/apk/res/android"
4-
xmlns:app="http://schemas.android.com/apk/res-auto"
5-
android:layout_width="match_parent"
6-
android:layout_height="match_parent">
7-
<androidx.recyclerview.widget.RecyclerView
8-
android:id="@+id/recycler_topic"
9-
android:layout_width="0dp"
10-
android:layout_height="0dp"
11-
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
12-
app:layout_constraintTop_toTopOf="parent"
13-
app:layout_constraintStart_toStartOf="parent"
14-
app:layout_constraintEnd_toEndOf="parent"
15-
app:layout_constraintBottom_toBottomOf="parent"/>
16-
</androidx.constraintlayout.widget.ConstraintLayout>
2+
<layout xmlns:bind="http://schemas.android.com/tools">
3+
<data>
4+
<variable
5+
name="state"
6+
type="moe.yuuta.mipushtester.widgets.multi_state.State" />
7+
</data>
8+
<androidx.constraintlayout.widget.ConstraintLayout
9+
xmlns:android="http://schemas.android.com/apk/res/android"
10+
xmlns:app="http://schemas.android.com/apk/res-auto"
11+
android:layout_width="match_parent"
12+
android:layout_height="match_parent">
13+
<androidx.recyclerview.widget.RecyclerView
14+
android:id="@+id/recycler_topic"
15+
android:layout_width="0dp"
16+
android:layout_height="0dp"
17+
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
18+
app:layout_constraintTop_toTopOf="parent"
19+
app:layout_constraintStart_toStartOf="parent"
20+
app:layout_constraintEnd_toEndOf="parent"
21+
app:layout_constraintBottom_toBottomOf="parent"/>
22+
<include layout="@layout/layout_multi_state"
23+
android:layout_width="match_parent"
24+
android:layout_height="match_parent"
25+
app:layout_constraintTop_toTopOf="parent"
26+
app:layout_constraintBottom_toBottomOf="parent"
27+
app:layout_constraintStart_toStartOf="parent"
28+
app:layout_constraintEnd_toEndOf="parent"
29+
bind:state="@{state}" />
30+
</androidx.constraintlayout.widget.ConstraintLayout>
31+
</layout>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<layout>
3+
<data>
4+
<variable
5+
name="state"
6+
type="moe.yuuta.mipushtester.widgets.multi_state.State" />
7+
<import type="android.view.View" />
8+
</data>
9+
<androidx.constraintlayout.widget.ConstraintLayout
10+
xmlns:android="http://schemas.android.com/apk/res/android"
11+
xmlns:app="http://schemas.android.com/apk/res-auto"
12+
xmlns:tools="http://schemas.android.com/tools"
13+
android:layout_width="match_parent"
14+
android:layout_height="match_parent">
15+
16+
<ImageView
17+
android:id="@android:id/icon"
18+
android:layout_width="256dp"
19+
android:layout_height="256dp"
20+
android:contentDescription="@{state.contentDescription}"
21+
android:scaleType="centerCrop"
22+
android:src="@{state.icon}"
23+
android:visibility="@{state.showIcon ? View.VISIBLE : View.GONE}"
24+
android:layout_marginTop="32dp"
25+
app:layout_constraintEnd_toEndOf="parent"
26+
app:layout_constraintStart_toStartOf="parent"
27+
app:layout_constraintTop_toTopOf="parent"
28+
tools:src="@mipmap/illustration_fetal_error" />
29+
<TextView
30+
android:id="@android:id/title"
31+
android:layout_width="wrap_content"
32+
android:layout_height="wrap_content"
33+
android:textAppearance="@style/TextAppearance.AppCompat.Title"
34+
android:layout_marginTop="16dp"
35+
android:text="@{state.text}"
36+
android:visibility="@{state.showTitle ? View.VISIBLE : View.GONE}"
37+
app:layout_constraintTop_toBottomOf="@android:id/icon"
38+
app:layout_constraintStart_toStartOf="parent"
39+
app:layout_constraintEnd_toEndOf="parent"
40+
tools:text="@string/app_name"/>
41+
<TextView
42+
android:id="@android:id/summary"
43+
android:layout_width="wrap_content"
44+
android:layout_height="wrap_content"
45+
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
46+
android:layout_marginTop="8dp"
47+
android:visibility="@{state.showDescription ? View.VISIBLE : View.GONE}"
48+
android:text="@{state.description}"
49+
app:layout_constraintTop_toBottomOf="@android:id/title"
50+
app:layout_constraintStart_toStartOf="parent"
51+
app:layout_constraintEnd_toEndOf="parent"
52+
tools:text="@string/app_name"/>
53+
<Button
54+
android:id="@android:id/button1"
55+
android:layout_width="wrap_content"
56+
android:layout_height="wrap_content"
57+
android:layout_marginTop="8dp"
58+
android:text="@string/retry"
59+
android:visibility="@{state.showRetry ? View.VISIBLE : View.GONE}"
60+
android:onClick="@{state.onRetryListener}"
61+
app:layout_constraintTop_toBottomOf="@android:id/summary"
62+
app:layout_constraintStart_toStartOf="parent"
63+
app:layout_constraintEnd_toEndOf="parent"
64+
style="@style/Widget.AppCompat.Button.Colored"/>
65+
<ProgressBar
66+
android:id="@android:id/progress"
67+
android:layout_width="wrap_content"
68+
android:layout_height="wrap_content"
69+
android:visibility="@{state.showProgress ? View.VISIBLE : View.GONE}"
70+
app:layout_constraintTop_toTopOf="parent"
71+
app:layout_constraintEnd_toEndOf="parent"
72+
app:layout_constraintBottom_toBottomOf="parent"
73+
app:layout_constraintStart_toStartOf="parent"/>
74+
</androidx.constraintlayout.widget.ConstraintLayout>
75+
</layout>
86.4 KB
Loading
106 KB
Loading

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,8 @@
105105
<string name="topic_action_title">Subscribe topics</string>
106106
<string name="topic_action_description">Subscribe some topics provided by the author, I\'ll send some messages to these topics</string>
107107
<string name="error_load_topics">Cannot retain topics</string>
108+
<string name="error_description_global">Check your internet access and try again later.</string>
109+
<string name="retry">Retry</string>
110+
<string name="topic_empty_title">Currently empty</string>
111+
<string name="topic_empty_description">We\'ll add more topics here, check it back later.</string>
108112
</resources>

0 commit comments

Comments
 (0)