mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Show campaigns (#2113)
* Show campaigns * Added a ui util class SwipableCardView which passes the onSwipe event to its children * NearbyCardView & CampaignView extend SwipableCardView * Fetch campaigns in ContributionsFragment * Added an option to enable disable campaign in Settings/Preferences * synced strings with master * removed duplicate initialsation of CampaignPresenter
This commit is contained in:
parent
707c52c768
commit
1b01c6517f
22 changed files with 608 additions and 47 deletions
16
app/src/main/java/fr/free/nrw/commons/BasePresenter.java
Normal file
16
app/src/main/java/fr/free/nrw/commons/BasePresenter.java
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base presenter, enforcing contracts to atach and detach view
|
||||||
|
*/
|
||||||
|
public interface BasePresenter {
|
||||||
|
/**
|
||||||
|
* Until a view is attached, it is open to listen events from the presenter
|
||||||
|
*/
|
||||||
|
void onAttachView(MvpView view);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaching a view makes sure that the view no more receives events from the presenter
|
||||||
|
*/
|
||||||
|
void onDetachView();
|
||||||
|
}
|
||||||
8
app/src/main/java/fr/free/nrw/commons/MvpView.java
Normal file
8
app/src/main/java/fr/free/nrw/commons/MvpView.java
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base interface for all the views
|
||||||
|
*/
|
||||||
|
public interface MvpView {
|
||||||
|
void showMessage(String message);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package fr.free.nrw.commons.campaigns;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data class to hold a campaign
|
||||||
|
*/
|
||||||
|
public class Campaign {
|
||||||
|
|
||||||
|
@SerializedName("title") private String title;
|
||||||
|
@SerializedName("description") private String description;
|
||||||
|
@SerializedName("startDate") private String startDate;
|
||||||
|
@SerializedName("endDate") private String endDate;
|
||||||
|
@SerializedName("link") private String link;
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStartDate() {
|
||||||
|
return startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartDate(String startDate) {
|
||||||
|
this.startDate = startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEndDate() {
|
||||||
|
return endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndDate(String endDate) {
|
||||||
|
this.endDate = endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLink() {
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLink(String link) {
|
||||||
|
this.link = link;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.campaigns;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data class to hold the campaign configs
|
||||||
|
*/
|
||||||
|
class CampaignConfig {
|
||||||
|
|
||||||
|
@SerializedName("showOnlyLiveCampaigns") private boolean showOnlyLiveCampaigns;
|
||||||
|
@SerializedName("sortBy") private String sortBy;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package fr.free.nrw.commons.campaigns;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class to hold the response from the campaigns api
|
||||||
|
*/
|
||||||
|
public class CampaignResponseDTO {
|
||||||
|
|
||||||
|
@SerializedName("config")
|
||||||
|
private CampaignConfig campaignConfig;
|
||||||
|
|
||||||
|
@SerializedName("campaigns")
|
||||||
|
private List<Campaign> campaigns;
|
||||||
|
|
||||||
|
public CampaignConfig getCampaignConfig() {
|
||||||
|
return campaignConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Campaign> getCampaigns() {
|
||||||
|
return campaigns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package fr.free.nrw.commons.campaigns;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
|
import fr.free.nrw.commons.utils.SwipableCardView;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A view which represents a single campaign
|
||||||
|
*/
|
||||||
|
public class CampaignView extends SwipableCardView {
|
||||||
|
Campaign campaign = null;
|
||||||
|
private ViewHolder viewHolder;
|
||||||
|
|
||||||
|
public CampaignView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CampaignView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CampaignView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCampaign(Campaign campaign) {
|
||||||
|
this.campaign = campaign;
|
||||||
|
if (campaign != null) {
|
||||||
|
this.setVisibility(View.VISIBLE);
|
||||||
|
viewHolder.init();
|
||||||
|
} else {
|
||||||
|
this.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public boolean onSwipe(View view) {
|
||||||
|
view.setVisibility(View.GONE);
|
||||||
|
((MainActivity) getContext()).prefs.edit()
|
||||||
|
.putBoolean("displayCampaignsCardView", false)
|
||||||
|
.apply();
|
||||||
|
ViewUtil.showLongToast(getContext(),
|
||||||
|
getResources().getString(R.string.nearby_campaign_dismiss_message));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
View rootView = inflate(getContext(), R.layout.layout_campagin, this);
|
||||||
|
viewHolder = new ViewHolder(rootView);
|
||||||
|
setOnClickListener(view -> {
|
||||||
|
if (campaign != null) {
|
||||||
|
showCampaignInBrowser(campaign.getLink());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* open the url associated with the campaign in the system's default browser
|
||||||
|
*/
|
||||||
|
private void showCampaignInBrowser(String link) {
|
||||||
|
Intent view = new Intent();
|
||||||
|
view.setAction(Intent.ACTION_VIEW);
|
||||||
|
view.setData(Uri.parse(link));
|
||||||
|
getContext().startActivity(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.tv_title) TextView tvTitle;
|
||||||
|
@BindView(R.id.tv_description) TextView tvDescription;
|
||||||
|
@BindView(R.id.tv_dates) TextView tvDates;
|
||||||
|
|
||||||
|
public ViewHolder(View itemView) {
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
if (campaign != null) {
|
||||||
|
tvTitle.setText(campaign.getTitle());
|
||||||
|
tvDescription.setText(campaign.getDescription());
|
||||||
|
SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
SimpleDateFormat outputDateFormat = new SimpleDateFormat("dd MMM");
|
||||||
|
try {
|
||||||
|
Date startDate = inputDateFormat.parse(campaign.getStartDate());
|
||||||
|
Date endDate = inputDateFormat.parse(campaign.getEndDate());
|
||||||
|
tvDates.setText(String.format("%1s - %2s", outputDateFormat.format(startDate),
|
||||||
|
outputDateFormat.format(endDate)));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package fr.free.nrw.commons.campaigns;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.MvpView;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import io.reactivex.SingleObserver;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The presenter for the campaigns view, fetches the campaigns from the api and informs the view on
|
||||||
|
* success and error
|
||||||
|
*/
|
||||||
|
public class CampaignsPresenter implements BasePresenter {
|
||||||
|
private final String TAG = "#CampaignsPresenter#";
|
||||||
|
private ICampaignsView view;
|
||||||
|
private MediaWikiApi mediaWikiApi;
|
||||||
|
private Disposable disposable;
|
||||||
|
private Campaign campaign;
|
||||||
|
|
||||||
|
@Override public void onAttachView(MvpView view) {
|
||||||
|
this.view = (ICampaignsView) view;
|
||||||
|
this.mediaWikiApi = ((ICampaignsView) view).getMediaWikiApi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onDetachView() {
|
||||||
|
this.view = null;
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make the api call to fetch the campaigns
|
||||||
|
*/
|
||||||
|
public void getCampaigns() {
|
||||||
|
if (view != null && mediaWikiApi != null) {
|
||||||
|
//If we already have a campaign, lets not make another call
|
||||||
|
if (this.campaign != null) {
|
||||||
|
view.showCampaigns(campaign);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Single<CampaignResponseDTO> campaigns = mediaWikiApi.getCampaigns();
|
||||||
|
campaigns.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribeWith(new SingleObserver<CampaignResponseDTO>() {
|
||||||
|
|
||||||
|
@Override public void onSubscribe(Disposable d) {
|
||||||
|
disposable = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onSuccess(CampaignResponseDTO campaignResponseDTO) {
|
||||||
|
List<Campaign> campaigns = campaignResponseDTO.getCampaigns();
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
if (campaigns == null || campaigns.isEmpty()) {
|
||||||
|
Log.e(TAG, "The campaigns list is empty");
|
||||||
|
view.showCampaigns(null);
|
||||||
|
}
|
||||||
|
Collections.sort(campaigns, (campaign, t1) -> {
|
||||||
|
Date date1, date2;
|
||||||
|
try {
|
||||||
|
date1 = dateFormat.parse(campaign.getStartDate());
|
||||||
|
date2 = dateFormat.parse(t1.getStartDate());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return date1.compareTo(date2);
|
||||||
|
});
|
||||||
|
Date campaignEndDate = null;
|
||||||
|
try {
|
||||||
|
campaignEndDate = dateFormat.parse(campaigns.get(0).getEndDate());
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (campaignEndDate == null) {
|
||||||
|
view.showCampaigns(null);
|
||||||
|
} else if (campaignEndDate.compareTo(new Date()) > 0) {
|
||||||
|
campaign = campaigns.get(0);
|
||||||
|
view.showCampaigns(campaign);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "The campaigns has already finished");
|
||||||
|
view.showCampaigns(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onError(Throwable e) {
|
||||||
|
Log.e(TAG, "could not fetch campaigns: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package fr.free.nrw.commons.campaigns;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.MvpView;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface which defines the view contracts of the campaign view
|
||||||
|
*/
|
||||||
|
public interface ICampaignsView extends MvpView {
|
||||||
|
MediaWikiApi getMediaWikiApi();
|
||||||
|
|
||||||
|
void showCampaigns(Campaign campaign);
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
|
||||||
|
|
@ -22,6 +23,8 @@ import android.support.v4.content.Loader;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -30,6 +33,14 @@ import android.widget.AdapterView;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
|
|
||||||
|
import android.widget.Toast;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.campaigns.Campaign;
|
||||||
|
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||||
|
import fr.free.nrw.commons.campaigns.CampaignView;
|
||||||
|
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
|
||||||
|
import fr.free.nrw.commons.campaigns.ICampaignsView;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
|
@ -60,6 +71,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import org.acra.util.ToastSender;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
||||||
|
|
@ -76,7 +88,7 @@ public class ContributionsFragment
|
||||||
MediaDetailPagerFragment.MediaDetailProvider,
|
MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
FragmentManager.OnBackStackChangedListener,
|
FragmentManager.OnBackStackChangedListener,
|
||||||
ContributionsListFragment.SourceRefresher,
|
ContributionsListFragment.SourceRefresher,
|
||||||
LocationUpdateListener
|
LocationUpdateListener,ICampaignsView
|
||||||
{
|
{
|
||||||
@Inject
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
|
|
@ -112,6 +124,10 @@ public class ContributionsFragment
|
||||||
private boolean isFragmentAttachedBefore = false;
|
private boolean isFragmentAttachedBefore = false;
|
||||||
private View checkBoxView;
|
private View checkBoxView;
|
||||||
private CheckBox checkBox;
|
private CheckBox checkBox;
|
||||||
|
private CampaignsPresenter presenter;
|
||||||
|
|
||||||
|
|
||||||
|
@BindView(R.id.campaigns_view) CampaignView campaignView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Since we will need to use parent activity on onAuthCookieAcquired, we have to wait
|
* Since we will need to use parent activity on onAuthCookieAcquired, we have to wait
|
||||||
|
|
@ -142,6 +158,10 @@ public class ContributionsFragment
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_contributions, container, false);
|
View view = inflater.inflate(R.layout.fragment_contributions, container, false);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
presenter = new CampaignsPresenter();
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
campaignView.setVisibility(View.GONE);
|
||||||
nearbyNoificationCardView = view.findViewById(R.id.card_view_nearby);
|
nearbyNoificationCardView = view.findViewById(R.id.card_view_nearby);
|
||||||
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
||||||
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
||||||
|
|
@ -173,6 +193,27 @@ public class ContributionsFragment
|
||||||
setUploadCount();
|
setUploadCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getChildFragmentManager().registerFragmentLifecycleCallbacks(
|
||||||
|
new FragmentManager.FragmentLifecycleCallbacks() {
|
||||||
|
@Override public void onFragmentResumed(FragmentManager fm, Fragment f) {
|
||||||
|
super.onFragmentResumed(fm, f);
|
||||||
|
//If media detail pager fragment is visible, hide the campaigns view [might not be the best way to do, this but yeah, this proves to work for now]
|
||||||
|
Log.e("#CF#", "onFragmentResumed" + f.getClass().getName());
|
||||||
|
if (f instanceof MediaDetailPagerFragment) {
|
||||||
|
campaignView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onFragmentDetached(FragmentManager fm, Fragment f) {
|
||||||
|
super.onFragmentDetached(fm, f);
|
||||||
|
Log.e("#CF#", "onFragmentDetached" + f.getClass().getName());
|
||||||
|
//If media detail pager fragment is detached, ContributionsList fragment is gonna be visible, [becomes tightly coupled though]
|
||||||
|
if (f instanceof MediaDetailPagerFragment) {
|
||||||
|
fetchCampaigns();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -537,7 +578,7 @@ public class ContributionsFragment
|
||||||
nearbyNoificationCardView.setVisibility(View.GONE);
|
nearbyNoificationCardView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchCampaigns();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -694,5 +735,38 @@ public class ContributionsFragment
|
||||||
// Update closest nearby card view if location changed more than 500 meters
|
// Update closest nearby card view if location changed more than 500 meters
|
||||||
updateClosestNearbyCardViewInfo();
|
updateClosestNearbyCardViewInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public void onViewCreated(@NonNull View view,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ask the presenter to fetch the campaigns only if user has not manually disabled it
|
||||||
|
*/
|
||||||
|
private void fetchCampaigns() {
|
||||||
|
if (prefs.getBoolean("displayCampaignsCardView", true)) {
|
||||||
|
presenter.getCampaigns();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void showMessage(String message) {
|
||||||
|
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public MediaWikiApi getMediaWikiApi() {
|
||||||
|
return mediaWikiApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void showCampaigns(Campaign campaign) {
|
||||||
|
if (campaign != null) {
|
||||||
|
campaignView.setCampaign(campaign);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
presenter.onDetachView();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import android.text.TextUtils;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
|
|
@ -77,6 +78,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
private SharedPreferences categoryPreferences;
|
private SharedPreferences categoryPreferences;
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
private final OkHttpClient okHttpClient;
|
private final OkHttpClient okHttpClient;
|
||||||
|
private final String WIKIMEDIA_CAMPAIGNS_BASE_URL =
|
||||||
|
"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json";
|
||||||
|
|
||||||
public ApacheHttpClientMediaWikiApi(Context context,
|
public ApacheHttpClientMediaWikiApi(Context context,
|
||||||
String apiURL,
|
String apiURL,
|
||||||
|
|
@ -1054,4 +1057,18 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public Single<CampaignResponseDTO> getCampaigns() {
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
Request request = new Request.Builder().url(WIKIMEDIA_CAMPAIGNS_BASE_URL).build();
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return gson.fromJson(json, CampaignResponseDTO.class);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -105,6 +106,8 @@ public interface MediaWikiApi {
|
||||||
|
|
||||||
void logout();
|
void logout();
|
||||||
|
|
||||||
|
Single<CampaignResponseDTO> getCampaigns();
|
||||||
|
|
||||||
interface ProgressListener {
|
interface ProgressListener {
|
||||||
void onProgress(long transferred, long total);
|
void onProgress(long transferred, long total);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,8 @@ import android.content.res.Resources;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.design.widget.CoordinatorLayout;
|
|
||||||
import android.support.design.widget.SwipeDismissBehavior;
|
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.CardView;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
@ -20,19 +16,17 @@ import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.contributions.MainActivity;
|
import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
|
import fr.free.nrw.commons.utils.SwipableCardView;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom card view for nearby notification card view on main screen, above contributions list
|
* Custom card view for nearby notification card view on main screen, above contributions list
|
||||||
*/
|
*/
|
||||||
public class NearbyNoificationCardView extends CardView{
|
public class NearbyNoificationCardView extends SwipableCardView {
|
||||||
|
|
||||||
private static final float MINIMUM_THRESHOLD_FOR_SWIPE = 100;
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
private Button permissionRequestButton;
|
private Button permissionRequestButton;
|
||||||
|
|
@ -99,41 +93,15 @@ public class NearbyNoificationCardView extends CardView{
|
||||||
|
|
||||||
private void setActionListeners() {
|
private void setActionListeners() {
|
||||||
this.setOnClickListener(view -> ((MainActivity)context).viewPager.setCurrentItem(1));
|
this.setOnClickListener(view -> ((MainActivity)context).viewPager.setCurrentItem(1));
|
||||||
|
|
||||||
this.setOnTouchListener(
|
|
||||||
(v, event) -> {
|
|
||||||
boolean isSwipe = false;
|
|
||||||
float deltaX=0.0f;
|
|
||||||
switch (event.getAction()) {
|
|
||||||
case MotionEvent.ACTION_DOWN:
|
|
||||||
x1 = event.getX();
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_UP:
|
|
||||||
x2 = event.getX();
|
|
||||||
deltaX = x2 - x1;
|
|
||||||
if (deltaX < 0) {
|
|
||||||
//Right to left swipe
|
|
||||||
isSwipe = true;
|
|
||||||
} else if (deltaX > 0) {
|
|
||||||
//Left to right swipe
|
|
||||||
isSwipe = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (isSwipe && (pixelToDp(Math.abs(deltaX)) > MINIMUM_THRESHOLD_FOR_SWIPE)) {
|
|
||||||
v.setVisibility(GONE);
|
|
||||||
// Save shared preference for nearby card view accordingly
|
|
||||||
((MainActivity) context).prefs.edit()
|
|
||||||
.putBoolean("displayNearbyCardView", false).apply();
|
|
||||||
ViewUtil.showLongToast(context, getResources().getString(R.string.nearby_notification_dismiss_message));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float pixelToDp(float pixels) {
|
@Override public boolean onSwipe(View view) {
|
||||||
return (pixels / Resources.getSystem().getDisplayMetrics().density);
|
view.setVisibility(GONE);
|
||||||
|
// Save shared preference for nearby card view accordingly
|
||||||
|
((MainActivity) context).prefs.edit().putBoolean("displayNearbyCardView", false).apply();
|
||||||
|
ViewUtil.showLongToast(context,
|
||||||
|
getResources().getString(R.string.nearby_notification_dismiss_message));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.CardView;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A card view which informs onSwipe events to its child
|
||||||
|
*/
|
||||||
|
public abstract class SwipableCardView extends CardView {
|
||||||
|
float x1, x2;
|
||||||
|
private static final float MINIMUM_THRESHOLD_FOR_SWIPE = 100;
|
||||||
|
|
||||||
|
public SwipableCardView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
interceptOnTouchListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwipableCardView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
interceptOnTouchListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwipableCardView(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
interceptOnTouchListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void interceptOnTouchListener() {
|
||||||
|
this.setOnTouchListener((v, event) -> {
|
||||||
|
boolean isSwipe = false;
|
||||||
|
float deltaX = 0.0f;
|
||||||
|
Log.e("#SwipableCardView#", event.getAction() + "");
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
x1 = event.getX();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
x2 = event.getX();
|
||||||
|
deltaX = x2 - x1;
|
||||||
|
if (deltaX < 0) {
|
||||||
|
//Right to left swipe
|
||||||
|
isSwipe = true;
|
||||||
|
} else if (deltaX > 0) {
|
||||||
|
//Left to right swipe
|
||||||
|
isSwipe = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (isSwipe && (pixelToDp(Math.abs(deltaX)) > MINIMUM_THRESHOLD_FOR_SWIPE)) {
|
||||||
|
return onSwipe(v);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* abstract function which informs swipe events to those who have inherited from it
|
||||||
|
*/
|
||||||
|
public abstract boolean onSwipe(View view);
|
||||||
|
|
||||||
|
private float pixelToDp(float pixels) {
|
||||||
|
return (pixels / Resources.getSystem().getDisplayMetrics().density);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_campaign.png
Executable file
BIN
app/src/main/res/drawable-hdpi/ic_campaign.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 807 B |
BIN
app/src/main/res/drawable-mdpi/ic_campaign.png
Executable file
BIN
app/src/main/res/drawable-mdpi/ic_campaign.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 542 B |
BIN
app/src/main/res/drawable-xhdpi/ic_campaign.png
Executable file
BIN
app/src/main/res/drawable-xhdpi/ic_campaign.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_campaign.png
Executable file
BIN
app/src/main/res/drawable-xxhdpi/ic_campaign.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_campaign.png
Executable file
BIN
app/src/main/res/drawable-xxxhdpi/ic_campaign.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -12,11 +12,21 @@
|
||||||
app:cardBackgroundColor="?attr/mainCardBackground"
|
app:cardBackgroundColor="?attr/mainCardBackground"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<fr.free.nrw.commons.campaigns.CampaignView
|
||||||
|
android:id="@+id/campaigns_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
app:cardBackgroundColor="?attr/mainCardBackground"
|
||||||
|
></fr.free.nrw.commons.campaigns.CampaignView>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/root_frame"
|
android:id="@+id/root_frame"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#000">
|
android:layout_marginTop="2dp"
|
||||||
|
android:background="#000"
|
||||||
|
>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
71
app/src/main/res/layout/layout_campagin.xml
Normal file
71
app/src/main/res/layout/layout_campagin.xml
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingEnd="10dp"
|
||||||
|
android:paddingRight="10dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_campaign"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_campaign"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="4dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/iv_campaign"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Campaign Title"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_description"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:gravity="start"
|
||||||
|
android:paddingTop="2dp"
|
||||||
|
android:textAlignment="textStart"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_title"
|
||||||
|
tools:text="Campaign Description"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_dates"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:paddingTop="2dp"
|
||||||
|
android:text="@string/ends_on"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tv_description"
|
||||||
|
/>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
@ -433,5 +433,9 @@ Upload your first media by touching the camera or gallery icon above.</string>
|
||||||
<string name="never_ask_again">Never ask this again</string>
|
<string name="never_ask_again">Never ask this again</string>
|
||||||
<string name="display_location_permission_title">Display location permission</string>
|
<string name="display_location_permission_title">Display location permission</string>
|
||||||
<string name="display_location_permission_explanation">Ask for location permission when needed for nearby notification card view feature.</string>
|
<string name="display_location_permission_explanation">Ask for location permission when needed for nearby notification card view feature.</string>
|
||||||
|
<string name="ends_on">Ends on:</string>
|
||||||
|
<string name="display_campaigns">Display campaigns</string>
|
||||||
|
<string name="display_campaigns_explanation">Tap here to see the ongoing campaigns</string>
|
||||||
|
<string name="nearby_campaign_dismiss_message">You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,11 @@
|
||||||
android:title="@string/display_location_permission_title"
|
android:title="@string/display_location_permission_title"
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:summary="@string/display_location_permission_explanation" />
|
android:summary="@string/display_location_permission_explanation" />
|
||||||
|
<fr.free.nrw.commons.ui.LongTitlePreferences.LongTitleSwitchPreference
|
||||||
|
android:key="displayCampaignsCardView"
|
||||||
|
android:title="@string/display_campaigns"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:summary="@string/display_campaigns_explanation" />
|
||||||
|
|
||||||
</fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory>
|
</fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue