Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/PatternsResult.java
public class PatternsResult implements Paging<PatternShort> {

    public List<PatternShort> patterns;
    public Paginator paginator;
    private String etag;

    @Override
    public void setETag(String etag) {
        this.etag = etag;
    }

    @Override
    public String getETag() {
        return this.etag;
    }

    @Override
    public Paginator getPaginator() {
        return paginator;
    }

    @Override
    public List<PatternShort> getItems() {
        return patterns;
    }

    public static PatternsResult emptyResult() {
        PatternsResult patternsResult = new PatternsResult();
        patternsResult.paginator = Paginator.emptyPaginator();
        patternsResult.patterns = Collections.emptyList();
        return patternsResult;
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/components/DeletableTag.java
public class DeletableTag extends TextView {

    public DeletableTag(Context context, String text) {
        super(context);
        setText(text);
        setBackgroundResource(R.drawable.border_delete);
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/requests/AbstractRavelryGetRequest.java
public abstract class AbstractRavelryGetRequest<T extends ETaggable> extends AbstractRavelryRequest<T> {

    public static final long CACHE_DURATION = DurationInMillis.ONE_MINUTE;
    private final GsonObjectPersisterFactory persisterFactory;


    public AbstractRavelryGetRequest(Class<T> clazz, Application application, YarrnPrefs_ prefs) {
        super(clazz, prefs, application);
        try {
            persisterFactory = new GsonObjectPersisterFactory(application);
        } catch (SpiceException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    @Override
    public T loadDataFromNetwork() throws Exception {
        final OAuthRequest request = getRequest();
        ACRA.getErrorReporter().putCustomData("lastUrl", getCacheKey().toString());
        /*T dataFromCache = persisterFactory.createObjectPersister(getResultType()).loadDataFromCache(getCacheKey(), DurationInMillis.ALWAYS_RETURNED);
        if (dataFromCache != null) {
            request.addHeader("If-None-Match", dataFromCache.getETag());
        }*/
        final Response response = executeRequest(request);
        /*if (response.getCode() == 304) {
            return dataFromCache;
        } else {*/
        T result = parseResult(response.getBody());
        result.setETag(response.getHeader("ETag"));
        return result;
        /*}*/
    }


    protected abstract T parseResult(String responseBody);

    protected abstract OAuthRequest getRequest();

    public Object getCacheKey() {
        OAuthRequest request = getRequest();
        return request.getCompleteUrl();
    }

}
Yarrn/src/main/java/de/vanmar/android/yarrn/components/PagingListFragment.java
public abstract class PagingListFragment<RESULT extends Paging<ITEM>, ITEM> extends Fragment {
    protected static final int PAGE_SIZE = 25;
    public SpiceManager spiceManager; // public for testing
    private Paginator paginator;
    private boolean isLoading = false;
    private View listFooter;

    public void afterViews() {
        if (spiceManager == null) {
            spiceManager = new SpiceManager(YarrnSpiceService.class);
        }
        ListView listView = getListView();
        if (listFooter == null) {
            listFooter = getActivity().getLayoutInflater().inflate(R.layout.loading_indicator, listView, false);
            listView.addFooterView(listFooter);
        }
        listView.setOnScrollListener(new ScrollListener());
    }

    protected abstract ListView getListView();

    protected void displayResult(final RESULT result) {
        YarrnAdapter<ITEM> adapter = getAdapter();
        if (result.getPaginator().page == 1) {
            adapter.clear();
        }
        adapter.addAllItems(result.getItems());
    }

    protected abstract YarrnAdapter<ITEM> getAdapter();

    @Override
    public void onStart() {
        super.onStart();
        spiceManager.start(this.getActivity());
    }

    @Override
    public void onResume() {
        super.onResume();
        loadData(1);
    }

    protected void loadData(int page) {
        if (getActivity() == null) {
            return;
        }
        loadingStarted();
        AbstractRavelryGetRequest<RESULT> request = getRequest(page);
        spiceManager.execute(request, request.getCacheKey(), AbstractRavelryGetRequest.CACHE_DURATION, new RavelryResultListener<RESULT>(getRavelryActivity()) {
            @Override
            public void onRequestSuccess(RESULT result) {
                displayResult(result);
                PagingListFragment.this.paginator = result.getPaginator();
                loadingFinished();
            }

            @Override
            public void onRequestFailure(SpiceException spiceException) {
                loadingFinished();
                super.onRequestFailure(spiceException);
            }
        });
    }

    protected abstract IRavelryActivity getRavelryActivity();

    protected abstract AbstractRavelryGetRequest<RESULT> getRequest(int page);

    private void loadingStarted() {
        ((TextView) listFooter.findViewById(R.id.loading_text)).setText(R.string.loading);
        listFooter.findViewById(R.id.loading_progress).setVisibility(View.VISIBLE);
        listFooter.setVisibility(View.VISIBLE);
        isLoading = true;
    }

    private void loadingFinished() {
        ((TextView) listFooter.findViewById(R.id.loading_text)).setText(R.string.load_more);
        listFooter.findViewById(R.id.loading_progress).setVisibility(View.GONE);
        listFooter.setVisibility((paginator != null && paginator.page == paginator.lastPage) ? View.GONE : View.VISIBLE);
        isLoading = false;
    }

    @Override
    public void onStop() {
        if (spiceManager.isStarted()) {
            spiceManager.shouldStop();
        }
        super.onStop();
    }

    public void menuRefresh() {
        loadData(1);
    }

    private class ScrollListener implements AbsListView.OnScrollListener {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            if (!isLoading && firstVisibleItem + visibleItemCount >= totalItemCount) {
                if (paginator != null && paginator.page < paginator.pageCount) {
                    loadData(paginator.page + 1);
                }
            }
        }
    }
}
Yarrn/src/main/java/org/apmem/tools/layouts/FlowLayout.java
public class FlowLayout extends ViewGroup {
    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;
    private int horizontalSpacing = 0;
    private int verticalSpacing = 0;
    private int orientation = 0;
    private boolean debugDraw = false;

    public FlowLayout(Context context) {
        super(context);

        this.readStyleParameters(context, null);
    }

    public FlowLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);

        this.readStyleParameters(context, attributeSet);
    }

    public FlowLayout(Context context, AttributeSet attributeSet, int defStyle) {
        super(context, attributeSet, defStyle);

        this.readStyleParameters(context, attributeSet);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - this.getPaddingRight() - this.getPaddingLeft();
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - this.getPaddingTop() - this.getPaddingBottom();

        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        int size;
        int mode;

        if (orientation == HORIZONTAL) {
            size = sizeWidth;
            mode = modeWidth;
        } else {
            size = sizeHeight;
            mode = modeHeight;
        }

        int lineThicknessWithSpacing = 0;
        int lineThickness = 0;
        int lineLengthWithSpacing = 0;
        int lineLength;

        int prevLinePosition = 0;

        int controlMaxLength = 0;
        int controlMaxThickness = 0;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            LayoutParams lp = (LayoutParams) child.getLayoutParams();

            child.measure(
                    getChildMeasureSpec(widthMeasureSpec, this.getPaddingLeft() + this.getPaddingRight(), lp.width),
                    getChildMeasureSpec(heightMeasureSpec, this.getPaddingTop() + this.getPaddingBottom(), lp.height)
            );

            int hSpacing = this.getHorizontalSpacing(lp);
            int vSpacing = this.getVerticalSpacing(lp);

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            int childLength;
            int childThickness;
            int spacingLength;
            int spacingThickness;

            if (orientation == HORIZONTAL) {
                childLength = childWidth;
                childThickness = childHeight;
                spacingLength = hSpacing;
                spacingThickness = vSpacing;
            } else {
                childLength = childHeight;
                childThickness = childWidth;
                spacingLength = vSpacing;
                spacingThickness = hSpacing;
            }

            lineLength = lineLengthWithSpacing + childLength;
            lineLengthWithSpacing = lineLength + spacingLength;

            boolean newLine = lp.newLine || (mode != MeasureSpec.UNSPECIFIED && lineLength > size);
            if (newLine) {
                prevLinePosition = prevLinePosition + lineThicknessWithSpacing;

                lineThickness = childThickness;
                lineLength = childLength;
                lineThicknessWithSpacing = childThickness + spacingThickness;
                lineLengthWithSpacing = lineLength + spacingLength;
            }

            lineThicknessWithSpacing = Math.max(lineThicknessWithSpacing, childThickness + spacingThickness);
            lineThickness = Math.max(lineThickness, childThickness);

            int posX;
            int posY;
            if (orientation == HORIZONTAL) {
                posX = getPaddingLeft() + lineLength - childLength;
                posY = getPaddingTop() + prevLinePosition;
            } else {
                posX = getPaddingLeft() + prevLinePosition;
                posY = getPaddingTop() + lineLength - childHeight;
            }
            lp.setPosition(posX, posY);

            controlMaxLength = Math.max(controlMaxLength, lineLength);
            controlMaxThickness = prevLinePosition + lineThickness;
        }

        /* need to take paddings into account */
        if (orientation == HORIZONTAL) {
            controlMaxLength += getPaddingLeft() + getPaddingRight();
            controlMaxThickness += getPaddingBottom() + getPaddingTop();
        } else {
            controlMaxLength += getPaddingBottom() + getPaddingTop();
            controlMaxThickness += getPaddingLeft() + getPaddingRight();
        }

        if (orientation == HORIZONTAL) {
            this.setMeasuredDimension(resolveSize(controlMaxLength, widthMeasureSpec), resolveSize(controlMaxThickness, heightMeasureSpec));
        } else {
            this.setMeasuredDimension(resolveSize(controlMaxThickness, widthMeasureSpec), resolveSize(controlMaxLength, heightMeasureSpec));
        }
    }

    private int getVerticalSpacing(LayoutParams lp) {
        int vSpacing;
        if (lp.verticalSpacingSpecified()) {
            vSpacing = lp.verticalSpacing;
        } else {
            vSpacing = this.verticalSpacing;
        }
        return vSpacing;
    }

    private int getHorizontalSpacing(LayoutParams lp) {
        int hSpacing;
        if (lp.horizontalSpacingSpecified()) {
            hSpacing = lp.horizontalSpacing;
        } else {
            hSpacing = this.horizontalSpacing;
        }
        return hSpacing;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            LayoutParams lp = (LayoutParams) child.getLayoutParams();
            child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
        }
    }

    @Override
    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        boolean more = super.drawChild(canvas, child, drawingTime);
        this.drawDebugInfo(canvas, child);
        return more;
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attributeSet) {
        return new LayoutParams(getContext(), attributeSet);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    private void readStyleParameters(Context context, AttributeSet attributeSet) {
        TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout);
        try {
            horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 0);
            verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 0);
            orientation = a.getInteger(R.styleable.FlowLayout_orientation, HORIZONTAL);
            debugDraw = a.getBoolean(R.styleable.FlowLayout_debugDraw, false);
        } finally {
            a.recycle();
        }
    }

    private void drawDebugInfo(Canvas canvas, View child) {
        if (!debugDraw) {
            return;
        }

        Paint childPaint = this.createPaint(0xffffff00);
        Paint layoutPaint = this.createPaint(0xff00ff00);
        Paint newLinePaint = this.createPaint(0xffff0000);

        LayoutParams lp = (LayoutParams) child.getLayoutParams();

        if (lp.horizontalSpacing > 0) {
            float x = child.getRight();
            float y = child.getTop() + child.getHeight() / 2.0f;
            canvas.drawLine(x, y, x + lp.horizontalSpacing, y, childPaint);
            canvas.drawLine(x + lp.horizontalSpacing - 4.0f, y - 4.0f, x + lp.horizontalSpacing, y, childPaint);
            canvas.drawLine(x + lp.horizontalSpacing - 4.0f, y + 4.0f, x + lp.horizontalSpacing, y, childPaint);
        } else if (this.horizontalSpacing > 0) {
            float x = child.getRight();
            float y = child.getTop() + child.getHeight() / 2.0f;
            canvas.drawLine(x, y, x + this.horizontalSpacing, y, layoutPaint);
            canvas.drawLine(x + this.horizontalSpacing - 4.0f, y - 4.0f, x + this.horizontalSpacing, y, layoutPaint);
            canvas.drawLine(x + this.horizontalSpacing - 4.0f, y + 4.0f, x + this.horizontalSpacing, y, layoutPaint);
        }

        if (lp.verticalSpacing > 0) {
            float x = child.getLeft() + child.getWidth() / 2.0f;
            float y = child.getBottom();
            canvas.drawLine(x, y, x, y + lp.verticalSpacing, childPaint);
            canvas.drawLine(x - 4.0f, y + lp.verticalSpacing - 4.0f, x, y + lp.verticalSpacing, childPaint);
            canvas.drawLine(x + 4.0f, y + lp.verticalSpacing - 4.0f, x, y + lp.verticalSpacing, childPaint);
        } else if (this.verticalSpacing > 0) {
            float x = child.getLeft() + child.getWidth() / 2.0f;
            float y = child.getBottom();
            canvas.drawLine(x, y, x, y + this.verticalSpacing, layoutPaint);
            canvas.drawLine(x - 4.0f, y + this.verticalSpacing - 4.0f, x, y + this.verticalSpacing, layoutPaint);
            canvas.drawLine(x + 4.0f, y + this.verticalSpacing - 4.0f, x, y + this.verticalSpacing, layoutPaint);
        }

        if (lp.newLine) {
            if (orientation == HORIZONTAL) {
                float x = child.getLeft();
                float y = child.getTop() + child.getHeight() / 2.0f;
                canvas.drawLine(x, y - 6.0f, x, y + 6.0f, newLinePaint);
            } else {
                float x = child.getLeft() + child.getWidth() / 2.0f;
                float y = child.getTop();
                canvas.drawLine(x - 6.0f, y, x + 6.0f, y, newLinePaint);
            }
        }
    }

    private Paint createPaint(int color) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(color);
        paint.setStrokeWidth(2.0f);
        return paint;
    }

    public static class LayoutParams extends ViewGroup.LayoutParams {
        private static int NO_SPACING = -1;
        private int x;
        private int y;
        private int horizontalSpacing = NO_SPACING;
        private int verticalSpacing = NO_SPACING;
        private boolean newLine = false;

        public LayoutParams(Context context, AttributeSet attributeSet) {
            super(context, attributeSet);
            this.readStyleParameters(context, attributeSet);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams layoutParams) {
            super(layoutParams);
        }

        public boolean horizontalSpacingSpecified() {
            return horizontalSpacing != NO_SPACING;
        }

        public boolean verticalSpacingSpecified() {
            return verticalSpacing != NO_SPACING;
        }

        public void setPosition(int x, int y) {
            this.x = x;
            this.y = y;
        }

        private void readStyleParameters(Context context, AttributeSet attributeSet) {
            TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout_LayoutParams);
            try {
                horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_horizontalSpacing, NO_SPACING);
                verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_verticalSpacing, NO_SPACING);
                newLine = a.getBoolean(R.styleable.FlowLayout_LayoutParams_layout_newLine, false);
            } finally {
                a.recycle();
            }
        }
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/components/SearchCriteria.java
public class SearchCriteria {
    private SearchType type;
    private String name;
    private String value;
    private String description;

