Merge remote-tracking branch 'origin/master' into canonicalize
Conflicts: .gitmodules OpenKeychain/build.gradle OpenKeychain/src/main/AndroidManifest.xml OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/ImportKeysListEntry.java OpenKeychain/src/main/res/values/strings.xml settings.gradle
This commit is contained in:
@@ -25,7 +25,7 @@ import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.util.SlidingTabLayout;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
|
||||
public class HelpActivity extends ActionBarActivity {
|
||||
public static final String EXTRA_SELECTED_TAB = "selected_tab";
|
||||
|
||||
@@ -252,7 +252,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
static final int INDEX_HAS_ANY_SECRET = 6;
|
||||
|
||||
static final String ORDER =
|
||||
KeyRings.HAS_ANY_SECRET + " DESC, " + KeyRings.USER_ID + " ASC";
|
||||
KeyRings.HAS_ANY_SECRET + " DESC, UPPER(" + KeyRings.USER_ID + ") ASC";
|
||||
|
||||
|
||||
@Override
|
||||
@@ -592,7 +592,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
|
||||
String headerText = convertView.getResources().getString(R.string.user_id_no_name);
|
||||
if (userId != null && userId.length() > 0) {
|
||||
headerText = "" + userId.subSequence(0, 1).charAt(0);
|
||||
headerText = "" + userId.charAt(0);
|
||||
}
|
||||
holder.mText.setText(headerText);
|
||||
holder.mCount.setVisibility(View.GONE);
|
||||
@@ -621,7 +621,7 @@ public class KeyListFragment extends LoaderFragment
|
||||
// otherwise, return the first character of the name as ID
|
||||
String userId = mCursor.getString(KeyListFragment.INDEX_USER_ID);
|
||||
if (userId != null && userId.length() > 0) {
|
||||
return userId.charAt(0);
|
||||
return Character.toUpperCase(userId.charAt(0));
|
||||
} else {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@@ -32,6 +33,7 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
@@ -47,6 +49,7 @@ import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ContactHelper;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
@@ -54,7 +57,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.SlidingTabLayout;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -92,6 +95,8 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
|
||||
private boolean mShowAdvancedTabs;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -116,16 +121,13 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
|
||||
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout);
|
||||
|
||||
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||
mViewPager.setAdapter(mTabsAdapter);
|
||||
|
||||
int switchToTab = TAB_MAIN;
|
||||
Intent intent = getIntent();
|
||||
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
|
||||
switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
|
||||
}
|
||||
|
||||
Uri dataUri = getIntent().getData();
|
||||
Uri dataUri = getDataUri();
|
||||
if (dataUri == null) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
finish();
|
||||
@@ -136,6 +138,18 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
|
||||
initNfc(dataUri);
|
||||
|
||||
mShowAdvancedTabs = false;
|
||||
|
||||
initTabs(dataUri);
|
||||
|
||||
// switch to tab selected by extra
|
||||
mViewPager.setCurrentItem(switchToTab);
|
||||
}
|
||||
|
||||
private void initTabs(Uri dataUri) {
|
||||
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||
mViewPager.setAdapter(mTabsAdapter);
|
||||
|
||||
Bundle mainBundle = new Bundle();
|
||||
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
|
||||
mTabsAdapter.addTab(ViewKeyMainFragment.class,
|
||||
@@ -146,6 +160,11 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
mTabsAdapter.addTab(ViewKeyShareFragment.class,
|
||||
mainBundle, getString(R.string.key_view_tab_share));
|
||||
|
||||
// update layout after operations
|
||||
mSlidingTabLayout.setViewPager(mViewPager);
|
||||
}
|
||||
|
||||
private void addAdvancedTabs(Uri dataUri) {
|
||||
Bundle keyDetailsBundle = new Bundle();
|
||||
keyDetailsBundle.putParcelable(ViewKeyKeysFragment.ARG_DATA_URI, dataUri);
|
||||
mTabsAdapter.addTab(ViewKeyKeysFragment.class,
|
||||
@@ -156,11 +175,54 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
mTabsAdapter.addTab(ViewKeyCertsFragment.class,
|
||||
certBundle, getString(R.string.key_view_tab_certs));
|
||||
|
||||
// NOTE: must be after adding the tabs!
|
||||
// update layout after operations
|
||||
mSlidingTabLayout.setViewPager(mViewPager);
|
||||
}
|
||||
|
||||
// switch to tab selected by extra
|
||||
mViewPager.setCurrentItem(switchToTab);
|
||||
private void removeAdvancedTabs() {
|
||||
// before removing, switch to the first tab if necessary
|
||||
if (mViewPager.getCurrentItem() >= TAB_KEYS) {
|
||||
// remove _after_ switching to the main tab
|
||||
mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
if (ViewPager.SCROLL_STATE_SETTLING == state) {
|
||||
mTabsAdapter.removeTab(TAB_CERTS);
|
||||
mTabsAdapter.removeTab(TAB_KEYS);
|
||||
|
||||
// update layout after operations
|
||||
mSlidingTabLayout.setViewPager(mViewPager);
|
||||
|
||||
// remove this listener again
|
||||
// mViewPager.setOnPageChangeListener(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mViewPager.setCurrentItem(TAB_MAIN);
|
||||
} else {
|
||||
mTabsAdapter.removeTab(TAB_CERTS);
|
||||
mTabsAdapter.removeTab(TAB_KEYS);
|
||||
}
|
||||
|
||||
// update layout after operations
|
||||
mSlidingTabLayout.setViewPager(mViewPager);
|
||||
}
|
||||
|
||||
private Uri getDataUri() {
|
||||
Uri dataUri = getIntent().getData();
|
||||
if (dataUri != null && dataUri.getHost().equals(ContactsContract.AUTHORITY)) {
|
||||
dataUri = ContactHelper.dataUriFromContactUri(this, dataUri);
|
||||
}
|
||||
return dataUri;
|
||||
}
|
||||
|
||||
private void loadData(Uri dataUri) {
|
||||
@@ -177,6 +239,9 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.key_view, menu);
|
||||
|
||||
MenuItem showAdvancedInfoItem = menu.findItem(R.id.menu_key_view_advanced);
|
||||
showAdvancedInfoItem.setChecked(mShowAdvancedTabs);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -184,24 +249,37 @@ public class ViewKeyActivity extends ActionBarActivity implements
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
try {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
case android.R.id.home: {
|
||||
Intent homeIntent = new Intent(this, KeyListActivity.class);
|
||||
homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(homeIntent);
|
||||
return true;
|
||||
case R.id.menu_key_view_update:
|
||||
}
|
||||
case R.id.menu_key_view_update: {
|
||||
updateFromKeyserver(mDataUri, mProviderHelper);
|
||||
return true;
|
||||
case R.id.menu_key_view_export_keyserver:
|
||||
}
|
||||
case R.id.menu_key_view_export_keyserver: {
|
||||
uploadToKeyserver(mDataUri);
|
||||
return true;
|
||||
case R.id.menu_key_view_export_file:
|
||||
}
|
||||
case R.id.menu_key_view_export_file: {
|
||||
exportToFile(mDataUri, mExportHelper, mProviderHelper);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_delete: {
|
||||
deleteKey(mDataUri, mExportHelper);
|
||||
return true;
|
||||
}
|
||||
case R.id.menu_key_view_advanced: {
|
||||
mShowAdvancedTabs = !mShowAdvancedTabs;
|
||||
item.setChecked(mShowAdvancedTabs);
|
||||
if (mShowAdvancedTabs) {
|
||||
addAdvancedTabs(mDataUri);
|
||||
} else {
|
||||
removeAdvancedTabs();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ProviderHelper.NotFoundException e) {
|
||||
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
|
||||
|
||||
@@ -33,7 +33,6 @@ import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.util.Highlighter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -120,13 +119,13 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
}
|
||||
|
||||
// main user id
|
||||
String userId = entry.userIds.get(0);
|
||||
String userId = entry.getUserIds().get(0);
|
||||
String[] userIdSplit = KeyRing.splitUserId(userId);
|
||||
|
||||
// name
|
||||
if (userIdSplit[0] != null) {
|
||||
// show red user id if it is a secret key
|
||||
if (entry.secretKey) {
|
||||
if (entry.isSecretKey()) {
|
||||
holder.mainUserId.setText(mActivity.getString(R.string.secret_key)
|
||||
+ " " + userIdSplit[0]);
|
||||
holder.mainUserId.setTextColor(Color.RED);
|
||||
@@ -147,30 +146,26 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
holder.mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.keyId.setText(entry.keyIdHex);
|
||||
holder.keyId.setText(entry.getKeyIdHex());
|
||||
|
||||
if (entry.fingerprintHex != null) {
|
||||
holder.fingerprint.setVisibility(View.VISIBLE);
|
||||
holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerprintHex));
|
||||
} else {
|
||||
holder.fingerprint.setVisibility(View.GONE);
|
||||
}
|
||||
// don't show full fingerprint on key import
|
||||
holder.fingerprint.setVisibility(View.GONE);
|
||||
|
||||
if (entry.bitStrength != 0 && entry.algorithm != null) {
|
||||
holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
|
||||
if (entry.getBitStrength() != 0 && entry.getAlgorithm() != null) {
|
||||
holder.algorithm.setText("" + entry.getBitStrength() + "/" + entry.getAlgorithm());
|
||||
holder.algorithm.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.algorithm.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
if (entry.revoked) {
|
||||
if (entry.isRevoked()) {
|
||||
holder.status.setVisibility(View.VISIBLE);
|
||||
holder.status.setText(R.string.revoked);
|
||||
} else {
|
||||
holder.status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (entry.userIds.size() == 1) {
|
||||
if (entry.getUserIds().size() == 1) {
|
||||
holder.userIdsList.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.userIdsList.setVisibility(View.VISIBLE);
|
||||
@@ -178,7 +173,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
// clear view from holder
|
||||
holder.userIdsList.removeAllViews();
|
||||
|
||||
Iterator<String> it = entry.userIds.iterator();
|
||||
Iterator<String> it = entry.getUserIds().iterator();
|
||||
// skip primary user id
|
||||
it.next();
|
||||
while (it.hasNext()) {
|
||||
|
||||
@@ -20,8 +20,13 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -52,6 +57,11 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void removeTab(int index) {
|
||||
mTabs.remove(index);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTabs.size();
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* 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 org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.support.v4.view.PagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Copied from http://developer.android.com/samples/SlidingTabsColors/index.html
|
||||
*/
|
||||
|
||||
/**
|
||||
* To be used with ViewPager to provide a tab indicator component which give constant feedback as to
|
||||
* the user's scroll progress.
|
||||
* <p/>
|
||||
* To use the component, simply add it to your view hierarchy. Then in your
|
||||
* {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call
|
||||
* {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is being used for.
|
||||
* <p/>
|
||||
* The colors can be customized in two ways. The first and simplest is to provide an array of colors
|
||||
* via {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)}. The
|
||||
* alternative is via the {@link TabColorizer} interface which provides you complete control over
|
||||
* which color is used for any individual position.
|
||||
* <p/>
|
||||
* The views used as tabs can be customized by calling {@link #setCustomTabView(int, int)},
|
||||
* providing the layout ID of your custom layout.
|
||||
*/
|
||||
public class SlidingTabLayout extends HorizontalScrollView {
|
||||
|
||||
/**
|
||||
* Allows complete control over the colors drawn in the tab layout. Set with
|
||||
* {@link #setCustomTabColorizer(TabColorizer)}.
|
||||
*/
|
||||
public interface TabColorizer {
|
||||
|
||||
/**
|
||||
* @return return the color of the indicator used when {@code position} is selected.
|
||||
*/
|
||||
int getIndicatorColor(int position);
|
||||
|
||||
/**
|
||||
* @return return the color of the divider drawn to the right of {@code position}.
|
||||
*/
|
||||
int getDividerColor(int position);
|
||||
|
||||
}
|
||||
|
||||
private static final int TITLE_OFFSET_DIPS = 24;
|
||||
private static final int TAB_VIEW_PADDING_DIPS = 16;
|
||||
private static final int TAB_VIEW_TEXT_SIZE_SP = 12;
|
||||
|
||||
private int mTitleOffset;
|
||||
|
||||
private int mTabViewLayoutId;
|
||||
private int mTabViewTextViewId;
|
||||
|
||||
private ViewPager mViewPager;
|
||||
private ViewPager.OnPageChangeListener mViewPagerPageChangeListener;
|
||||
|
||||
private final SlidingTabStrip mTabStrip;
|
||||
|
||||
public SlidingTabLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SlidingTabLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
// Disable the Scroll Bar
|
||||
setHorizontalScrollBarEnabled(false);
|
||||
// Make sure that the Tab Strips fills this View
|
||||
setFillViewport(true);
|
||||
|
||||
mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
|
||||
|
||||
mTabStrip = new SlidingTabStrip(context);
|
||||
addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom {@link TabColorizer} to be used.
|
||||
* <p/>
|
||||
* If you only require simple custmisation then you can use
|
||||
* {@link #setSelectedIndicatorColors(int...)} and {@link #setDividerColors(int...)} to achieve
|
||||
* similar effects.
|
||||
*/
|
||||
public void setCustomTabColorizer(TabColorizer tabColorizer) {
|
||||
mTabStrip.setCustomTabColorizer(tabColorizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the colors to be used for indicating the selected tab. These colors are treated as a
|
||||
* circular array. Providing one color will mean that all tabs are indicated with the same color.
|
||||
*/
|
||||
public void setSelectedIndicatorColors(int... colors) {
|
||||
mTabStrip.setSelectedIndicatorColors(colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the colors to be used for tab dividers. These colors are treated as a circular array.
|
||||
* Providing one color will mean that all tabs are indicated with the same color.
|
||||
*/
|
||||
public void setDividerColors(int... colors) {
|
||||
mTabStrip.setDividerColors(colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ViewPager.OnPageChangeListener}. When using {@link SlidingTabLayout} you are
|
||||
* required to set any {@link ViewPager.OnPageChangeListener} through this method. This is so
|
||||
* that the layout can update it's scroll position correctly.
|
||||
*
|
||||
* @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener)
|
||||
*/
|
||||
public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
|
||||
mViewPagerPageChangeListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom layout to be inflated for the tab views.
|
||||
*
|
||||
* @param layoutResId Layout id to be inflated
|
||||
* @param textViewId id of the {@link TextView} in the inflated view
|
||||
*/
|
||||
public void setCustomTabView(int layoutResId, int textViewId) {
|
||||
mTabViewLayoutId = layoutResId;
|
||||
mTabViewTextViewId = textViewId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the associated view pager. Note that the assumption here is that the pager content
|
||||
* (number of tabs and tab titles) does not change after this call has been made.
|
||||
*/
|
||||
public void setViewPager(ViewPager viewPager) {
|
||||
mTabStrip.removeAllViews();
|
||||
|
||||
mViewPager = viewPager;
|
||||
if (viewPager != null) {
|
||||
viewPager.setOnPageChangeListener(new InternalViewPagerListener());
|
||||
populateTabStrip();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default view to be used for tabs. This is called if a custom tab view is not set via
|
||||
* {@link #setCustomTabView(int, int)}.
|
||||
*/
|
||||
protected TextView createDefaultTabView(Context context) {
|
||||
TextView textView = new TextView(context);
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);
|
||||
textView.setTypeface(Typeface.DEFAULT_BOLD);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
// If we're running on Honeycomb or newer, then we can use the Theme's
|
||||
// selectableItemBackground to ensure that the View has a pressed state
|
||||
TypedValue outValue = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
|
||||
outValue, true);
|
||||
textView.setBackgroundResource(outValue.resourceId);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
// If we're running on ICS or newer, enable all-caps to match the Action Bar tab style
|
||||
textView.setAllCaps(true);
|
||||
}
|
||||
|
||||
int padding = (int) (TAB_VIEW_PADDING_DIPS * getResources().getDisplayMetrics().density);
|
||||
textView.setPadding(padding, padding, padding, padding);
|
||||
|
||||
return textView;
|
||||
}
|
||||
|
||||
private void populateTabStrip() {
|
||||
final PagerAdapter adapter = mViewPager.getAdapter();
|
||||
final View.OnClickListener tabClickListener = new TabClickListener();
|
||||
|
||||
for (int i = 0; i < adapter.getCount(); i++) {
|
||||
View tabView = null;
|
||||
TextView tabTitleView = null;
|
||||
|
||||
if (mTabViewLayoutId != 0) {
|
||||
// If there is a custom tab view layout id set, try and inflate it
|
||||
tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
|
||||
false);
|
||||
tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
|
||||
}
|
||||
|
||||
if (tabView == null) {
|
||||
tabView = createDefaultTabView(getContext());
|
||||
}
|
||||
|
||||
if (tabTitleView == null && TextView.class.isInstance(tabView)) {
|
||||
tabTitleView = (TextView) tabView;
|
||||
}
|
||||
|
||||
tabTitleView.setText(adapter.getPageTitle(i));
|
||||
tabView.setOnClickListener(tabClickListener);
|
||||
|
||||
mTabStrip.addView(tabView);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
if (mViewPager != null) {
|
||||
scrollToTab(mViewPager.getCurrentItem(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollToTab(int tabIndex, int positionOffset) {
|
||||
final int tabStripChildCount = mTabStrip.getChildCount();
|
||||
if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
View selectedChild = mTabStrip.getChildAt(tabIndex);
|
||||
if (selectedChild != null) {
|
||||
int targetScrollX = selectedChild.getLeft() + positionOffset;
|
||||
|
||||
if (tabIndex > 0 || positionOffset > 0) {
|
||||
// If we're not at the first child and are mid-scroll, make sure we obey the offset
|
||||
targetScrollX -= mTitleOffset;
|
||||
}
|
||||
|
||||
scrollTo(targetScrollX, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
|
||||
private int mScrollState;
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
int tabStripChildCount = mTabStrip.getChildCount();
|
||||
if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTabStrip.onViewPagerPageChanged(position, positionOffset);
|
||||
|
||||
View selectedTitle = mTabStrip.getChildAt(position);
|
||||
int extraOffset = (selectedTitle != null)
|
||||
? (int) (positionOffset * selectedTitle.getWidth())
|
||||
: 0;
|
||||
scrollToTab(position, extraOffset);
|
||||
|
||||
if (mViewPagerPageChangeListener != null) {
|
||||
mViewPagerPageChangeListener.onPageScrolled(position, positionOffset,
|
||||
positionOffsetPixels);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
mScrollState = state;
|
||||
|
||||
if (mViewPagerPageChangeListener != null) {
|
||||
mViewPagerPageChangeListener.onPageScrollStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
|
||||
mTabStrip.onViewPagerPageChanged(position, 0f);
|
||||
scrollToTab(position, 0);
|
||||
}
|
||||
|
||||
if (mViewPagerPageChangeListener != null) {
|
||||
mViewPagerPageChangeListener.onPageSelected(position);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TabClickListener implements View.OnClickListener {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
|
||||
if (v == mTabStrip.getChildAt(i)) {
|
||||
mViewPager.setCurrentItem(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* 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 org.sufficientlysecure.keychain.ui.widget;
|
||||
|
||||
import android.R;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* Copied from http://developer.android.com/samples/SlidingTabsColors/index.html
|
||||
*/
|
||||
class SlidingTabStrip extends LinearLayout {
|
||||
|
||||
private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 2;
|
||||
private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
|
||||
private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 8;
|
||||
private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFFAA66CC;
|
||||
|
||||
private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
|
||||
private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
|
||||
private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
|
||||
|
||||
private final int mBottomBorderThickness;
|
||||
private final Paint mBottomBorderPaint;
|
||||
|
||||
private final int mSelectedIndicatorThickness;
|
||||
private final Paint mSelectedIndicatorPaint;
|
||||
|
||||
private final int mDefaultBottomBorderColor;
|
||||
|
||||
private final Paint mDividerPaint;
|
||||
private final float mDividerHeight;
|
||||
|
||||
private int mSelectedPosition;
|
||||
private float mSelectionOffset;
|
||||
|
||||
private SlidingTabLayout.TabColorizer mCustomTabColorizer;
|
||||
private final SimpleTabColorizer mDefaultTabColorizer;
|
||||
|
||||
SlidingTabStrip(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
SlidingTabStrip(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setWillNotDraw(false);
|
||||
|
||||
final float density = getResources().getDisplayMetrics().density;
|
||||
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.colorForeground, outValue, true);
|
||||
final int themeForegroundColor = outValue.data;
|
||||
|
||||
mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor,
|
||||
DEFAULT_BOTTOM_BORDER_COLOR_ALPHA);
|
||||
|
||||
mDefaultTabColorizer = new SimpleTabColorizer();
|
||||
mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR);
|
||||
mDefaultTabColorizer.setDividerColors(setColorAlpha(themeForegroundColor,
|
||||
DEFAULT_DIVIDER_COLOR_ALPHA));
|
||||
|
||||
mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
|
||||
mBottomBorderPaint = new Paint();
|
||||
mBottomBorderPaint.setColor(mDefaultBottomBorderColor);
|
||||
|
||||
mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
|
||||
mSelectedIndicatorPaint = new Paint();
|
||||
|
||||
mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
|
||||
mDividerPaint = new Paint();
|
||||
mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
|
||||
}
|
||||
|
||||
void setCustomTabColorizer(SlidingTabLayout.TabColorizer customTabColorizer) {
|
||||
mCustomTabColorizer = customTabColorizer;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void setSelectedIndicatorColors(int... colors) {
|
||||
// Make sure that the custom colorizer is removed
|
||||
mCustomTabColorizer = null;
|
||||
mDefaultTabColorizer.setIndicatorColors(colors);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void setDividerColors(int... colors) {
|
||||
// Make sure that the custom colorizer is removed
|
||||
mCustomTabColorizer = null;
|
||||
mDefaultTabColorizer.setDividerColors(colors);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void onViewPagerPageChanged(int position, float positionOffset) {
|
||||
mSelectedPosition = position;
|
||||
mSelectionOffset = positionOffset;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
final int height = getHeight();
|
||||
final int childCount = getChildCount();
|
||||
final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
|
||||
final SlidingTabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null
|
||||
? mCustomTabColorizer
|
||||
: mDefaultTabColorizer;
|
||||
|
||||
// Thick colored underline below the current selection
|
||||
if (childCount > 0) {
|
||||
View selectedTitle = getChildAt(mSelectedPosition);
|
||||
int left = selectedTitle.getLeft();
|
||||
int right = selectedTitle.getRight();
|
||||
int color = tabColorizer.getIndicatorColor(mSelectedPosition);
|
||||
|
||||
if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
|
||||
int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
|
||||
if (color != nextColor) {
|
||||
color = blendColors(nextColor, color, mSelectionOffset);
|
||||
}
|
||||
|
||||
// Draw the selection partway between the tabs
|
||||
View nextTitle = getChildAt(mSelectedPosition + 1);
|
||||
left = (int) (mSelectionOffset * nextTitle.getLeft() +
|
||||
(1.0f - mSelectionOffset) * left);
|
||||
right = (int) (mSelectionOffset * nextTitle.getRight() +
|
||||
(1.0f - mSelectionOffset) * right);
|
||||
}
|
||||
|
||||
mSelectedIndicatorPaint.setColor(color);
|
||||
|
||||
canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
|
||||
height, mSelectedIndicatorPaint);
|
||||
}
|
||||
|
||||
// Thin underline along the entire bottom edge
|
||||
canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
|
||||
|
||||
// Vertical separators between the titles
|
||||
int separatorTop = (height - dividerHeightPx) / 2;
|
||||
for (int i = 0; i < childCount - 1; i++) {
|
||||
View child = getChildAt(i);
|
||||
mDividerPaint.setColor(tabColorizer.getDividerColor(i));
|
||||
canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
|
||||
separatorTop + dividerHeightPx, mDividerPaint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha value of the {@code color} to be the given {@code alpha} value.
|
||||
*/
|
||||
private static int setColorAlpha(int color, byte alpha) {
|
||||
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* Blend {@code color1} and {@code color2} using the given ratio.
|
||||
*
|
||||
* @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend,
|
||||
* 0.0 will return {@code color2}.
|
||||
*/
|
||||
private static int blendColors(int color1, int color2, float ratio) {
|
||||
final float inverseRation = 1f - ratio;
|
||||
float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
|
||||
float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
|
||||
float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
|
||||
return Color.rgb((int) r, (int) g, (int) b);
|
||||
}
|
||||
|
||||
private static class SimpleTabColorizer implements SlidingTabLayout.TabColorizer {
|
||||
private int[] mIndicatorColors;
|
||||
private int[] mDividerColors;
|
||||
|
||||
@Override
|
||||
public final int getIndicatorColor(int position) {
|
||||
return mIndicatorColors[position % mIndicatorColors.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getDividerColor(int position) {
|
||||
return mDividerColors[position % mDividerColors.length];
|
||||
}
|
||||
|
||||
void setIndicatorColors(int... colors) {
|
||||
mIndicatorColors = colors;
|
||||
}
|
||||
|
||||
void setDividerColors(int... colors) {
|
||||
mDividerColors = colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user