Manage filters - end

This commit is contained in:
stom79 2018-09-06 18:45:43 +02:00
parent c8545a32b7
commit 3f2d1bcd24
5 changed files with 240 additions and 172 deletions

View File

@ -1430,7 +1430,7 @@ public abstract class BaseMainActivity extends BaseActivity
}else{
delete_all.setVisibility(View.VISIBLE);
}
if( id != R.id.nav_list){
if( id != R.id.nav_list && id != R.id.nav_filters){
add_new.setVisibility(View.GONE);
}else{
add_new.setVisibility(View.VISIBLE);

View File

@ -16,7 +16,6 @@ package fr.gouv.etalab.mastodon.client;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
@ -36,6 +35,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.client.Entities.*;
import fr.gouv.etalab.mastodon.client.Entities.Error;
@ -1415,10 +1415,9 @@ public class API {
*/
public APIResponse getFilters(){
List<fr.gouv.etalab.mastodon.client.Entities.Filters> filters = new ArrayList<>();
List<Filters> filters = null;
try {
String response = new HttpsConnection(context).get(getAbsoluteUrl("/filters"), 60, null, prefKeyOauthTokenT);
Log.v(Helper.TAG,"resp: " + response);
filters = parseFilters(new JSONArray(response));
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
@ -1446,7 +1445,6 @@ public class API {
fr.gouv.etalab.mastodon.client.Entities.Filters filter;
try {
String response = new HttpsConnection(context).get(getAbsoluteUrl(String.format("/filters/%s", filterId)), 60, null, prefKeyOauthTokenT);
Log.v(Helper.TAG,"resp: " + response);
filter = parseFilter(new JSONObject(response));
filters.add(filter);
} catch (HttpsConnection.HttpsConnectionException e) {
@ -1472,14 +1470,22 @@ public class API {
*/
public APIResponse addFilters(Filters filter){
HashMap<String, String> params = new HashMap<>();
params.put("custom_filter[phrase]", filter.getPhrase());
params.put("phrase", filter.getPhrase());
StringBuilder parameters = new StringBuilder();
for(String context: filter.getContext())
params.put("custom_filter[context]", context);
params.put("custom_filter[irreversible]", String.valueOf(filter.isIrreversible()));
params.put("custom_filter[whole_word]", String.valueOf(filter.isWhole_word()));
params.put("custom_filter[expires_in]", String.valueOf(filter.getExpires_in()));
parameters.append("context[]=").append(context).append("&");
if( parameters.length() > 0) {
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(10));
params.put("context[]", parameters.toString());
}
params.put("irreversible", String.valueOf(filter.isIrreversible()));
params.put("whole_word", String.valueOf(filter.isWhole_word()));
params.put("expires_in", String.valueOf(filter.getExpires_in()));
ArrayList<Filters> filters = new ArrayList<>();
try {
new HttpsConnection(context).post(getAbsoluteUrl("/filters"), 60, params, prefKeyOauthTokenT);
String response = new HttpsConnection(context).post(getAbsoluteUrl("/filters"), 60, params, prefKeyOauthTokenT);
Filters resfilter = parseFilter(new JSONObject(response));
filters.add(resfilter);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
@ -1488,7 +1494,10 @@ public class API {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse.setFilters(filters);
return apiResponse;
}
@ -1501,7 +1510,7 @@ public class API {
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
new HttpsConnection(context).delete(getAbsoluteUrl(String.format("/filters/%s", filter.getId())), 60, null, prefKeyOauthTokenT);
httpsConnection.delete(getAbsoluteUrl(String.format("/filters/%s", filter.getId())), 60, null, prefKeyOauthTokenT);
actionCode = httpsConnection.getActionCode();
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
@ -1522,14 +1531,22 @@ public class API {
*/
public APIResponse updateFilters(Filters filter){
HashMap<String, String> params = new HashMap<>();
params.put("custom_filter[phrase]", filter.getPhrase());
params.put("phrase", filter.getPhrase());
StringBuilder parameters = new StringBuilder();
for(String context: filter.getContext())
params.put("custom_filter[context]", context);
params.put("custom_filter[irreversible]", String.valueOf(filter.isIrreversible()));
params.put("custom_filter[whole_word]", String.valueOf(filter.isWhole_word()));
params.put("custom_filter[expires_in]", String.valueOf(filter.getExpires_in()));
parameters.append("context[]=").append(context).append("&");
if( parameters.length() > 0) {
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(10));
params.put("context[]", parameters.toString());
}
params.put("irreversible", String.valueOf(filter.isIrreversible()));
params.put("whole_word", String.valueOf(filter.isWhole_word()));
params.put("expires_in", String.valueOf(filter.getExpires_in()));
ArrayList<Filters> filters = new ArrayList<>();
try {
new HttpsConnection(context).put(getAbsoluteUrl(String.format("/filters/%s", filter.getId())), 60, params, prefKeyOauthTokenT);
String response = new HttpsConnection(context).put(getAbsoluteUrl(String.format("/filters/%s", filter.getId())), 60, params, prefKeyOauthTokenT);
Filters resfilter = parseFilter(new JSONObject(response));
filters.add(resfilter);
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
@ -1538,7 +1555,10 @@ public class API {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
apiResponse.setFilters(filters);
return apiResponse;
}
@ -2192,13 +2212,13 @@ public class API {
* @param jsonArray JSONArray
* @return List<Filters> of filters
*/
private List<fr.gouv.etalab.mastodon.client.Entities.Filters> parseFilters(JSONArray jsonArray){
List<fr.gouv.etalab.mastodon.client.Entities.Filters> filters = new ArrayList<>();
private List<Filters> parseFilters(JSONArray jsonArray){
List<Filters> filters = new ArrayList<>();
try {
int i = 0;
while (i < jsonArray.length() ) {
JSONObject resobj = jsonArray.getJSONObject(i);
fr.gouv.etalab.mastodon.client.Entities.Filters filter = parseFilter(resobj);
Filters filter = parseFilter(resobj);
filters.add(filter);
i++;
}
@ -2216,6 +2236,7 @@ public class API {
private fr.gouv.etalab.mastodon.client.Entities.Filters parseFilter(JSONObject resobj){
fr.gouv.etalab.mastodon.client.Entities.Filters filter = new fr.gouv.etalab.mastodon.client.Entities.Filters();
try {
filter.setId(resobj.get("id").toString());
filter.setPhrase(resobj.get("phrase").toString());
filter.setSetExpires_at(Helper.mstStringToDate(context, resobj.get("expires_at").toString()));
filter.setWhole_word(Boolean.parseBoolean(resobj.get("whole_word").toString()));
@ -2233,9 +2254,9 @@ public class API {
filter.setContext(finalContext);
}
}
return filter;
}catch (Exception ignored){ return null;}
}catch (Exception ignored){}
return filter;
}

View File

@ -18,6 +18,7 @@ package fr.gouv.etalab.mastodon.drawers;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
@ -31,12 +32,10 @@ import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Filter;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.etalab.mastodon.R;
@ -44,6 +43,7 @@ import fr.gouv.etalab.mastodon.activities.BaseMainActivity;
import fr.gouv.etalab.mastodon.asynctasks.ManageFiltersAsyncTask;
import fr.gouv.etalab.mastodon.client.APIResponse;
import fr.gouv.etalab.mastodon.client.Entities.Filters;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnFilterActionInterface;
@ -158,14 +158,20 @@ public class FilterAdapter extends BaseAdapter implements OnFilterActionInterfac
add_phrase.setText(filter.getPhrase());
if( filter.getContext() != null)
for(String val: filter.getContext()){
if(val.equals("home"))
context_home.setChecked(true);
else if(val.equals("public"))
context_public.setChecked(true);
else if(val.equals("notifications"))
context_notification.setChecked(true);
else if(val.equals("thread"))
context_conversation.setChecked(true);
switch (val) {
case "home":
context_home.setChecked(true);
break;
case "public":
context_public.setChecked(true);
break;
case "notifications":
context_notification.setChecked(true);
break;
case "thread":
context_conversation.setChecked(true);
break;
}
}
context_whole_word.setChecked(filter.isWhole_word());
context_drop.setChecked(filter.isIrreversible());
@ -175,7 +181,8 @@ public class FilterAdapter extends BaseAdapter implements OnFilterActionInterfac
public void onClick(DialogInterface dialog, int id) {
if( add_phrase.getText() != null && add_phrase.getText().toString().trim().length() > 0 ) {
Filters filter = new Filters();
Filters filterSent = new Filters();
filterSent.setId(filter.getId());
ArrayList<String> contextFilter = new ArrayList<>();
if( context_home.isChecked())
contextFilter.add("home");
@ -185,12 +192,12 @@ public class FilterAdapter extends BaseAdapter implements OnFilterActionInterfac
contextFilter.add("notifications");
if( context_conversation.isChecked())
contextFilter.add("thread");
filter.setContext(contextFilter);
filter.setExpires_in(expire[0]);
filter.setPhrase(add_phrase.getText().toString());
filter.setWhole_word(context_whole_word.isChecked());
filter.setIrreversible(context_drop.isChecked());
new ManageFiltersAsyncTask(context, ManageFiltersAsyncTask.action.UPDATE_FILTER, filter, FilterAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
filterSent.setContext(contextFilter);
filterSent.setExpires_in(expire[0]);
filterSent.setPhrase(add_phrase.getText().toString());
filterSent.setWhole_word(context_whole_word.isChecked());
filterSent.setIrreversible(context_drop.isChecked());
new ManageFiltersAsyncTask(context, ManageFiltersAsyncTask.action.UPDATE_FILTER, filterSent, FilterAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
dialog.dismiss();
}
@ -219,10 +226,9 @@ public class FilterAdapter extends BaseAdapter implements OnFilterActionInterfac
alertDialog.show();
}
});
holder.delete_filter.setOnLongClickListener(new View.OnLongClickListener() {
holder.delete_filter.setOnClickListener(new View.OnClickListener() {
@Override
public boolean onLongClick(View view) {
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(context.getString(R.string.action_filter_delete) );
builder.setMessage(context.getString(R.string.action_lists_confirm_delete) );
@ -245,15 +251,47 @@ public class FilterAdapter extends BaseAdapter implements OnFilterActionInterfac
}
})
.show();
return false;
}
});
return convertView;
}
private Filters getItemAt(int position){
if( filters.size() > position)
return filters.get(position);
else
return null;
}
@Override
public void onActionDone(ManageFiltersAsyncTask.action actionType, APIResponse apiResponse, int statusCode) {
if( apiResponse != null) {
if (apiResponse.getError() != null) {
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean show_error_messages = sharedpreferences.getBoolean(Helper.SET_SHOW_ERROR_MESSAGES, true);
if (show_error_messages)
Toast.makeText(context, apiResponse.getError().getError(), Toast.LENGTH_LONG).show();
}
List<Filters> filtersRes = apiResponse.getFilters();
if (filtersRes != null && filtersRes.size() > 0) {
notifyStatusChanged(filtersRes.get(0));
}
}
}
private void notifyStatusChanged(Filters filter){
for (int i = 0; i < filterAdapter.getCount(); i++) {
//noinspection ConstantConditions
if (filterAdapter.getItemAt(i) != null && filterAdapter.getItemAt(i).getId().equals(filter.getId())) {
filters.set(i, filter);
try {
filterAdapter.notifyDataSetChanged();
} catch (Exception ignored) {
}
return;
}
}
}
private class ViewHolder {

View File

@ -36,11 +36,11 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.activities.MainActivity;
@ -62,10 +62,11 @@ public class DisplayFiltersFragment extends Fragment implements OnFilterActionIn
private Context context;
private AsyncTask<Void, Void, Void> asyncTask;
private List<fr.gouv.etalab.mastodon.client.Entities.Filters> filters;
private TextView no_action_text;
private RelativeLayout mainLoader;
private FloatingActionButton add_new;
private FilterAdapter filterAdapter;
private RelativeLayout textviewNoAction;
private ListView lv_filters;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -77,16 +78,14 @@ public class DisplayFiltersFragment extends Fragment implements OnFilterActionIn
filters = new ArrayList<>();
ListView lv_filters = rootView.findViewById(R.id.lv_filters);
RelativeLayout textviewNoAction = rootView.findViewById(R.id.no_action);
no_action_text = rootView.findViewById(R.id.no_action_text);
lv_filters = rootView.findViewById(R.id.lv_filters);
textviewNoAction = rootView.findViewById(R.id.no_action);
mainLoader = rootView.findViewById(R.id.loader);
RelativeLayout nextElementLoader = rootView.findViewById(R.id.loading_next_items);
mainLoader.setVisibility(View.VISIBLE);
nextElementLoader.setVisibility(View.GONE);
filterAdapter = new FilterAdapter(context, filters, textviewNoAction);
lv_filters.setAdapter(filterAdapter);
no_action_text.setVisibility(View.GONE);
asyncTask = new ManageFiltersAsyncTask(context, ManageFiltersAsyncTask.action.GET_ALL_FILTER, null, DisplayFiltersFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
try {
add_new = ((MainActivity) context).findViewById(R.id.add_new);
@ -108,7 +107,7 @@ public class DisplayFiltersFragment extends Fragment implements OnFilterActionIn
CheckBox context_whole_word = dialogView.findViewById(R.id.context_whole_word);
CheckBox context_drop = dialogView.findViewById(R.id.context_drop);
Spinner filter_expire = dialogView.findViewById(R.id.filter_expire);
ArrayAdapter<CharSequence> adapterResize = ArrayAdapter.createFromResource(getContext(),
ArrayAdapter<CharSequence> adapterResize = ArrayAdapter.createFromResource(Objects.requireNonNull(getContext()),
R.array.filter_expire, android.R.layout.simple_spinner_item);
filter_expire.setAdapter(adapterResize);
final int[] expire = {-1};
@ -230,20 +229,25 @@ public class DisplayFiltersFragment extends Fragment implements OnFilterActionIn
}
if( actionType == ManageFiltersAsyncTask.action.GET_ALL_FILTER) {
if (apiResponse.getFilters() != null && apiResponse.getFilters().size() > 0) {
this.filters.addAll(apiResponse.getFilters());
filterAdapter.notifyDataSetChanged();
textviewNoAction.setVisibility(View.GONE);
lv_filters.setVisibility(View.VISIBLE);
} else {
no_action_text.setVisibility(View.VISIBLE);
textviewNoAction.setVisibility(View.VISIBLE);
lv_filters.setVisibility(View.GONE);
}
}else if( actionType == ManageFiltersAsyncTask.action.CREATE_FILTER){
if (apiResponse.getFilters() != null && apiResponse.getFilters().size() > 0) {
this.filters.add(0, apiResponse.getFilters().get(0));
filterAdapter.notifyDataSetChanged();
textviewNoAction.setVisibility(View.GONE);
lv_filters.setVisibility(View.VISIBLE);
}else{
Toast.makeText(context, apiResponse.getError().getError(),Toast.LENGTH_LONG).show();
Toast.makeText(context, R.string.toast_error,Toast.LENGTH_LONG).show();
}
}
}
}

View File

@ -15,123 +15,128 @@
You should have received a copy of the GNU General Public License along with Mastalab; if not,
see <http://www.gnu.org/licenses>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:text="@string/filter_keyword"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/filter_keyword_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/add_phrase"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
/>
<TextView
android:textSize="12sp"
android:text="@string/filter_context"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/filter_context_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/context_home"
android:layout_width="0dp"
android:text="@string/context_home"
android:layout_weight="1"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/context_public"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/context_public"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/context_notification"
android:layout_width="0dp"
android:text="@string/context_notification"
android:layout_weight="1"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/context_conversation"
android:layout_width="0dp"
android:text="@string/context_conversation"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<CheckBox
android:id="@+id/context_drop"
android:layout_width="match_parent"
android:text="@string/context_drop"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/context_drop_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"/>
<CheckBox
android:id="@+id/context_whole_word"
android:layout_width="match_parent"
android:checked="true"
android:text="@string/context_whole_word"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/context_whole_word_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:padding="10dp"
android:layout_height="wrap_content">
<TextView
android:text="@string/filter_expire"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:text="@string/filter_keyword"
android:layout_height="wrap_content" />
<Spinner
android:layout_marginStart="30dp"
android:id="@+id/filter_expire"
android:layout_width="wrap_content"
<TextView
android:textSize="12sp"
android:text="@string/filter_keyword_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/add_phrase"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp" />
android:maxLines="1"
/>
<TextView
android:textSize="12sp"
android:text="@string/filter_context"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/filter_context_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/context_home"
android:layout_width="0dp"
android:text="@string/context_home"
android:layout_weight="1"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/context_public"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/context_public"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/context_notification"
android:layout_width="0dp"
android:text="@string/context_notification"
android:layout_weight="1"
android:layout_height="wrap_content" />
<CheckBox
android:id="@+id/context_conversation"
android:layout_width="0dp"
android:text="@string/context_conversation"
android:layout_weight="1"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<CheckBox
android:id="@+id/context_drop"
android:layout_width="match_parent"
android:text="@string/context_drop"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/context_drop_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"/>
<CheckBox
android:id="@+id/context_whole_word"
android:layout_width="match_parent"
android:checked="true"
android:text="@string/context_whole_word"
android:layout_height="wrap_content" />
<TextView
android:textSize="12sp"
android:text="@string/context_whole_word_explanations"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginLeft="20dp"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:text="@string/filter_expire"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Spinner
android:layout_marginStart="30dp"
android:id="@+id/filter_expire"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>