diff --git a/android/app/src/main/java/org/fossasia/openevent/common/api/APIClient.java b/android/app/src/main/java/org/fossasia/openevent/common/api/APIClient.java index 7c0adc2907..ff4fd5117d 100644 --- a/android/app/src/main/java/org/fossasia/openevent/common/api/APIClient.java +++ b/android/app/src/main/java/org/fossasia/openevent/common/api/APIClient.java @@ -10,6 +10,7 @@ import org.fossasia.openevent.common.network.NetworkUtils; import org.fossasia.openevent.core.auth.AuthUtil; import org.fossasia.openevent.core.auth.model.User; +import org.fossasia.openevent.data.DiscountCode; import org.fossasia.openevent.data.Event; import org.fossasia.openevent.data.FAQ; import org.fossasia.openevent.data.Microlocation; @@ -90,7 +91,7 @@ public static OpenEventAPI getOpenEventAPI() { ObjectMapper objectMapper = getObjectMapper(); - Class[] classes = {Event.class, Track.class, Speaker.class, Sponsor.class, Session.class, Microlocation.class, User.class, FAQ.class, Notification.class}; + Class[] classes = {Event.class, Track.class, Speaker.class, Sponsor.class, Session.class, Microlocation.class, User.class, FAQ.class, Notification.class, DiscountCode.class}; openEventAPI = new Retrofit.Builder() .client(okHttpClient) diff --git a/android/app/src/main/java/org/fossasia/openevent/common/api/OpenEventAPI.java b/android/app/src/main/java/org/fossasia/openevent/common/api/OpenEventAPI.java index 0b3c002a3a..f332ad36b4 100644 --- a/android/app/src/main/java/org/fossasia/openevent/common/api/OpenEventAPI.java +++ b/android/app/src/main/java/org/fossasia/openevent/common/api/OpenEventAPI.java @@ -5,6 +5,7 @@ import org.fossasia.openevent.core.auth.model.LoginResponse; import org.fossasia.openevent.core.auth.model.UploadImage; import org.fossasia.openevent.core.auth.model.User; +import org.fossasia.openevent.data.DiscountCode; import org.fossasia.openevent.data.Event; import org.fossasia.openevent.data.FAQ; import org.fossasia.openevent.data.Microlocation; @@ -69,4 +70,7 @@ public interface OpenEventAPI { @GET("../../users/{id}/notifications") Observable> getNotifications(@Path("id") long id); + @GET("discount-codes") + Observable> getDiscountCodes(); + } \ No newline at end of file diff --git a/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountCodeFragment.java b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountCodeFragment.java new file mode 100644 index 0000000000..bb5b487717 --- /dev/null +++ b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountCodeFragment.java @@ -0,0 +1,152 @@ +package org.fossasia.openevent.core.discount; + +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.Snackbar; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import org.fossasia.openevent.R; +import org.fossasia.openevent.common.network.NetworkUtils; +import org.fossasia.openevent.common.ui.base.BaseFragment; +import org.fossasia.openevent.common.utils.Utils; +import org.fossasia.openevent.core.auth.AuthUtil; +import org.fossasia.openevent.core.auth.LoginActivity; +import org.fossasia.openevent.data.DiscountCode; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import timber.log.Timber; + +public class DiscountCodeFragment extends BaseFragment { + + @BindView(R.id.discount_refresh_layout) + protected SwipeRefreshLayout swipeRefreshLayout; + @BindView(R.id.txt_no_discount_codes) + protected TextView noDiscountCodeView; + @BindView(R.id.discount_code_header) + protected TextView discountCodeHeader; + @BindView(R.id.list_discount_codes) + protected RecyclerView discountCodesRecyclerView; + + private List discountCodes = new ArrayList<>(); + private DiscountCodesListAdapter discountCodesListAdapter; + private DiscountFragmentViewModel discountFragmentViewModel; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + setHasOptionsMenu(true); + final View view = super.onCreateView(inflater, container, savedInstanceState); + setUpRecyclerView(); + Utils.registerIfUrlValid(swipeRefreshLayout, this, this::refresh); + discountFragmentViewModel = ViewModelProviders.of(this).get(DiscountFragmentViewModel.class); + if (AuthUtil.isUserLoggedIn()) { + if (NetworkUtils.haveNetworkConnection(getContext())) { + swipeRefreshLayout.setRefreshing(true); + downloadDiscountCodes(); + } + loadData(); + } else { + redirectToLogin(); + } + return view; + } + + private void redirectToLogin() { + Toast.makeText(getContext(), "User need to be logged in!", Toast.LENGTH_SHORT).show(); + Intent intent = new Intent(getActivity(), LoginActivity.class); + startActivity(intent); + } + + private void downloadDiscountCodes() { + discountFragmentViewModel.downloadDiscountCodes().observe(this, this::onDiscountCodeDownloadDone); + } + + private void setUpRecyclerView() { + discountCodesRecyclerView.setVisibility(View.VISIBLE); + discountCodesListAdapter = new DiscountCodesListAdapter(discountCodes); + discountCodesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); + discountCodesRecyclerView.setNestedScrollingEnabled(false); + discountCodesRecyclerView.setAdapter(discountCodesListAdapter); + discountCodesRecyclerView.setItemAnimator(new DefaultItemAnimator()); + } + + private void handleVisibility() { + if (!discountCodes.isEmpty()) { + discountCodeHeader.setVisibility(View.VISIBLE); + discountCodesRecyclerView.setVisibility(View.VISIBLE); + noDiscountCodeView.setVisibility(View.GONE); + } else { + discountCodeHeader.setVisibility(View.GONE); + discountCodesRecyclerView.setVisibility(View.GONE); + } + } + + private void loadData() { + discountFragmentViewModel.getDiscountCodes().observe(this, discountCodes -> { + this.discountCodes.clear(); + this.discountCodes.addAll(discountCodes); + discountCodesListAdapter.notifyDataSetChanged(); + handleVisibility(); + }); + } + + public void onDiscountCodeDownloadDone(boolean status) { + if (!status) { + Timber.d("Discount Codes Download failed"); + if (getActivity() != null && swipeRefreshLayout != null) { + Snackbar.make(swipeRefreshLayout, getActivity().getString(R.string.refresh_failed), Snackbar.LENGTH_LONG) + .setAction(R.string.retry_download, view -> refresh()).show(); + } + } + if (swipeRefreshLayout != null) + swipeRefreshLayout.setRefreshing(false); + } + + private void refresh() { + NetworkUtils.checkConnection(new WeakReference<>(getContext()), new NetworkUtils.NetworkStateReceiverListener() { + + @Override + public void networkAvailable() { + if (AuthUtil.isUserLoggedIn()) { + downloadDiscountCodes(); + } else { + redirectToLogin(); + if (swipeRefreshLayout != null) + swipeRefreshLayout.setRefreshing(false); + } + } + + @Override + public void networkUnavailable() { + onDiscountCodeDownloadDone(false); + } + }); + } + + @Override + protected int getLayoutResource() { + return R.layout.list_discount_codes; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + Utils.unregisterIfUrlValid(this); + } + +} diff --git a/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountCodesListAdapter.java b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountCodesListAdapter.java new file mode 100644 index 0000000000..ad4b354a41 --- /dev/null +++ b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountCodesListAdapter.java @@ -0,0 +1,29 @@ +package org.fossasia.openevent.core.discount; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.fossasia.openevent.R; +import org.fossasia.openevent.common.ui.base.BaseRVAdapter; +import org.fossasia.openevent.data.DiscountCode; + +import java.util.List; + +public class DiscountCodesListAdapter extends BaseRVAdapter { + + public DiscountCodesListAdapter(List discountCodes) { + super(discountCodes); + } + + @Override + public DiscountViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_discount_code, parent, false); + return new DiscountViewHolder(view); + } + + public void onBindViewHolder(DiscountViewHolder holder, int position) { + holder.bindDiscountCode(getItem(position)); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountFragmentViewModel.java b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountFragmentViewModel.java new file mode 100644 index 0000000000..a9c1e6a300 --- /dev/null +++ b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountFragmentViewModel.java @@ -0,0 +1,68 @@ +package org.fossasia.openevent.core.discount; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; +import android.arch.lifecycle.Transformations; +import android.arch.lifecycle.ViewModel; + +import org.fossasia.openevent.common.api.APIClient; +import org.fossasia.openevent.common.arch.LiveRealmData; +import org.fossasia.openevent.data.DiscountCode; +import org.fossasia.openevent.data.repository.RealmDataRepository; + +import java.util.List; + +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +public class DiscountFragmentViewModel extends ViewModel { + + private LiveData> discountCodes; + private RealmDataRepository realmRepo; + private MutableLiveData discountCodesDownloadResponse; + private CompositeDisposable compositeDisposable; + + public DiscountFragmentViewModel() { + realmRepo = RealmDataRepository.getDefaultInstance(); + compositeDisposable = new CompositeDisposable(); + } + + + public LiveData> getDiscountCodes() { + if (discountCodes == null) { + LiveRealmData discountCodeLiveRealmData = RealmDataRepository.asLiveData(realmRepo.getDiscountCodes()); + discountCodes = Transformations.map(discountCodeLiveRealmData, input -> input); + } + return discountCodes; + } + + public LiveData downloadDiscountCodes() { + if (discountCodesDownloadResponse == null) + discountCodesDownloadResponse = new MutableLiveData<>(); + try { + compositeDisposable.add(APIClient.getOpenEventAPI().getDiscountCodes() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(discountList -> { + Timber.i("Downloaded Discount Codes"); + realmRepo.saveDiscountCodes(discountList).subscribe(); + discountCodesDownloadResponse.setValue(true); + }, throwable -> { + Timber.i("Discount Codes download failed"); + discountCodesDownloadResponse.setValue(false); + })); + } catch (Exception e) { + Timber.e(e); + } + + return discountCodesDownloadResponse; + } + + @Override + protected void onCleared() { + super.onCleared(); + compositeDisposable.dispose(); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountViewHolder.java b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountViewHolder.java new file mode 100644 index 0000000000..37c94f0bc6 --- /dev/null +++ b/android/app/src/main/java/org/fossasia/openevent/core/discount/DiscountViewHolder.java @@ -0,0 +1,86 @@ +package org.fossasia.openevent.core.discount; + +import android.content.Context; +import android.content.res.Resources; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import org.fossasia.openevent.R; +import org.fossasia.openevent.common.utils.Utils; +import org.fossasia.openevent.data.DiscountCode; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class DiscountViewHolder extends RecyclerView.ViewHolder { + + @BindView(R.id.discount_code) + protected TextView discountCodeTextView; + + @BindView(R.id.discount_code_value) + protected TextView discountCodeValue; + + @BindView(R.id.discount_is_active) + protected TextView discountIsActive + ; + @BindView(R.id.discount_max_quantity) + protected TextView discountMaxQuantity; + + @BindView(R.id.discount_min_quantity) + protected TextView discountMinQuantity; + + @BindView(R.id.discount_tickets) + protected TextView discountTicket; + + @BindView(R.id.discount_url) + protected TextView discountUrl; + + @BindView(R.id.discount_used_for) + protected TextView discountUsedFor; + + private Context context; + + public DiscountViewHolder(View itemView) { + super(itemView); + ButterKnife.bind(this, itemView); + context = itemView.getContext(); + } + + public void bindDiscountCode(DiscountCode discountCode) { + String code = Utils.checkStringEmpty(discountCode.getCode()); + String discountUrl = Utils.checkStringEmpty(discountCode.getDiscount_url()); + String tickets = Utils.checkStringEmpty(discountCode.getTickets()); + String usedFor = Utils.checkStringEmpty(discountCode.getUsed_for()); + + Resources res = context.getResources(); + String minQuantity = String.format(res.getString(R.string.discount_code_min_quantity), discountCode.getMin_quantity()); + String maxQuantity = String.format(res.getString(R.string.discount_code_max_quantity), discountCode.getMax_quantity()); + String value = String.format(res.getString(R.string.discount_code_value), discountCode.getValue()); + + setStringFieldWithPrefix(discountCodeTextView, code, null); + setStringFieldWithPrefix(discountCodeValue, value, null); + setStringFieldWithPrefix(discountMinQuantity, minQuantity, null); + setStringFieldWithPrefix(discountMaxQuantity, maxQuantity, null); + setStringFieldWithPrefix(discountTicket, tickets, "Tickets : "); + setStringFieldWithPrefix(discountUsedFor, usedFor, "Used For : "); + setStringFieldWithPrefix(this.discountUrl, discountUrl, "Discount URL : "); + } + + + private void setStringFieldWithPrefix(TextView textView, String field, String prefix) { + if (textView == null) + return; + + if (!TextUtils.isEmpty(field.trim())) { + textView.setVisibility(View.VISIBLE); + if(prefix == null) + textView.setText(field); + else + textView.setText(prefix + field); + } else { + textView.setVisibility(View.GONE); + } + } +} diff --git a/android/app/src/main/java/org/fossasia/openevent/core/main/MainActivity.java b/android/app/src/main/java/org/fossasia/openevent/core/main/MainActivity.java index 60fdbc929b..fbd94bfd91 100644 --- a/android/app/src/main/java/org/fossasia/openevent/core/main/MainActivity.java +++ b/android/app/src/main/java/org/fossasia/openevent/core/main/MainActivity.java @@ -73,6 +73,7 @@ import org.fossasia.openevent.core.about.AboutFragment; import org.fossasia.openevent.core.auth.AuthUtil; import org.fossasia.openevent.core.auth.profile.UserProfileActivity; +import org.fossasia.openevent.core.discount.DiscountCodeFragment; import org.fossasia.openevent.core.faqs.FAQFragment; import org.fossasia.openevent.core.feed.FeedFragment; import org.fossasia.openevent.core.feed.facebook.api.FacebookApi; @@ -529,6 +530,9 @@ private void doMenuAction(int menuItemId) { case R.id.nav_home: replaceFragment(AboutFragment.newInstance(onMapSelectedListener), R.string.menu_home); break; + case R.id.nav_discount_code: + replaceFragment(new DiscountCodeFragment(), R.string.discount_code); + break; case R.id.nav_notification: replaceFragment(NotificationsFragment.getInstance(), R.string.menu_notification); break; diff --git a/android/app/src/main/java/org/fossasia/openevent/data/DiscountCode.java b/android/app/src/main/java/org/fossasia/openevent/data/DiscountCode.java new file mode 100644 index 0000000000..796abe09d1 --- /dev/null +++ b/android/app/src/main/java/org/fossasia/openevent/data/DiscountCode.java @@ -0,0 +1,34 @@ +package org.fossasia.openevent.data; + +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.github.jasminb.jsonapi.IntegerIdHandler; +import com.github.jasminb.jsonapi.annotations.Id; +import com.github.jasminb.jsonapi.annotations.Type; + +import io.realm.RealmObject; +import io.realm.annotations.PrimaryKey; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@Type("discount-code") +@EqualsAndHashCode(callSuper = false) +@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class) +public class DiscountCode extends RealmObject { + + @PrimaryKey + @Id(IntegerIdHandler.class) + private int id; + private String code; + private String discount_url; + private float value; + private String type; + private Boolean is_active; + private int tickets_number; + private int min_quantity; + private int max_quantity; + private String tickets; + private String used_for; + +} \ No newline at end of file diff --git a/android/app/src/main/java/org/fossasia/openevent/data/repository/RealmDataRepository.java b/android/app/src/main/java/org/fossasia/openevent/data/repository/RealmDataRepository.java index 93ec69394a..d340339ba4 100644 --- a/android/app/src/main/java/org/fossasia/openevent/data/repository/RealmDataRepository.java +++ b/android/app/src/main/java/org/fossasia/openevent/data/repository/RealmDataRepository.java @@ -8,6 +8,7 @@ import org.fossasia.openevent.common.events.BookmarkChangedEvent; import org.fossasia.openevent.config.StrategyRegistry; import org.fossasia.openevent.core.auth.model.User; +import org.fossasia.openevent.data.DiscountCode; import org.fossasia.openevent.data.Event; import org.fossasia.openevent.data.FAQ; import org.fossasia.openevent.data.Microlocation; @@ -500,6 +501,25 @@ public RealmResults getSponsors() { return realm.where(Sponsor.class).findAllSortedAsync("level", Sort.DESCENDING, "name", Sort.ASCENDING); } + //DiscountCode section + + private void saveDiscountCodesinRealm(List discountCodes) { + Realm realm = Realm.getDefaultInstance(); + realm.executeTransaction(realm1 -> realm1.insertOrUpdate(discountCodes)); + realm.close(); + } + + public Completable saveDiscountCodes(final List discountCodes) { + return Completable.fromAction(() -> { + saveDiscountCodesinRealm(discountCodes); + Timber.d("Saved DiscountCodes"); + }); + } + + public RealmResults getDiscountCodes() { + return realm.where(DiscountCode.class).findAllSortedAsync("code"); + } + // Location Section private void saveLocationsInRealm(List locations) { diff --git a/android/app/src/main/res/drawable/ic_discount_code_black_24dp.xml b/android/app/src/main/res/drawable/ic_discount_code_black_24dp.xml new file mode 100644 index 0000000000..8615261108 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_discount_code_black_24dp.xml @@ -0,0 +1,7 @@ + + + diff --git a/android/app/src/main/res/layout/item_discount_code.xml b/android/app/src/main/res/layout/item_discount_code.xml new file mode 100644 index 0000000000..d6072e9fbc --- /dev/null +++ b/android/app/src/main/res/layout/item_discount_code.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/list_discount_codes.xml b/android/app/src/main/res/layout/list_discount_codes.xml new file mode 100644 index 0000000000..d9bf60c7bf --- /dev/null +++ b/android/app/src/main/res/layout/list_discount_codes.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/menu/drawer.xml b/android/app/src/main/res/menu/drawer.xml index e47c172236..c1002f70ed 100644 --- a/android/app/src/main/res/menu/drawer.xml +++ b/android/app/src/main/res/menu/drawer.xml @@ -24,6 +24,11 @@ android:icon="@drawable/ic_notifications_black_24dp" android:title="@string/menu_notification" /> + Incorrect password Error, Cannot set empty password OK + Value : %1$f + Minimum Quantity : %1$d + Maximum Quantity : %1$d Successfully logged in Successfully logged out @@ -74,6 +77,7 @@ Event Info Sessions Session Types + Discount Codes Sign Up