Loading res/menu/cities_menu.xml +3 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ android:actionViewClass="android.widget.SearchView" android:imeOptions="actionSearch" android:orderInCategory="1" /> <item android:id="@+id/menu_item_sort" android:title="@string/menu_item_sort_by_gmt_offset" android:showAsAction="never" /> <item android:id="@+id/menu_item_settings" android:title="@string/menu_item_settings" android:icon="@android:drawable/ic_menu_preferences" Loading res/values/dimens.xml +1 −1 Original line number Diff line number Diff line Loading @@ -77,7 +77,7 @@ <dimen name="dialpad_font_size">48sp</dimen> <dimen name="dialpad_font_size_ampm">24sp</dimen> <dimen name="city_name_font_size">24sp</dimen> <dimen name="city_time_font_size">14sp</dimen> <dimen name="city_time_font_size">18sp</dimen> <dimen name="tablet_dialpad_font_size">60sp</dimen> <dimen name="tablet_dialpad_font_size_ampm">30sp</dimen> Loading res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -329,6 +329,10 @@ <string name="menu_item_help">Help</string> <!-- Menu item on clock screen to enter night mode. --> <string name="menu_item_night_mode">Night mode</string> <!-- Menu item on Cities screen to sort by GMT offset --> <string name="menu_item_sort_by_gmt_offset">Sort by time</string> <!-- Menu item on Cities screen to sort by alphabetical order --> <string name="menu_item_sort_by_name">Sort by name</string> <!-- Stop Watch strings --> <!-- Describes the purpose of the button to resume running a stopwatch --> Loading src/com/android/deskclock/Utils.java +31 −13 Original line number Diff line number Diff line Loading @@ -38,8 +38,11 @@ import android.os.Handler; import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.Settings; import android.text.Spannable; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.style.ForegroundColorSpan; import android.view.MenuItem; import android.view.View; import android.view.animation.AccelerateInterpolator; Loading @@ -50,13 +53,11 @@ import com.android.deskclock.stopwatch.Stopwatches; import com.android.deskclock.timer.Timers; import com.android.deskclock.worldclock.CityObj; import java.text.Collator; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.Locale; import java.util.TimeZone; public class Utils { Loading Loading @@ -437,8 +438,7 @@ public class Utils { } } public static CityObj[] loadCitiesDataBase(Context c) { final Collator collator = Collator.getInstance(); public static CityObj[] loadCitiesFromXml(Context c) { Resources r = c.getResources(); // Read strings array of name,timezone, id // make sure the list are the same length Loading @@ -453,17 +453,35 @@ public class Utils { for (int i = 0; i < cities.length; i++) { tempList[i] = new CityObj(cities[i], timezones[i], ids[i]); } // Sort alphabetically Arrays.sort(tempList, new Comparator<CityObj> () { @Override public int compare(CityObj c1, CityObj c2) { Comparator<CityObj> mCollator; return collator.compare(c1.mCityName, c2.mCityName); } }); return tempList; } /** * Returns string denoting the timezone hour offset (e.g. GMT-8:00) */ public static String getGMTHourOffset(TimeZone timezone, boolean showMinutes) { StringBuilder sb = new StringBuilder(); sb.append("GMT"); int gmtOffset = timezone.getRawOffset(); if (gmtOffset < 0) { sb.append('-'); } else { sb.append('+'); } sb.append(Math.abs(gmtOffset) / DateUtils.HOUR_IN_MILLIS); // Hour if (showMinutes) { final int min = (Math.abs(gmtOffset) / (int) DateUtils.MINUTE_IN_MILLIS) % 60; sb.append(':'); if (min < 10) { sb.append('0'); } sb.append(min); } return sb.toString(); } public static String getCityName(CityObj city, CityObj dbCity) { return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName; } Loading src/com/android/deskclock/worldclock/CitiesActivity.java +187 −70 Original line number Diff line number Diff line Loading @@ -18,16 +18,14 @@ package com.android.deskclock.worldclock; import android.app.ActionBar; import android.app.Activity; import android.app.SearchableInfo; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; Loading @@ -54,8 +52,10 @@ import com.android.deskclock.SettingsActivity; import com.android.deskclock.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.TimeZone; /** Loading @@ -66,6 +66,12 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, private static final String KEY_SEARCH_QUERY = "search_query"; private static final String KEY_SEARCH_MODE = "search_mode"; private static final String KEY_LIST_POSITION = "list_position"; private static final String PREF_SORT = "sort_preference"; private static final int SORT_BY_NAME = 0; private static final int SORT_BY_GMT_OFFSET = 1; /** * This must be false for production. If true, turns on logging, test code, Loading @@ -80,28 +86,35 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, private HashMap<String, CityObj> mUserSelectedCities; private Calendar mCalendar; private MenuItem mSearchMenu; private SearchView mSearchView; private StringBuffer mQueryTextBuffer = new StringBuffer(); private boolean mSearchMode; private int mPosition = -1; private SharedPreferences mPrefs; private int mSortType; /*** * Adapter for a list of cities with the respected time zone. The Adapter * sorts the list alphabetically and create an indexer. ***/ private class CityAdapter extends BaseAdapter implements Filterable, SectionIndexer { private static final String DELETED_ENTRY = "C0"; private ArrayList<CityObj> mAllTheCitiesList; // full list of the cities private final HashMap<String, CityObj> mSelectedCitiesList; // selected cities private List<CityObj> mDisplayedCitiesList; private CityObj[] mCities; private String[] mSortByNameSectionHeaders; private Integer[] mSortByNameSectionPositions; private String[] mSortByTimeSectionHeaders; private Integer[] mSortByTimeSectionPositions; private ArrayList<CityObj> mDisplayedCitiesList; private final int mSpanColor; private CityNameComparator mSortByNameComparator = new CityNameComparator(); private CityGmtOffsetComparator mSortByGmtOffsetComparator = new CityGmtOffsetComparator(); private final LayoutInflater mInflater; private boolean mIs24HoursMode; // AM/PM or 24 hours mode private Object[] mSectionHeaders; private Object[] mSectionPositions; private Filter mFilter = new Filter() { Loading @@ -110,18 +123,51 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, FilterResults results = new FilterResults(); String modifiedQuery = constraint.toString().trim().toUpperCase(); ArrayList<CityObj> filteredList = new ArrayList<CityObj>(); int positionIndex = 0; int i = 0; while (i < mCities.length) { CityObj city = mCities[i]; // If the city is a deleted entry, ignore it. if (city.mCityId.equals(DELETED_ENTRY)) { i++; continue; } // If the search query is empty, add section headers if (TextUtils.isEmpty(modifiedQuery)) { results.values = mAllTheCitiesList; results.count = mAllTheCitiesList.size(); return results; // If the list is sorted by name, and the position index is correct, // insert a section header into the list if (mSortType == SORT_BY_NAME && mSortByNameSectionPositions.length > positionIndex && mSortByNameSectionPositions[positionIndex] == filteredList.size()) { String name = mSortByNameSectionHeaders[positionIndex]; filteredList.add(new CityObj(name, null, null)); positionIndex++; continue; } ArrayList<CityObj> filteredList = new ArrayList<CityObj>(); for (CityObj city : mAllTheCitiesList) { // If the list is sorted by time, and the position index is correct, // insert a section header into the list if (mSortType == SORT_BY_GMT_OFFSET && mSortByTimeSectionPositions.length > positionIndex && mSortByTimeSectionPositions[positionIndex] == filteredList.size()) { String timezone = mSortByTimeSectionHeaders[positionIndex]; filteredList.add(new CityObj(null, timezone, null)); positionIndex++; continue; } } // If the city name begins with the query, add the city into the list. // If the query is empty, the city will automatically be added to the list. String cityName = city.mCityName.trim().toUpperCase(); if (city.mCityId != null && cityName.startsWith(modifiedQuery)) { filteredList.add(city); } i++; } results.values = filteredList; results.count = filteredList.size(); Loading @@ -131,6 +177,10 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, @Override protected void publishResults(CharSequence constraint, FilterResults results) { mDisplayedCitiesList = (ArrayList<CityObj>) results.values; if (mPosition >= 0) { mCitiesList.setSelectionFromTop(mPosition, 0); mPosition = -1; } notifyDataSetChanged(); } }; Loading @@ -138,15 +188,88 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, public CityAdapter( Context context, HashMap<String, CityObj> selectedList, LayoutInflater factory) { super(); loadCitiesDataBase(context); loadCities(context); mSelectedCitiesList = selectedList; mInflater = factory; mCalendar = Calendar.getInstance(); mCalendar.setTimeInMillis(System.currentTimeMillis()); mSpanColor = context.getResources().getColor(R.color.clock_blue); set24HoursMode(context); } private void loadCities(Context c) { mCities = Utils.loadCitiesFromXml(c); if (mCities == null) { return; } // Sort alphabetically and populate section headers for sort-by-name Arrays.sort(mCities, mSortByNameComparator); String val = null; ArrayList<String> sections = new ArrayList<String>(); ArrayList<Integer> positions = new ArrayList<Integer>(); int count = 0; for (CityObj city : mCities) { if (city.mCityId.equals(DELETED_ENTRY)) { continue; } if (!city.mCityName.substring(0, 1).equals(val)) { val = city.mCityName.substring(0, 1).toUpperCase(); sections.add(val); positions.add(count); count++; } count++; } mSortByNameSectionHeaders = sections.toArray(new String[sections.size()]); mSortByNameSectionPositions = positions.toArray(new Integer[positions.size()]); // Sort by GMT offset and populate section headers for sort-by-time Arrays.sort(mCities, mSortByGmtOffsetComparator); int offset = -100000; // some number that cannot be a real offset val = null; sections.clear(); positions.clear(); ArrayList<String> scrollLabels = new ArrayList<String>(); count = 0; for (CityObj city : mCities) { if (city.mCityId.equals(DELETED_ENTRY)) { continue; } TimeZone timezone = TimeZone.getTimeZone(city.mTimeZone); int newOffset = timezone.getRawOffset(); if (newOffset != offset) { offset = newOffset; sections.add(Utils.getGMTHourOffset(timezone, true)); positions.add(count); count++; } count++; } mSortByTimeSectionHeaders = sections.toArray(new String[sections.size()]); mSortByTimeSectionPositions = positions.toArray(new Integer[positions.size()]); sortCities(mSortType); } public void toggleSort() { if (mSortType == SORT_BY_NAME) { sortCities(SORT_BY_GMT_OFFSET); } else { sortCities(SORT_BY_NAME); } } private void sortCities(final int sortType) { mSortType = sortType; Arrays.sort(mCities, sortType == SORT_BY_NAME ? mSortByNameComparator : mSortByGmtOffsetComparator); mPrefs.edit().putInt(PREF_SORT, sortType).commit(); mFilter.filter(mQueryTextBuffer.toString()); } @Override public int getCount() { return (mDisplayedCitiesList != null) ? mDisplayedCitiesList.size() : 0; Loading Loading @@ -183,7 +306,7 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, view = mInflater.inflate(R.layout.city_list_header, parent, false); } TextView header = (TextView) view.findViewById(R.id.header); header.setText(c.mCityName); header.setText(mSortType == SORT_BY_NAME ? c.mCityName : c.mTimeZone); } else { // City view // Make sure to recycle a City view only if (view == null || view.findViewById(R.id.city_name) == null) { Loading @@ -208,55 +331,27 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, notifyDataSetChanged(); } private void loadCitiesDataBase(Context c) { CityObj[] tempList = Utils.loadCitiesDataBase(c); if (tempList == null) { return; } // Create section indexer and add headers to the cities list String val = null; ArrayList<String> sections = new ArrayList<String>(); ArrayList<Integer> positions = new ArrayList<Integer>(); ArrayList<CityObj> items = new ArrayList<CityObj>(); int count = 0; for (int i = 0; i < tempList.length; i++) { CityObj city = tempList[i]; if (city.mCityId.equals(DELETED_ENTRY)) { continue; } if (!city.mCityName.substring(0, 1).equals(val)) { val = city.mCityName.substring(0, 1).toUpperCase(); sections.add(val); positions.add(count); // Add a header items.add(new CityObj(val, null, null)); count++; } items.add(city); count++; } mSectionHeaders = sections.toArray(); mSectionPositions = positions.toArray(); mAllTheCitiesList = items; mDisplayedCitiesList = items; } @Override public int getPositionForSection(int section) { return (mSectionPositions != null) ? (Integer) mSectionPositions[section] : 0; Integer[] positions = mSortType == SORT_BY_NAME ? mSortByNameSectionPositions : mSortByTimeSectionPositions; return (positions != null) ? (Integer) positions[section] : 0; } @Override public int getSectionForPosition(int p) { if (mSectionPositions != null) { for (int i = 0; i < mSectionPositions.length - 1; i++) { if (p >= (Integer) mSectionPositions[i] && p < (Integer) mSectionPositions[i + 1]) { Integer[] positions = mSortType == SORT_BY_NAME ? mSortByNameSectionPositions : mSortByTimeSectionPositions; if (positions != null) { for (int i = 0; i < positions.length - 1; i++) { if (p >= positions[i] && p < positions[i + 1]) { return i; } } if (p >= (Integer) mSectionPositions[mSectionPositions.length - 1]) { return mSectionPositions.length - 1; if (p >= positions[positions.length - 1]) { return positions.length - 1; } } return 0; Loading @@ -264,7 +359,8 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, @Override public Object[] getSections() { return mSectionHeaders; return mSortType == SORT_BY_NAME ? mSortByNameSectionHeaders : mSortByTimeSectionHeaders; } @Override Loading @@ -277,9 +373,12 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mFactory = LayoutInflater.from(this); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mSortType = mPrefs.getInt(PREF_SORT, SORT_BY_NAME); if (savedInstanceState != null) { mQueryTextBuffer.append(savedInstanceState.getString(KEY_SEARCH_QUERY)); mSearchMode = savedInstanceState.getBoolean(KEY_SEARCH_MODE); mPosition = savedInstanceState.getInt(KEY_LIST_POSITION); } updateLayout(); } Loading @@ -289,13 +388,13 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, super.onSaveInstanceState(bundle); bundle.putString(KEY_SEARCH_QUERY, mQueryTextBuffer.toString()); bundle.putBoolean(KEY_SEARCH_MODE, mSearchMode); bundle.putInt(KEY_LIST_POSITION, mCitiesList.getFirstVisiblePosition()); } private void updateLayout() { setContentView(R.layout.cities_activity); mCitiesList = (ListView) findViewById(R.id.cities_list); mCitiesList.setFastScrollAlwaysVisible(true); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString())); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim())); mCitiesList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); mUserSelectedCities = Cities.readCitiesFromSharedPrefs( PreferenceManager.getDefaultSharedPreferences(this)); Loading Loading @@ -340,6 +439,13 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, } } return true; case R.id.menu_item_sort: if (mAdapter != null) { mAdapter.toggleSort(); mCitiesList.setFastScrollEnabled( TextUtils.isEmpty(mQueryTextBuffer.toString().trim())); } return true; case android.R.id.home: Intent intent = new Intent(this, DeskClock.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Loading @@ -359,8 +465,8 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, Utils.prepareHelpMenuItem(this, help); } mSearchMenu = menu.findItem(R.id.menu_item_search); mSearchView = (SearchView) mSearchMenu.getActionView(); MenuItem searchMenu = menu.findItem(R.id.menu_item_search); mSearchView = (SearchView) searchMenu.getActionView(); mSearchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); mSearchView.setOnSearchClickListener(new OnClickListener() { Loading Loading @@ -388,6 +494,17 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem sortMenuItem = menu.findItem(R.id.menu_item_sort); if (mSortType == SORT_BY_NAME) { sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_gmt_offset)); } else { sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_name)); } return super.onPrepareOptionsMenu(menu); } @Override public void onCheckedChanged(CompoundButton b, boolean checked) { CityObj c = (CityObj) b.getTag(); Loading @@ -410,7 +527,7 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, public boolean onQueryTextChange(String queryText) { mQueryTextBuffer.setLength(0); mQueryTextBuffer.append(queryText); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(queryText.trim())); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim())); mAdapter.getFilter().filter(queryText); return true; } Loading Loading
res/menu/cities_menu.xml +3 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,9 @@ android:actionViewClass="android.widget.SearchView" android:imeOptions="actionSearch" android:orderInCategory="1" /> <item android:id="@+id/menu_item_sort" android:title="@string/menu_item_sort_by_gmt_offset" android:showAsAction="never" /> <item android:id="@+id/menu_item_settings" android:title="@string/menu_item_settings" android:icon="@android:drawable/ic_menu_preferences" Loading
res/values/dimens.xml +1 −1 Original line number Diff line number Diff line Loading @@ -77,7 +77,7 @@ <dimen name="dialpad_font_size">48sp</dimen> <dimen name="dialpad_font_size_ampm">24sp</dimen> <dimen name="city_name_font_size">24sp</dimen> <dimen name="city_time_font_size">14sp</dimen> <dimen name="city_time_font_size">18sp</dimen> <dimen name="tablet_dialpad_font_size">60sp</dimen> <dimen name="tablet_dialpad_font_size_ampm">30sp</dimen> Loading
res/values/strings.xml +4 −0 Original line number Diff line number Diff line Loading @@ -329,6 +329,10 @@ <string name="menu_item_help">Help</string> <!-- Menu item on clock screen to enter night mode. --> <string name="menu_item_night_mode">Night mode</string> <!-- Menu item on Cities screen to sort by GMT offset --> <string name="menu_item_sort_by_gmt_offset">Sort by time</string> <!-- Menu item on Cities screen to sort by alphabetical order --> <string name="menu_item_sort_by_name">Sort by name</string> <!-- Stop Watch strings --> <!-- Describes the purpose of the button to resume running a stopwatch --> Loading
src/com/android/deskclock/Utils.java +31 −13 Original line number Diff line number Diff line Loading @@ -38,8 +38,11 @@ import android.os.Handler; import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.Settings; import android.text.Spannable; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.style.ForegroundColorSpan; import android.view.MenuItem; import android.view.View; import android.view.animation.AccelerateInterpolator; Loading @@ -50,13 +53,11 @@ import com.android.deskclock.stopwatch.Stopwatches; import com.android.deskclock.timer.Timers; import com.android.deskclock.worldclock.CityObj; import java.text.Collator; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.Locale; import java.util.TimeZone; public class Utils { Loading Loading @@ -437,8 +438,7 @@ public class Utils { } } public static CityObj[] loadCitiesDataBase(Context c) { final Collator collator = Collator.getInstance(); public static CityObj[] loadCitiesFromXml(Context c) { Resources r = c.getResources(); // Read strings array of name,timezone, id // make sure the list are the same length Loading @@ -453,17 +453,35 @@ public class Utils { for (int i = 0; i < cities.length; i++) { tempList[i] = new CityObj(cities[i], timezones[i], ids[i]); } // Sort alphabetically Arrays.sort(tempList, new Comparator<CityObj> () { @Override public int compare(CityObj c1, CityObj c2) { Comparator<CityObj> mCollator; return collator.compare(c1.mCityName, c2.mCityName); } }); return tempList; } /** * Returns string denoting the timezone hour offset (e.g. GMT-8:00) */ public static String getGMTHourOffset(TimeZone timezone, boolean showMinutes) { StringBuilder sb = new StringBuilder(); sb.append("GMT"); int gmtOffset = timezone.getRawOffset(); if (gmtOffset < 0) { sb.append('-'); } else { sb.append('+'); } sb.append(Math.abs(gmtOffset) / DateUtils.HOUR_IN_MILLIS); // Hour if (showMinutes) { final int min = (Math.abs(gmtOffset) / (int) DateUtils.MINUTE_IN_MILLIS) % 60; sb.append(':'); if (min < 10) { sb.append('0'); } sb.append(min); } return sb.toString(); } public static String getCityName(CityObj city, CityObj dbCity) { return (city.mCityId == null || dbCity == null) ? city.mCityName : dbCity.mCityName; } Loading
src/com/android/deskclock/worldclock/CitiesActivity.java +187 −70 Original line number Diff line number Diff line Loading @@ -18,16 +18,14 @@ package com.android.deskclock.worldclock; import android.app.ActionBar; import android.app.Activity; import android.app.SearchableInfo; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; Loading @@ -54,8 +52,10 @@ import com.android.deskclock.SettingsActivity; import com.android.deskclock.Utils; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.TimeZone; /** Loading @@ -66,6 +66,12 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, private static final String KEY_SEARCH_QUERY = "search_query"; private static final String KEY_SEARCH_MODE = "search_mode"; private static final String KEY_LIST_POSITION = "list_position"; private static final String PREF_SORT = "sort_preference"; private static final int SORT_BY_NAME = 0; private static final int SORT_BY_GMT_OFFSET = 1; /** * This must be false for production. If true, turns on logging, test code, Loading @@ -80,28 +86,35 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, private HashMap<String, CityObj> mUserSelectedCities; private Calendar mCalendar; private MenuItem mSearchMenu; private SearchView mSearchView; private StringBuffer mQueryTextBuffer = new StringBuffer(); private boolean mSearchMode; private int mPosition = -1; private SharedPreferences mPrefs; private int mSortType; /*** * Adapter for a list of cities with the respected time zone. The Adapter * sorts the list alphabetically and create an indexer. ***/ private class CityAdapter extends BaseAdapter implements Filterable, SectionIndexer { private static final String DELETED_ENTRY = "C0"; private ArrayList<CityObj> mAllTheCitiesList; // full list of the cities private final HashMap<String, CityObj> mSelectedCitiesList; // selected cities private List<CityObj> mDisplayedCitiesList; private CityObj[] mCities; private String[] mSortByNameSectionHeaders; private Integer[] mSortByNameSectionPositions; private String[] mSortByTimeSectionHeaders; private Integer[] mSortByTimeSectionPositions; private ArrayList<CityObj> mDisplayedCitiesList; private final int mSpanColor; private CityNameComparator mSortByNameComparator = new CityNameComparator(); private CityGmtOffsetComparator mSortByGmtOffsetComparator = new CityGmtOffsetComparator(); private final LayoutInflater mInflater; private boolean mIs24HoursMode; // AM/PM or 24 hours mode private Object[] mSectionHeaders; private Object[] mSectionPositions; private Filter mFilter = new Filter() { Loading @@ -110,18 +123,51 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, FilterResults results = new FilterResults(); String modifiedQuery = constraint.toString().trim().toUpperCase(); ArrayList<CityObj> filteredList = new ArrayList<CityObj>(); int positionIndex = 0; int i = 0; while (i < mCities.length) { CityObj city = mCities[i]; // If the city is a deleted entry, ignore it. if (city.mCityId.equals(DELETED_ENTRY)) { i++; continue; } // If the search query is empty, add section headers if (TextUtils.isEmpty(modifiedQuery)) { results.values = mAllTheCitiesList; results.count = mAllTheCitiesList.size(); return results; // If the list is sorted by name, and the position index is correct, // insert a section header into the list if (mSortType == SORT_BY_NAME && mSortByNameSectionPositions.length > positionIndex && mSortByNameSectionPositions[positionIndex] == filteredList.size()) { String name = mSortByNameSectionHeaders[positionIndex]; filteredList.add(new CityObj(name, null, null)); positionIndex++; continue; } ArrayList<CityObj> filteredList = new ArrayList<CityObj>(); for (CityObj city : mAllTheCitiesList) { // If the list is sorted by time, and the position index is correct, // insert a section header into the list if (mSortType == SORT_BY_GMT_OFFSET && mSortByTimeSectionPositions.length > positionIndex && mSortByTimeSectionPositions[positionIndex] == filteredList.size()) { String timezone = mSortByTimeSectionHeaders[positionIndex]; filteredList.add(new CityObj(null, timezone, null)); positionIndex++; continue; } } // If the city name begins with the query, add the city into the list. // If the query is empty, the city will automatically be added to the list. String cityName = city.mCityName.trim().toUpperCase(); if (city.mCityId != null && cityName.startsWith(modifiedQuery)) { filteredList.add(city); } i++; } results.values = filteredList; results.count = filteredList.size(); Loading @@ -131,6 +177,10 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, @Override protected void publishResults(CharSequence constraint, FilterResults results) { mDisplayedCitiesList = (ArrayList<CityObj>) results.values; if (mPosition >= 0) { mCitiesList.setSelectionFromTop(mPosition, 0); mPosition = -1; } notifyDataSetChanged(); } }; Loading @@ -138,15 +188,88 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, public CityAdapter( Context context, HashMap<String, CityObj> selectedList, LayoutInflater factory) { super(); loadCitiesDataBase(context); loadCities(context); mSelectedCitiesList = selectedList; mInflater = factory; mCalendar = Calendar.getInstance(); mCalendar.setTimeInMillis(System.currentTimeMillis()); mSpanColor = context.getResources().getColor(R.color.clock_blue); set24HoursMode(context); } private void loadCities(Context c) { mCities = Utils.loadCitiesFromXml(c); if (mCities == null) { return; } // Sort alphabetically and populate section headers for sort-by-name Arrays.sort(mCities, mSortByNameComparator); String val = null; ArrayList<String> sections = new ArrayList<String>(); ArrayList<Integer> positions = new ArrayList<Integer>(); int count = 0; for (CityObj city : mCities) { if (city.mCityId.equals(DELETED_ENTRY)) { continue; } if (!city.mCityName.substring(0, 1).equals(val)) { val = city.mCityName.substring(0, 1).toUpperCase(); sections.add(val); positions.add(count); count++; } count++; } mSortByNameSectionHeaders = sections.toArray(new String[sections.size()]); mSortByNameSectionPositions = positions.toArray(new Integer[positions.size()]); // Sort by GMT offset and populate section headers for sort-by-time Arrays.sort(mCities, mSortByGmtOffsetComparator); int offset = -100000; // some number that cannot be a real offset val = null; sections.clear(); positions.clear(); ArrayList<String> scrollLabels = new ArrayList<String>(); count = 0; for (CityObj city : mCities) { if (city.mCityId.equals(DELETED_ENTRY)) { continue; } TimeZone timezone = TimeZone.getTimeZone(city.mTimeZone); int newOffset = timezone.getRawOffset(); if (newOffset != offset) { offset = newOffset; sections.add(Utils.getGMTHourOffset(timezone, true)); positions.add(count); count++; } count++; } mSortByTimeSectionHeaders = sections.toArray(new String[sections.size()]); mSortByTimeSectionPositions = positions.toArray(new Integer[positions.size()]); sortCities(mSortType); } public void toggleSort() { if (mSortType == SORT_BY_NAME) { sortCities(SORT_BY_GMT_OFFSET); } else { sortCities(SORT_BY_NAME); } } private void sortCities(final int sortType) { mSortType = sortType; Arrays.sort(mCities, sortType == SORT_BY_NAME ? mSortByNameComparator : mSortByGmtOffsetComparator); mPrefs.edit().putInt(PREF_SORT, sortType).commit(); mFilter.filter(mQueryTextBuffer.toString()); } @Override public int getCount() { return (mDisplayedCitiesList != null) ? mDisplayedCitiesList.size() : 0; Loading Loading @@ -183,7 +306,7 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, view = mInflater.inflate(R.layout.city_list_header, parent, false); } TextView header = (TextView) view.findViewById(R.id.header); header.setText(c.mCityName); header.setText(mSortType == SORT_BY_NAME ? c.mCityName : c.mTimeZone); } else { // City view // Make sure to recycle a City view only if (view == null || view.findViewById(R.id.city_name) == null) { Loading @@ -208,55 +331,27 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, notifyDataSetChanged(); } private void loadCitiesDataBase(Context c) { CityObj[] tempList = Utils.loadCitiesDataBase(c); if (tempList == null) { return; } // Create section indexer and add headers to the cities list String val = null; ArrayList<String> sections = new ArrayList<String>(); ArrayList<Integer> positions = new ArrayList<Integer>(); ArrayList<CityObj> items = new ArrayList<CityObj>(); int count = 0; for (int i = 0; i < tempList.length; i++) { CityObj city = tempList[i]; if (city.mCityId.equals(DELETED_ENTRY)) { continue; } if (!city.mCityName.substring(0, 1).equals(val)) { val = city.mCityName.substring(0, 1).toUpperCase(); sections.add(val); positions.add(count); // Add a header items.add(new CityObj(val, null, null)); count++; } items.add(city); count++; } mSectionHeaders = sections.toArray(); mSectionPositions = positions.toArray(); mAllTheCitiesList = items; mDisplayedCitiesList = items; } @Override public int getPositionForSection(int section) { return (mSectionPositions != null) ? (Integer) mSectionPositions[section] : 0; Integer[] positions = mSortType == SORT_BY_NAME ? mSortByNameSectionPositions : mSortByTimeSectionPositions; return (positions != null) ? (Integer) positions[section] : 0; } @Override public int getSectionForPosition(int p) { if (mSectionPositions != null) { for (int i = 0; i < mSectionPositions.length - 1; i++) { if (p >= (Integer) mSectionPositions[i] && p < (Integer) mSectionPositions[i + 1]) { Integer[] positions = mSortType == SORT_BY_NAME ? mSortByNameSectionPositions : mSortByTimeSectionPositions; if (positions != null) { for (int i = 0; i < positions.length - 1; i++) { if (p >= positions[i] && p < positions[i + 1]) { return i; } } if (p >= (Integer) mSectionPositions[mSectionPositions.length - 1]) { return mSectionPositions.length - 1; if (p >= positions[positions.length - 1]) { return positions.length - 1; } } return 0; Loading @@ -264,7 +359,8 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, @Override public Object[] getSections() { return mSectionHeaders; return mSortType == SORT_BY_NAME ? mSortByNameSectionHeaders : mSortByTimeSectionHeaders; } @Override Loading @@ -277,9 +373,12 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mFactory = LayoutInflater.from(this); mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mSortType = mPrefs.getInt(PREF_SORT, SORT_BY_NAME); if (savedInstanceState != null) { mQueryTextBuffer.append(savedInstanceState.getString(KEY_SEARCH_QUERY)); mSearchMode = savedInstanceState.getBoolean(KEY_SEARCH_MODE); mPosition = savedInstanceState.getInt(KEY_LIST_POSITION); } updateLayout(); } Loading @@ -289,13 +388,13 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, super.onSaveInstanceState(bundle); bundle.putString(KEY_SEARCH_QUERY, mQueryTextBuffer.toString()); bundle.putBoolean(KEY_SEARCH_MODE, mSearchMode); bundle.putInt(KEY_LIST_POSITION, mCitiesList.getFirstVisiblePosition()); } private void updateLayout() { setContentView(R.layout.cities_activity); mCitiesList = (ListView) findViewById(R.id.cities_list); mCitiesList.setFastScrollAlwaysVisible(true); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString())); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim())); mCitiesList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); mUserSelectedCities = Cities.readCitiesFromSharedPrefs( PreferenceManager.getDefaultSharedPreferences(this)); Loading Loading @@ -340,6 +439,13 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, } } return true; case R.id.menu_item_sort: if (mAdapter != null) { mAdapter.toggleSort(); mCitiesList.setFastScrollEnabled( TextUtils.isEmpty(mQueryTextBuffer.toString().trim())); } return true; case android.R.id.home: Intent intent = new Intent(this, DeskClock.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); Loading @@ -359,8 +465,8 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, Utils.prepareHelpMenuItem(this, help); } mSearchMenu = menu.findItem(R.id.menu_item_search); mSearchView = (SearchView) mSearchMenu.getActionView(); MenuItem searchMenu = menu.findItem(R.id.menu_item_search); mSearchView = (SearchView) searchMenu.getActionView(); mSearchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); mSearchView.setOnSearchClickListener(new OnClickListener() { Loading Loading @@ -388,6 +494,17 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, return super.onCreateOptionsMenu(menu); } @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem sortMenuItem = menu.findItem(R.id.menu_item_sort); if (mSortType == SORT_BY_NAME) { sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_gmt_offset)); } else { sortMenuItem.setTitle(getString(R.string.menu_item_sort_by_name)); } return super.onPrepareOptionsMenu(menu); } @Override public void onCheckedChanged(CompoundButton b, boolean checked) { CityObj c = (CityObj) b.getTag(); Loading @@ -410,7 +527,7 @@ public class CitiesActivity extends Activity implements OnCheckedChangeListener, public boolean onQueryTextChange(String queryText) { mQueryTextBuffer.setLength(0); mQueryTextBuffer.append(queryText); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(queryText.trim())); mCitiesList.setFastScrollEnabled(TextUtils.isEmpty(mQueryTextBuffer.toString().trim())); mAdapter.getFilter().filter(queryText); return true; } Loading