Skip to content

Commit b0fab8a

Browse files
mayank8318iamareebjamal
authored andcommitted
feat: Add FAQs section to the app. (fossasia#2314)
1 parent 38fbf7c commit b0fab8a

File tree

14 files changed

+439
-2
lines changed

14 files changed

+439
-2
lines changed

android/app/src/main/java/org/fossasia/openevent/common/api/APIClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.fossasia.openevent.BuildConfig;
99
import org.fossasia.openevent.OpenEventApp;
1010
import org.fossasia.openevent.data.Event;
11+
import org.fossasia.openevent.data.FAQ;
1112
import org.fossasia.openevent.data.Microlocation;
1213
import org.fossasia.openevent.data.Session;
1314
import org.fossasia.openevent.data.Speaker;
@@ -87,7 +88,7 @@ public static OpenEventAPI getOpenEventAPI() {
8788
.build();
8889

8990
ObjectMapper objectMapper = getObjectMapper();
90-
Class[] classes = {Event.class, Track.class, Speaker.class, Sponsor.class, Session.class, Microlocation.class, User.class};
91+
Class[] classes = {Event.class, Track.class, Speaker.class, Sponsor.class, Session.class, Microlocation.class, User.class, FAQ.class};
9192

9293
openEventAPI = new Retrofit.Builder()
9394
.client(okHttpClient)

android/app/src/main/java/org/fossasia/openevent/common/api/OpenEventAPI.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.fossasia.openevent.common.api;
22

33
import org.fossasia.openevent.data.Event;
4+
import org.fossasia.openevent.data.FAQ;
45
import org.fossasia.openevent.data.Microlocation;
56
import org.fossasia.openevent.data.Session;
67
import org.fossasia.openevent.data.SessionType;
@@ -61,4 +62,7 @@ public interface OpenEventAPI {
6162
@GET("session-types")
6263
Call<List<SessionType>> getSessionTypes();
6364

65+
@GET("faqs")
66+
Observable<List<FAQ>> getFAQs();
67+
6468
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package org.fossasia.openevent.core.faqs;
2+
3+
import android.arch.lifecycle.ViewModelProviders;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
import android.support.annotation.NonNull;
7+
import android.support.annotation.Nullable;
8+
import android.support.design.widget.Snackbar;
9+
import android.support.v4.widget.SwipeRefreshLayout;
10+
import android.support.v7.widget.LinearLayoutManager;
11+
import android.support.v7.widget.RecyclerView;
12+
import android.view.LayoutInflater;
13+
import android.view.View;
14+
import android.view.ViewGroup;
15+
import android.widget.TextView;
16+
import android.widget.Toast;
17+
18+
import org.fossasia.openevent.R;
19+
import org.fossasia.openevent.common.network.NetworkUtils;
20+
import org.fossasia.openevent.common.ui.base.BaseFragment;
21+
import org.fossasia.openevent.common.utils.Utils;
22+
import org.fossasia.openevent.core.auth.AuthUtil;
23+
import org.fossasia.openevent.core.auth.LoginActivity;
24+
import org.fossasia.openevent.data.FAQ;
25+
26+
import java.lang.ref.WeakReference;
27+
import java.util.ArrayList;
28+
29+
import butterknife.BindView;
30+
import timber.log.Timber;
31+
32+
public class FAQFragment extends BaseFragment {
33+
34+
@BindView(R.id.rv_faqs)
35+
protected RecyclerView rvFaq;
36+
@BindView(R.id.tv_emptyFaqs)
37+
protected TextView tvEmptyFaqs;
38+
@BindView(R.id.faq_swiperefreshlayout)
39+
protected SwipeRefreshLayout swipeRefreshLayout;
40+
41+
private ArrayList<FAQ> faqArrayList;
42+
private FAQListAdapter faqListAdapter;
43+
private FAQViewModel faqViewModel;
44+
45+
@Nullable
46+
@Override
47+
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
48+
// setHasOptionsMenu(true); TODO : ADD SEARCH OPTION
49+
View view = super.onCreateView(inflater, container, savedInstanceState);
50+
Utils.registerIfUrlValid(swipeRefreshLayout, this, this::refresh);
51+
faqArrayList = new ArrayList<>();
52+
faqViewModel = ViewModelProviders.of(this).get(FAQViewModel.class);
53+
setUpRecyclerView();
54+
55+
if (AuthUtil.isUserLoggedIn()) {
56+
if (NetworkUtils.haveNetworkConnection(getContext())) {
57+
downloadFAQS();
58+
}
59+
loadFAQs();
60+
handleVisibility();
61+
} else {
62+
redirectToLogin();
63+
}
64+
65+
return view;
66+
}
67+
68+
private void redirectToLogin() {
69+
Toast.makeText(getContext(), R.string.login_to_see_faqs, Toast.LENGTH_SHORT).show();
70+
Intent intent = new Intent(getActivity(), LoginActivity.class);
71+
startActivity(intent);
72+
}
73+
74+
private void loadFAQs() {
75+
faqViewModel.getFaqData().observe(this, faqList -> {
76+
faqArrayList.clear();
77+
faqArrayList.addAll(faqList);
78+
faqListAdapter.notifyDataSetChanged();
79+
handleVisibility();
80+
});
81+
}
82+
83+
private void downloadFAQS() {
84+
faqViewModel.downloadFAQ().observe(this, this::onDownloadResponse);
85+
}
86+
87+
private void onDownloadResponse(boolean faqDownloadResult) {
88+
if (!faqDownloadResult) {
89+
Timber.d("FAQs download failed");
90+
if (getActivity() != null && swipeRefreshLayout != null)
91+
Snackbar.make(swipeRefreshLayout, getActivity().getString(R.string.refresh_failed), Snackbar.LENGTH_LONG).setAction(R.string.retry_download, view -> refresh()).show();
92+
}
93+
94+
if (swipeRefreshLayout != null)
95+
swipeRefreshLayout.setRefreshing(false);
96+
}
97+
98+
private void refresh() {
99+
NetworkUtils.checkConnection(new WeakReference<>(getContext()), new NetworkUtils.NetworkStateReceiverListener() {
100+
101+
@Override
102+
public void networkAvailable() {
103+
downloadFAQS();
104+
swipeRefreshLayout.setRefreshing(false);
105+
}
106+
107+
@Override
108+
public void networkUnavailable() {
109+
onDownloadResponse(false);
110+
}
111+
112+
});
113+
}
114+
115+
private void handleVisibility() {
116+
if (faqArrayList.isEmpty()) {
117+
tvEmptyFaqs.setVisibility(View.VISIBLE);
118+
rvFaq.setVisibility(View.GONE);
119+
} else {
120+
tvEmptyFaqs.setVisibility(View.GONE);
121+
rvFaq.setVisibility(View.VISIBLE);
122+
}
123+
}
124+
125+
@Override
126+
public void onResume() {
127+
super.onResume();
128+
if (!AuthUtil.isUserLoggedIn()) {
129+
tvEmptyFaqs.setText(R.string.login_to_view_faqs);
130+
}
131+
}
132+
133+
private void setUpRecyclerView() {
134+
rvFaq.setLayoutManager(new LinearLayoutManager(getContext()));
135+
faqListAdapter = new FAQListAdapter(faqArrayList);
136+
rvFaq.setAdapter(faqListAdapter);
137+
}
138+
139+
@Override
140+
public void onDestroyView() {
141+
super.onDestroyView();
142+
Utils.unregisterIfUrlValid(this);
143+
if (swipeRefreshLayout != null)
144+
swipeRefreshLayout.setOnRefreshListener(null);
145+
}
146+
147+
@Override
148+
protected int getLayoutResource() {
149+
return R.layout.fragment_faqs;
150+
}
151+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.fossasia.openevent.core.faqs;
2+
3+
import android.view.LayoutInflater;
4+
import android.view.View;
5+
import android.view.ViewGroup;
6+
7+
import org.fossasia.openevent.R;
8+
import org.fossasia.openevent.common.ui.base.BaseRVAdapter;
9+
import org.fossasia.openevent.data.FAQ;
10+
11+
import java.util.List;
12+
13+
public class FAQListAdapter extends BaseRVAdapter<FAQ, FAQViewHolder> {
14+
15+
public FAQListAdapter(List<FAQ> dataList) {
16+
super(dataList);
17+
}
18+
19+
@Override
20+
public FAQViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
21+
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
22+
View view = layoutInflater.inflate(R.layout.item_faq, parent, false);
23+
return new FAQViewHolder(view);
24+
}
25+
26+
@Override
27+
public void onBindViewHolder(FAQViewHolder holder, int position) {
28+
holder.bindFAQs(getDataList().get(position));
29+
}
30+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package org.fossasia.openevent.core.faqs;
2+
3+
import android.support.v7.widget.CardView;
4+
import android.support.v7.widget.RecyclerView;
5+
import android.view.View;
6+
import android.widget.TextView;
7+
8+
import org.fossasia.openevent.R;
9+
import org.fossasia.openevent.data.FAQ;
10+
11+
import butterknife.BindView;
12+
import butterknife.ButterKnife;
13+
14+
public class FAQViewHolder extends RecyclerView.ViewHolder {
15+
16+
@BindView(R.id.faq_question)
17+
protected TextView tvQuestion;
18+
@BindView(R.id.faq_answer)
19+
protected TextView tvAnswer;
20+
@BindView(R.id.parent_card_faq)
21+
protected CardView parentCardView;
22+
23+
public FAQViewHolder(View itemView) {
24+
super(itemView);
25+
ButterKnife.bind(this, itemView);
26+
}
27+
28+
public void bindFAQs(FAQ faq) {
29+
tvQuestion.setText(faq.getQuestion());
30+
tvAnswer.setText(faq.getAnswer());
31+
tvAnswer.setVisibility(View.GONE);
32+
33+
tvQuestion.setOnClickListener(view -> toggleAnswerVisibility());
34+
}
35+
36+
private void toggleAnswerVisibility() {
37+
if (tvAnswer.getVisibility() == View.GONE)
38+
tvAnswer.setVisibility(View.VISIBLE);
39+
else
40+
tvAnswer.setVisibility(View.GONE);
41+
}
42+
43+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.fossasia.openevent.core.faqs;
2+
3+
import android.arch.lifecycle.LiveData;
4+
import android.arch.lifecycle.MutableLiveData;
5+
import android.arch.lifecycle.Transformations;
6+
import android.arch.lifecycle.ViewModel;
7+
8+
import org.fossasia.openevent.common.api.APIClient;
9+
import org.fossasia.openevent.common.arch.LiveRealmData;
10+
import org.fossasia.openevent.data.FAQ;
11+
import org.fossasia.openevent.data.repository.RealmDataRepository;
12+
13+
import java.util.List;
14+
15+
import io.reactivex.android.schedulers.AndroidSchedulers;
16+
import io.reactivex.disposables.CompositeDisposable;
17+
import io.reactivex.schedulers.Schedulers;
18+
import timber.log.Timber;
19+
20+
public class FAQViewModel extends ViewModel {
21+
22+
private RealmDataRepository realmRepo;
23+
private LiveData<List<FAQ>> faqData;
24+
private MutableLiveData<Boolean> faqOnDownloadResponse;
25+
private final CompositeDisposable compositeDisposable;
26+
27+
public FAQViewModel() {
28+
realmRepo = RealmDataRepository.getDefaultInstance();
29+
compositeDisposable = new CompositeDisposable();
30+
}
31+
32+
public LiveData<List<FAQ>> getFaqData() {
33+
if (faqData == null) {
34+
LiveRealmData<FAQ> faqLiveRealmData = RealmDataRepository.asLiveData(realmRepo.getEventFAQs());
35+
faqData = Transformations.map(faqLiveRealmData, input -> input);
36+
}
37+
return faqData;
38+
}
39+
40+
public LiveData<Boolean> downloadFAQ() {
41+
if (faqOnDownloadResponse == null)
42+
faqOnDownloadResponse = new MutableLiveData<>();
43+
try {
44+
compositeDisposable.add(APIClient.getOpenEventAPI().getFAQs()
45+
.subscribeOn(Schedulers.io())
46+
.observeOn(AndroidSchedulers.mainThread())
47+
.subscribe(faqList -> {
48+
Timber.i("Downloaded FAQs");
49+
realmRepo.saveFAQs(faqList).subscribe();
50+
faqOnDownloadResponse.setValue(true);
51+
}, throwable -> {
52+
Timber.i("FAQs download failed");
53+
faqOnDownloadResponse.setValue(false);
54+
}));
55+
} catch (Exception e) {
56+
Timber.e(e);
57+
}
58+
59+
return faqOnDownloadResponse;
60+
}
61+
62+
@Override
63+
protected void onCleared() {
64+
super.onCleared();
65+
compositeDisposable.dispose();
66+
}
67+
}

android/app/src/main/java/org/fossasia/openevent/core/main/MainActivity.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.fossasia.openevent.core.about.AboutFragment;
7575
import org.fossasia.openevent.core.auth.AuthUtil;
7676
import org.fossasia.openevent.core.auth.profile.UserProfileActivity;
77+
import org.fossasia.openevent.core.faqs.FAQFragment;
7778
import org.fossasia.openevent.core.feed.facebook.CommentsDialogFragment;
7879
import org.fossasia.openevent.core.feed.facebook.FeedAdapter;
7980
import org.fossasia.openevent.core.feed.facebook.FeedFragment;
@@ -545,6 +546,9 @@ private void doMenuAction(int menuItemId) {
545546
case R.id.nav_locations:
546547
replaceFragment(new LocationsFragment(), R.string.menu_locations);
547548
break;
549+
case R.id.nav_faqs:
550+
replaceFragment(new FAQFragment(), R.string.menu_faqs);
551+
break;
548552
case R.id.nav_map:
549553
Bundle bundle = new Bundle();
550554
bundle.putBoolean(ConstantStrings.IS_MAP_FRAGMENT_FROM_MAIN_ACTIVITY, true);
@@ -833,6 +837,7 @@ public void downloadFromAssets() {
833837
readJsonAsset(Urls.SPONSORS);
834838
readJsonAsset(Urls.MICROLOCATIONS);
835839
readJsonAsset(Urls.SESSION_TYPES);
840+
//readJsonAsset(Urls.FAQS);
836841
} else {
837842
completeHandler.hide();
838843
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.fossasia.openevent.data;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
import com.github.jasminb.jsonapi.IntegerIdHandler;
6+
import com.github.jasminb.jsonapi.annotations.Id;
7+
import com.github.jasminb.jsonapi.annotations.Type;
8+
9+
import io.realm.RealmObject;
10+
import io.realm.annotations.PrimaryKey;
11+
import lombok.Data;
12+
import lombok.EqualsAndHashCode;
13+
14+
/**
15+
* Created by mayank on 4/2/18.
16+
*/
17+
@Data
18+
@Type("faq")
19+
@EqualsAndHashCode(callSuper = false)
20+
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
21+
public class FAQ extends RealmObject {
22+
23+
@PrimaryKey
24+
@Id(IntegerIdHandler.class)
25+
private int id;
26+
27+
private String question;
28+
private String answer;
29+
30+
// @Relationship("faq-type")
31+
// private RealmList<FAQType> faqTypes;
32+
}

0 commit comments

Comments
 (0)