diff --git a/app/src/main/java/fr/free/nrw/commons/BasePresenter.java b/app/src/main/java/fr/free/nrw/commons/BasePresenter.java new file mode 100644 index 000000000..041fde6b2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/BasePresenter.java @@ -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(); +} diff --git a/app/src/main/java/fr/free/nrw/commons/MvpView.java b/app/src/main/java/fr/free/nrw/commons/MvpView.java new file mode 100644 index 000000000..7485b2aaf --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/MvpView.java @@ -0,0 +1,8 @@ +package fr.free.nrw.commons; + +/** + * Base interface for all the views + */ +public interface MvpView { + void showMessage(String message); +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/Campaign.java b/app/src/main/java/fr/free/nrw/commons/campaigns/Campaign.java new file mode 100644 index 000000000..2bd4893b8 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/Campaign.java @@ -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; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.java b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.java new file mode 100644 index 000000000..a715aaf63 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.java @@ -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; +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.java b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.java new file mode 100644 index 000000000..dd0bd51ce --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.java @@ -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 campaigns; + + public CampaignConfig getCampaignConfig() { + return campaignConfig; + } + + public List getCampaigns() { + return campaigns; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignView.java b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignView.java new file mode 100644 index 000000000..dec62cc1b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignView.java @@ -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(); + } + } + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.java b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.java new file mode 100644 index 000000000..98ef7e6de --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignsPresenter.java @@ -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 campaigns = mediaWikiApi.getCampaigns(); + campaigns.observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(Schedulers.io()) + .subscribeWith(new SingleObserver() { + + @Override public void onSubscribe(Disposable d) { + disposable = d; + } + + @Override public void onSuccess(CampaignResponseDTO campaignResponseDTO) { + List 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()); + } + }); + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/ICampaignsView.java b/app/src/main/java/fr/free/nrw/commons/campaigns/ICampaignsView.java new file mode 100644 index 000000000..8610728b3 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/ICampaignsView.java @@ -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); +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index c8f140575..8b43c9f90 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -14,6 +14,7 @@ import android.os.Bundle; import android.os.IBinder; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; 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.widget.CursorAdapter; import android.support.v7.app.AlertDialog; +import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -30,6 +33,14 @@ import android.widget.AdapterView; import android.widget.CheckBox; 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.concurrent.CountDownLatch; @@ -60,6 +71,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.acra.util.ToastSender; import timber.log.Timber; import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED; @@ -76,7 +88,7 @@ public class ContributionsFragment MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener, ContributionsListFragment.SourceRefresher, - LocationUpdateListener + LocationUpdateListener,ICampaignsView { @Inject @Named("default_preferences") @@ -112,6 +124,10 @@ public class ContributionsFragment private boolean isFragmentAttachedBefore = false; private View checkBoxView; 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 @@ -142,6 +158,10 @@ public class ContributionsFragment @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 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); checkBoxView = View.inflate(getActivity(), R.layout.nearby_permission_dialog, null); checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again); @@ -173,6 +193,27 @@ public class ContributionsFragment 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; } @@ -537,7 +578,7 @@ public class ContributionsFragment nearbyNoificationCardView.setVisibility(View.GONE); } - + fetchCampaigns(); } /** @@ -694,5 +735,38 @@ public class ContributionsFragment // Update closest nearby card view if location changed more than 500 meters 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(); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java index 2e1cbb1ac..8c4c39639 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java @@ -11,6 +11,7 @@ import android.text.TextUtils; import com.google.gson.Gson; +import fr.free.nrw.commons.campaigns.CampaignResponseDTO; import org.apache.http.HttpResponse; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; @@ -77,6 +78,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { private SharedPreferences categoryPreferences; private Gson gson; 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, String apiURL, @@ -1054,4 +1057,18 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { } } + @Override public Single 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; + }); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java index d7bf65802..46d71dc26 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java @@ -4,6 +4,7 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import fr.free.nrw.commons.campaigns.CampaignResponseDTO; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -105,6 +106,8 @@ public interface MediaWikiApi { void logout(); + Single getCampaigns(); + interface ProgressListener { void onProgress(long transferred, long total); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java index 61798a95a..c9f0e0ff2 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyNoificationCardView.java @@ -6,12 +6,8 @@ import android.content.res.Resources; import android.os.Handler; import android.support.annotation.NonNull; 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.widget.CardView; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.Button; @@ -20,19 +16,17 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; - 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 timber.log.Timber; /** * 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 Button permissionRequestButton; @@ -99,41 +93,15 @@ public class NearbyNoificationCardView extends CardView{ private void setActionListeners() { 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) { - return (pixels / Resources.getSystem().getDisplayMetrics().density); + @Override public boolean onSwipe(View view) { + 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; } /** diff --git a/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.java b/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.java new file mode 100644 index 000000000..a65033d15 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/utils/SwipableCardView.java @@ -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); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_campaign.png b/app/src/main/res/drawable-hdpi/ic_campaign.png new file mode 100755 index 000000000..315ec45d3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_campaign.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_campaign.png b/app/src/main/res/drawable-mdpi/ic_campaign.png new file mode 100755 index 000000000..b60884dd6 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_campaign.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_campaign.png b/app/src/main/res/drawable-xhdpi/ic_campaign.png new file mode 100755 index 000000000..8b93f7977 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_campaign.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_campaign.png b/app/src/main/res/drawable-xxhdpi/ic_campaign.png new file mode 100755 index 000000000..069ad8e1e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_campaign.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_campaign.png b/app/src/main/res/drawable-xxxhdpi/ic_campaign.png new file mode 100755 index 000000000..cef03959d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_campaign.png differ diff --git a/app/src/main/res/layout/fragment_contributions.xml b/app/src/main/res/layout/fragment_contributions.xml index dd1959178..5fc1a74dc 100644 --- a/app/src/main/res/layout/fragment_contributions.xml +++ b/app/src/main/res/layout/fragment_contributions.xml @@ -12,11 +12,21 @@ app:cardBackgroundColor="?attr/mainCardBackground" /> + + + android:id="@+id/root_frame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="2dp" + android:background="#000" + > \ No newline at end of file diff --git a/app/src/main/res/layout/layout_campagin.xml b/app/src/main/res/layout/layout_campagin.xml new file mode 100644 index 000000000..47824f5a3 --- /dev/null +++ b/app/src/main/res/layout/layout_campagin.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1631087a6..915c48729 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -433,5 +433,9 @@ Upload your first media by touching the camera or gallery icon above. Never ask this again Display location permission Ask for location permission when needed for nearby notification card view feature. + Ends on: + Display campaigns + Tap here to see the ongoing campaigns + You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e272e02af..39d0f3872 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -47,6 +47,11 @@ android:title="@string/display_location_permission_title" android:defaultValue="true" android:summary="@string/display_location_permission_explanation" /> +