Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support filtering the repositories page list #657

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 65 additions & 0 deletions app/res/layout/item_filter_list.xml
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2012 GitHub Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<EditText
android:id="@+id/et_filter"
style="@style/EditText"
android:layout_width="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_margin="2dp"
android:hint="@string/find_a_repository"
android:inputType="text|textNoSuggestions"
android:padding="6dp"
android:singleLine="true"
android:visibility="gone" />

<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/et_filter">

<ListView
android:id="@android:id/list"
style="@style/ListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />

</android.support.v4.widget.SwipeRefreshLayout>

<TextView
android:id="@android:id/empty"
style="@style/ListSubtitleText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone" />

<ProgressBar
android:id="@+id/pb_loading"
style="@style/Spinner"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_centerInParent="true" />

</RelativeLayout>
1 change: 1 addition & 0 deletions app/res/values/strings.xml
Expand Up @@ -210,6 +210,7 @@
<string name="prefix_created">created\u0020</string>
<string name="prefix_updated">updated\u0020</string>
<string name="prefix_opened">opened\u0020</string>
<string name="find_a_repository">Find a Repository…</string>
<string name="clear">Clear</string>
<string name="pull_request_commits">Commits: %d</string>
<string name="open_issues">Open Issues</string>
Expand Down
115 changes: 115 additions & 0 deletions app/src/main/java/com/github/mobile/ui/ItemFilter.java
@@ -0,0 +1,115 @@
/*
* Copyright 2012 GitHub Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.mobile.ui;

import static java.util.Locale.US;
import android.text.TextUtils;
import android.widget.Filter;

import java.util.ArrayList;
import java.util.List;

/**
* Item filter
*
* @param <V>
*/
public abstract class ItemFilter<V> extends Filter {

private final ItemListAdapter<V, ?> adapter;

/**
* Create item filter
*
* @param adapter
*/
public ItemFilter(ItemListAdapter<V, ?> adapter) {
this.adapter = adapter;
}

/**
* Does the value contain the filter text?
*
* @param filter
* @param value
* @return true if contains, false otherwise
*/
protected boolean contains(final String filter, final String value) {
if (TextUtils.isEmpty(value))
return false;

final int fLength = filter.length();
final int vLength = value.length();
if (fLength > vLength)
return false;

int fIndex = 0;
int vIndex = 0;
while (vIndex < vLength)
if (filter.charAt(fIndex) == Character.toUpperCase(value
.charAt(vIndex))) {
vIndex++;
fIndex++;
if (fIndex == fLength)
return true;
} else {
vIndex += fIndex + 1;
fIndex = 0;
}
return false;
}

/**
* Is item match for prefix?
*
* @param prefix
* @param upperCasePrefix
* @param item
* @return true if match, false otherwise
*/
protected abstract boolean isMatch(CharSequence prefix,
String upperCasePrefix, V item);

@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (TextUtils.isEmpty(prefix))
return results;

final List<V> initial = adapter.getInitialItems();
String upperPrefix = prefix.toString().toUpperCase(US);
List<V> filtered = new ArrayList<V>();
for (V item : initial)
if (isMatch(prefix, upperPrefix, item))
filtered.add(item);

results.values = filtered;
results.count = filtered.size();
return results;
}

@SuppressWarnings("unchecked")
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.values != null)
adapter.setFilteredItems((List<V>) results.values);
else
adapter.setItems(adapter.getInitialItems());

if (results.count == 0)
adapter.notifyDataSetInvalidated();
}
}
172 changes: 172 additions & 0 deletions app/src/main/java/com/github/mobile/ui/ItemListAdapter.java
@@ -0,0 +1,172 @@
/*
* Copyright 2012 GitHub Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.mobile.ui;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;

import java.util.Collections;
import java.util.List;

/**
* List adapter for items of a specific type
*
* @param <I>
* @param <V>
*/
public abstract class ItemListAdapter<I, V extends ItemView> extends
BaseAdapter implements Filterable {

private final LayoutInflater inflater;

private final int viewId;

private List<I> items;

private List<I> initialItems;

/**
* Create empty adapter
*
* @param viewId
* @param inflater
*/
public ItemListAdapter(final int viewId, final LayoutInflater inflater) {
this(viewId, inflater, null);
}

/**
* Create adapter
*
* @param viewId
* @param inflater
* @param items
*/
public ItemListAdapter(final int viewId, final LayoutInflater inflater,
final List<I> items) {
this.viewId = viewId;
this.inflater = inflater;
if (items != null)
this.items = items;
else
this.items = Collections.emptyList();
this.initialItems = this.items;
}

@Override
public boolean hasStableIds() {
return true;
}

/**
* Get items being displayed
*
* @return items
*/
public List<I> getItems() {
return items;
}

public int getCount() {
return items.size();
}

public I getItem(int position) {
return items.get(position);
}

public long getItemId(int position) {
return getItem(position).hashCode();
}

/**
* @return initialItems
*/
protected List<I> getInitialItems() {
return initialItems;
}

/**
* Set items
*
* @param items
* @return this adapter
*/
public ItemListAdapter<I, V> setItems(final List<I> items) {
if (items != null)
this.items = items;
else
this.items = Collections.emptyList();
initialItems = this.items;
notifyDataSetChanged();
return this;
}

/**
* Set filtered items to display
*
* @param items
* @return this adapter
*/
public ItemListAdapter<I, V> setFilteredItems(final List<I> items) {
if (items != null)
this.items = items;
else
this.items = Collections.emptyList();
notifyDataSetChanged();
return this;
}

/**
* Update view to display item
*
* @param position
* @param view
* @param item
*/
protected abstract void update(int position, V view, I item);

/**
* Create empty item view
*
* @param view
* @return item
*/
protected abstract V createView(View view);

@Override
public View getView(final int position, View convertView,
final ViewGroup parent) {
@SuppressWarnings("unchecked")
V view = convertView != null ? (V) convertView.getTag() : null;
if (view == null) {
convertView = inflater.inflate(viewId, null);
view = createView(convertView);
convertView.setTag(view);
}
update(position, view, getItem(position));
return convertView;
}

@Override
public Filter getFilter() {
return null;
}
}