    public enum SearchType {
        QUERY, USER, CRAFT
    }

    public enum SearchContext {
        PROJECT, STASH, PATTERN
    }

    public SearchCriteria(SearchType type, String name, String value, String description) {
        this.type = type;
        this.name = name;
        this.value = value;
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public String getValue() {
        return value;
    }

    public String getDescription() {
        return description;
    }

    public SearchType getType() {
        return type;
    }

    public static SearchCriteria byQuery(String queryText) {
        return new SearchCriteria(SearchCriteria.SearchType.QUERY, "query", queryText, "\"" + queryText + "\"");
    }

    public static SearchCriteria byUser(String user, SearchContext context, String description) {
        if (context == SearchContext.PROJECT) {
            return new SearchCriteria(SearchType.USER, "by", user, description);
        } else if (context == SearchContext.PATTERN) {
            return new SearchCriteria(SearchType.USER, "designer", user, description);
        } else if (context == SearchContext.STASH) {
            return new SearchCriteria(SearchType.USER, "user", user, description);
        }
        return null;
    }

    public static SearchCriteria byFriends(String description) {
        return new SearchCriteria(SearchType.USER, "friends", "yes", description);
    }

    public static SearchCriteria byCraft(String craft, String description) {
        return new SearchCriteria(SearchType.CRAFT, "craft", craft, description);
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/components/SearchCriteriaDialog.java
public class SearchCriteriaDialog extends Dialog implements CompoundButton.OnCheckedChangeListener {

    private SearchCriteria searchCriteria = null;
    private YarrnPrefs_ prefs;
    private RadioButton searchByUser;
    private RadioButton searchBySelf;
    private RadioButton searchByFriends;
    private RadioButton searchByAnyone;
    private SearchCriteria.SearchContext searchContext;
    private Spinner searchCriteriaType;
    private ViewAnimator viewAnimator;
    private RadioGroup craft;

    public SearchCriteriaDialog(Context context, SearchCriteria.SearchContext searchContext, YarrnPrefs_ prefs) {
        super(context);
        this.searchContext = searchContext;
        this.prefs = prefs;
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.search_criteria_dialog);

        searchCriteriaType = (Spinner) findViewById(R.id.search_criteria_type);
        List<String> searchCriteriaTypes = getSearchCriteriaTypes();
        ArrayAdapter<String> adapter = new ArrayAdapter(getContext(),
                android.R.layout.simple_spinner_item, searchCriteriaTypes);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        searchCriteriaType.setAdapter(adapter);

        searchCriteriaType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                String selected = getSearchCriteriaType(position);
                for (int i = 0; i < viewAnimator.getChildCount(); i++) {
                    View v = viewAnimator.getChildAt(i);
                    if (selected.equals(v.getTag())) {
                        viewAnimator.setDisplayedChild(i);
                        return;
                    }
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
                //
            }
        });

        viewAnimator = (ViewAnimator) findViewById(R.id.view_animator);

        searchByUser = (RadioButton) findViewById(R.id.search_by_user);
        searchBySelf = (RadioButton) findViewById(R.id.search_by_self);
        searchByFriends = (RadioButton) findViewById(R.id.search_by_friends);
        searchByAnyone = (RadioButton) findViewById(R.id.search_by_anyone);

        craft = (RadioGroup) findViewById(R.id.craft);

        searchByUser.setOnCheckedChangeListener(this);
        searchBySelf.setOnCheckedChangeListener(this);
        searchByFriends.setOnCheckedChangeListener(this);
        searchByAnyone.setOnCheckedChangeListener(this);

        final EditText searchBy = (EditText) findViewById(R.id.search_by);
        searchBy.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                searchByUser.setChecked(true);
            }
        });

        findViewById(R.id.add_search_by).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (searchByUser.isChecked()) {
                    if (searchBy.getText().length() > 0) {
                        // by=<user>
                        String user = searchBy.getText().toString();
                        SearchCriteriaDialog.this.searchCriteria = SearchCriteria.byUser(user, searchContext, getContext().getString(R.string.search_by_user_title) + " " + user);
                        dismiss();
                    }
                } else if (searchByFriends.isChecked()) {
                    // friends=yes
                    SearchCriteriaDialog.this.searchCriteria = SearchCriteria.byFriends(getContext().getString(R.string.search_by_friends_title));
                    dismiss();
                } else if (searchBySelf.isChecked()) {
                    // by:<self>
                    SearchCriteriaDialog.this.searchCriteria = SearchCriteria.byUser(prefs.username().get(), searchContext, getContext().getString(R.string.search_by_self_title));
                    dismiss();
                } else {
                    // <nothing>
                    SearchCriteriaDialog.this.searchCriteria = null;
                    dismiss();
                }
            }
        });

        findViewById(R.id.add_search_craft).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (craft.getCheckedRadioButtonId()) {
                    case R.id.search_craft_knitting:
                        SearchCriteriaDialog.this.searchCriteria = SearchCriteria.byCraft("knitting", getContext().getString(R.string.search_craft_knitting));
                        dismiss();
                        break;
                    case R.id.search_craft_crochet:
                        SearchCriteriaDialog.this.searchCriteria = SearchCriteria.byCraft("crochet", getContext().getString(R.string.search_craft_crochet));
                        dismiss();
                        break;
                    case R.id.search_craft_weaving:
                        SearchCriteriaDialog.this.searchCriteria = SearchCriteria.byCraft("weaving", getContext().getString(R.string.search_craft_weaving));
                        dismiss();
                        break;
                }
            }
        });
    }

    private String getSearchCriteriaType(int position) {
        return searchCriteriaType.getItemAtPosition(position).toString();
    }

    private List<String> getSearchCriteriaTypes() {
        LinkedList<String> searchCriteriaTypes = new LinkedList<String>();
        if (searchContext == SearchCriteria.SearchContext.PROJECT || searchContext == SearchCriteria.SearchContext.STASH) {
            searchCriteriaTypes.add(getContext().getText(R.string.search_criteria_name).toString());
        }
        if (searchContext == SearchCriteria.SearchContext.PROJECT || searchContext == SearchCriteria.SearchContext.PATTERN) {
            searchCriteriaTypes.add(getContext().getText(R.string.search_criteria_craft).toString());
        }
        return searchCriteriaTypes;
    }

    public SearchCriteria getSearchCriteria() {
        return this.searchCriteria;
    }

    public SearchCriteria.SearchType getSearchCriteriaType() {
        String type = getSearchCriteriaType(searchCriteriaType.getSelectedItemPosition());
        if (getContext().getString(R.string.search_criteria_name).equals(type)) {
            return SearchCriteria.SearchType.USER;
        } else if (getContext().getString(R.string.search_criteria_craft).equals(type)) {
            return SearchCriteria.SearchType.CRAFT;
        } else {
            ACRA.getErrorReporter().handleSilentException(new IllegalArgumentException("Unknown SearchCriteriaType: " + type));
            return SearchCriteria.SearchType.USER;
        }
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (isChecked) {
            if (buttonView != searchByUser) {
                searchByUser.setChecked(false);
            }
            if (buttonView != searchBySelf) {
                searchBySelf.setChecked(false);
            }
            if (buttonView != searchByFriends) {
                searchByFriends.setChecked(false);
            }
            if (buttonView != searchByAnyone) {
                searchByAnyone.setChecked(false);
            }
        }
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/IRavelryActivity.java
public interface IRavelryActivity {

    void requestToken();
}
Yarrn/src/main/java/de/vanmar/android/yarrn/ravelry/dts/PatternShort.java
public class PatternShort {
    public int id;
    public String name;
    @SerializedName("first_photo")
    public Photo firstPhoto;
    @SerializedName("designer")
    public PatternAuthor patternAuthor;
}
Yarrn/src/main/java/de/vanmar/android/yarrn/requests/SearchPatternsRequest.java
public class SearchPatternsRequest extends AbstractRavelryGetRequest<PatternsResult> {

    private Collection<SearchCriteria> searchCriteriaList;
    private int page;
    private int pageSize;

    public SearchPatternsRequest(Application application, YarrnPrefs_ prefs, Collection<SearchCriteria> searchCriteriaList, int page, int pageSize) {
        super(PatternsResult.class, application, prefs);
        this.searchCriteriaList = searchCriteriaList;
        this.page = page;
        this.pageSize = pageSize;
    }

    protected PatternsResult parseResult(String responseBody) {
        return new Gson().fromJson(responseBody, PatternsResult.class);
    }

    @Override
    public PatternsResult loadDataFromNetwork() throws Exception {
        if (searchCriteriaList.isEmpty()) {
            return PatternsResult.emptyResult();
        }
        return super.loadDataFromNetwork();
    }

    protected OAuthRequest getRequest() {
        return new OAuthRequest(Verb.GET,
                application.getString(R.string.ravelry_url) + String.format("/patterns/search.json?%spage=%s&page_size=%s",
                        getSearchCriteriaString(), page, pageSize)
        );
    }

    private String getSearchCriteriaString() {
        StringBuilder sb = new StringBuilder();
        for (SearchCriteria searchCriteria : searchCriteriaList) {
            try {
                sb.append(searchCriteria.getName()).append("=").append(URLEncoder.encode(searchCriteria.getValue(), "utf-8")).append("&");
            } catch (UnsupportedEncodingException e) {
                Log.e("SeachPatternsRequest", e.getMessage(), e);
            }
        }
        return sb.toString();
    }
}
Yarrn/src/main/java/de/vanmar/android/yarrn/YarrnAdapter.java
public interface YarrnAdapter<T> {
    void addAllItems(Collection<? extends T> collection);

    void clear();
}


Question: import android.app.Activity;
import android.content.DialogInterface;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.SystemService;
import org.androidannotations.annotations.ViewById;
import org.androidannotations.annotations.sharedpreferences.Pref;
import org.apmem.tools.layouts.FlowLayout;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import de.vanmar.android.yarrn.R;
import de.vanmar.android.yarrn.YarrnAdapter;
import de.vanmar.android.yarrn.YarrnPrefs_;
import de.vanmar.android.yarrn.components.DeletableTag;
import de.vanmar.android.yarrn.components.PagingListFragment;
import de.vanmar.android.yarrn.components.SearchCriteria;
import de.vanmar.android.yarrn.components.SearchCriteriaDialog;
import de.vanmar.android.yarrn.ravelry.IRavelryActivity;
import de.vanmar.android.yarrn.ravelry.dts.PatternShort;
import de.vanmar.android.yarrn.ravelry.dts.PatternsResult;
import de.vanmar.android.yarrn.requests.AbstractRavelryGetRequest;
import de.vanmar.android.yarrn.requests.SearchPatternsRequest;

        };
        if (searchCriteriaView == null) {
            searchCriteriaView = new FlowLayout(getActivity());
            patternlist.addHeaderView(searchCriteriaView);
        }
        patternlist.setAdapter(adapter);

        query.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                if (event != null && event.getAction() != KeyEvent.ACTION_DOWN) {
                    return false;
                }
                if (actionId == EditorInfo.IME_ACTION_SEARCH || event == null
                        || event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                    startSearch();
                }
                return true;
            }
        });
    }

    private void startSearch() {
        adapter.clear();
        inputMethodManager.hideSoftInputFromWindow(
                query.getWindowToken(), 0);
        loadData(1);
    }

    @Override
    public void onResume() {
        super.onResume();
        getActivity().setTitle(R.string.search_patterns_title);
        updateSearchCriteriaDescription();
    }

    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);
        if (activity instanceof PatternSearchFragmentListener) {
            listener = (PatternSearchFragmentListener) activity;
        } else {
            throw new ClassCastException(activity.toString()
                    + " must implement PatternSearchFragmentListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }

    @OptionsItem(R.id.menu_refresh)
    public void menuRefresh() {
        super.menuRefresh();
    }

    @Click(R.id.search_button)
    public void onSearchButtonClicked() {
        startSearch();
    }

    @Click(R.id.add_search_criteria)
    public void onAddSearchCriteriaClicked() {
        final SearchCriteriaDialog searchCriteriaDialog = new SearchCriteriaDialog(getActivity(), SearchCriteria.SearchContext.PATTERN, prefs);
        searchCriteriaDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {
                SearchCriteria.SearchType type = searchCriteriaDialog.getSearchCriteriaType();
                SearchCriteria criteria = searchCriteriaDialog.getSearchCriteria();
                if (criteria == null) {
                    searchCriteria.remove(type);
                } else {
                    searchCriteria.put(type, criteria);
                }
                updateSearchCriteriaDescription();
                startSearch();
            }
        });
        searchCriteriaDialog.show();
    }

    private void updateSearchCriteriaDescription() {
        searchCriteriaView.removeAllViews();
        if (searchCriteria.isEmpty()) {
            searchCriteriaView.setVisibility(View.GONE);
        } else {
            for (final SearchCriteria criteria : searchCriteria.values()) {
                final DeletableTag tag = new DeletableTag(getActivity(), criteria.getDescription());
                tag.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        searchCriteria.remove(criteria.getType());
                        searchCriteriaView.removeView(tag);
                        startSearch();
                    }
                });
                searchCriteriaView.addView(tag);
            }
            searchCriteriaView.setVisibility(View.VISIBLE);
        }
    }

    protected void displayResult(final PatternsResult result) {
        super.displayResult(result);
    }

    @Override
    protected ListView getListView() {
        return patternlist;
    }

    @Override
    protected YarrnAdapter<PatternShort> getAdapter() {
        return adapter;
    }

    @Override
Answer:     protected AbstractRavelryGetRequest<PatternsResult> getRequest(int page) {