From 8b0b3f47b968adada2b6d927901ccec1ba4991a4 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Thu, 3 Jul 2014 13:48:39 +0800 Subject: [PATCH] Initial commit --- .gitignore | 5 + .idea/.name | 1 + .idea/compiler.xml | 23 + .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 5 + .idea/gradle.xml | 18 + .idea/libraries/acra_4_5_0.xml | 11 + .idea/libraries/dashclock_api_2_0_0.xml | 11 + .../dnsjava_ipv6_1_0_with_sources.xml | 9 + .../drag_sort_listview_apklib_1_0.xml | 9 + .idea/libraries/httpclient_android_4_3_3.xml | 9 + .idea/libraries/httpmime_4_3_3.xml | 11 + .idea/libraries/jsonserializer_1_0.xml | 9 + .idea/libraries/library_1_0.xml | 10 + .idea/libraries/library_1_0_5.xml | 10 + .idea/libraries/library_2_0_0.xml | 10 + .idea/libraries/library_2_4_0.xml | 11 + .idea/libraries/library_2_4_1.xml | 9 + .idea/libraries/merge_1_0_1.xml | 9 + .idea/libraries/sacklist_1_0_0.xml | 9 + .idea/libraries/snakeyaml_1_12.xml | 11 + .../libraries/support_annotations_20_0_0.xml | 11 + .idea/libraries/support_v13_20_0_0.xml | 11 + .idea/libraries/support_v4_20_0_0.xml | 11 + .idea/libraries/support_v4_r7.xml | 11 + .idea/libraries/twitter_text_1_9_5.xml | 11 + .../universal_image_loader_1_9_2.xml | 11 + .idea/misc.xml | 10 + .idea/modules.xml | 10 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + Twidere-Android.iml | 19 + build.gradle | 19 + gradle.properties | 18 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 + gradlew.bat | 90 + settings.gradle | 1 + twidere/.gitignore | 1 + twidere/build.gradle | 36 + .../libs/dnsjava-ipv6-1.0-with-sources.jar | Bin 0 -> 141136 bytes twidere/libs/jsonserializer-1.0.jar | Bin 0 -> 12154 bytes twidere/libs/merge-1.0.1.jar | Bin 0 -> 9144 bytes twidere/libs/sacklist-1.0.0.jar | Bin 0 -> 2628 bytes twidere/proguard-rules.pro | 17 + .../mariotaku/twidere/ApplicationTest.java | 13 + twidere/src/assets/fonts/TwidereIconic.ttf | Bin 0 -> 13420 bytes twidere/src/assets/gpl-3.0-standalone.html | 694 +++ twidere/src/assets/images/loading_tile.png | Bin 0 -> 729 bytes twidere/src/assets/mapview.html | 75 + twidere/src/main/AndroidManifest.xml | 713 +++ .../support/v4/app/BackStackEntryTrojan.java | 9 + .../support/v4/app/FragmentManagerTrojan.java | 9 + .../support/v4/app/FragmentTrojan.java | 11 + .../support/v4/app/ListFragmentTrojan.java | 27 + .../iconicdroid/IconicFontDrawable.java | 274 ++ .../android/iconicdroid/icon/Icon.java | 43 + .../iconicdroid/util/TypefaceManager.java | 40 + .../viewbadger/BadgeView.java | 491 +++ .../levelup/views/gallery/AbsSpinner.java | 517 +++ .../levelup/views/gallery/AdapterView.java | 1182 +++++ .../scvngr/levelup/views/gallery/Gallery.java | 1520 +++++++ .../android/widget/crouton/Crouton.java | 847 ++++ .../widget/crouton/CroutonConfiguration.java | 127 + .../crouton/CroutonLifecycleCallback.java | 28 + .../widget/crouton/CroutonManager.java | 366 ++ .../android/widget/crouton/CroutonStyle.java | 479 +++ .../crouton/DefaultAnimationsBuilder.java | 83 + .../edu/ucdavis/earlybird/CSVFileFilter.java | 19 + .../edu/ucdavis/earlybird/ProfilingUtil.java | 70 + .../edu/ucdavis/earlybird/UCDService.java | 84 + .../edu/ucdavis/earlybird/UploadReceiver.java | 27 + .../edu/ucdavis/earlybird/UploadTask.java | 136 + .../library/imagezoom/ImageViewTouch.java | 249 ++ .../library/imagezoom/ImageViewTouchBase.java | 504 +++ .../imagezoom/ScaleGestureDetector.java | 487 +++ .../library/imagezoom/easing/Cubic.java | 20 + .../library/imagezoom/easing/Easing.java | 10 + .../graphics/FastBitmapDrawable.java | 85 + .../imagezoom/graphics/IBitmapDrawable.java | 15 + .../library/imagezoom/utils/IDisposable.java | 6 + .../swipebacklayout/lib/SwipeBackLayout.java | 621 +++ .../swipebacklayout/lib/ViewDragHelper.java | 1511 +++++++ .../lib/app/SwipeBackActivity.java | 55 + .../lib/app/SwipeBackActivityBase.java | 21 + .../lib/app/SwipeBackActivityHelper.java | 67 + .../commons/lang3/StringEscapeUtils.java | 213 + .../text/translate/AggregateTranslator.java | 79 + .../translate/CharSequenceTranslator.java | 127 + .../lang3/text/translate/EntityArrays.java | 547 +++ .../text/translate/LookupTranslator.java | 81 + .../translate/NumericEntityUnescaper.java | 133 + .../dynamicgridview/DraggableAdapter.java | 23 + .../DraggableArrayAdapter.java | 122 + .../dynamicgridview/DynamicGridView.java | 647 +++ .../dynamicgridview/DynamicListView.java | 588 +++ .../mariotaku/gallery3d/GLImageLoader.java | 308 ++ .../gallery3d/ImageViewerGLActivity.java | 570 +++ .../mariotaku/gallery3d/PhotoViewAdapter.java | 241 ++ .../mariotaku/gallery3d/anim/Animation.java | 88 + .../gallery3d/anim/CanvasAnimation.java | 26 + .../mariotaku/gallery3d/ui/AnimationTime.java | 44 + .../mariotaku/gallery3d/ui/BasicTexture.java | 204 + .../gallery3d/ui/BitmapScreenNail.java | 58 + .../mariotaku/gallery3d/ui/BitmapTexture.java | 54 + .../gallery3d/ui/DownUpDetector.java | 63 + .../mariotaku/gallery3d/ui/EdgeEffect.java | 451 ++ .../org/mariotaku/gallery3d/ui/EdgeView.java | 128 + .../mariotaku/gallery3d/ui/FlingScroller.java | 138 + .../org/mariotaku/gallery3d/ui/GLCanvas.java | 98 + .../mariotaku/gallery3d/ui/GLCanvasImpl.java | 588 +++ .../java/org/mariotaku/gallery3d/ui/GLId.java | 44 + .../org/mariotaku/gallery3d/ui/GLRoot.java | 53 + .../mariotaku/gallery3d/ui/GLRootView.java | 541 +++ .../org/mariotaku/gallery3d/ui/GLView.java | 307 ++ .../gallery3d/ui/GalleryEGLConfigChooser.java | 112 + .../gallery3d/ui/GestureRecognizer.java | 134 + .../org/mariotaku/gallery3d/ui/PhotoView.java | 592 +++ .../gallery3d/ui/PositionController.java | 1019 +++++ .../gallery3d/ui/ResourceTexture.java | 51 + .../mariotaku/gallery3d/ui/ScreenNail.java | 36 + .../gallery3d/ui/SynchronizedHandler.java | 44 + .../org/mariotaku/gallery3d/ui/Texture.java | 47 + .../mariotaku/gallery3d/ui/TileImageView.java | 758 ++++ .../gallery3d/ui/UploadedTexture.java | 312 ++ .../mariotaku/gallery3d/util/ApiHelper.java | 26 + .../mariotaku/gallery3d/util/BitmapPool.java | 74 + .../mariotaku/gallery3d/util/BitmapUtils.java | 72 + .../mariotaku/gallery3d/util/DecodeUtils.java | 33 + .../org/mariotaku/gallery3d/util/Future.java | 38 + .../gallery3d/util/FutureListener.java | 23 + .../gallery3d/util/GalleryUtils.java | 148 + .../mariotaku/gallery3d/util/IntArray.java | 49 + .../gallery3d/util/LongSparseArray.java | 251 ++ .../gallery3d/util/MotionEventHelper.java | 33 + .../gallery3d/util/PriorityThreadFactory.java | 49 + .../mariotaku/gallery3d/util/ThreadPool.java | 249 ++ .../harmony/view/HorizontalGridView.java | 46 + .../harmony/view/HorizontalListView.java | 46 + .../view/HorizontalWrapperAdapter.java | 87 + .../mariotaku/jsonserializer/JSONFileIO.java | 171 + .../mariotaku/querybuilder/AllColumns.java | 47 + .../org/mariotaku/querybuilder/Columns.java | 92 + .../org/mariotaku/querybuilder/NewColumn.java | 37 + .../org/mariotaku/querybuilder/OrderBy.java | 45 + .../mariotaku/querybuilder/RawItemArray.java | 24 + .../mariotaku/querybuilder/SQLFunctions.java | 9 + .../org/mariotaku/querybuilder/SQLLang.java | 38 + .../querybuilder/SQLQueryBuilder.java | 98 + .../querybuilder/SQLQueryException.java | 49 + .../mariotaku/querybuilder/Selectable.java | 32 + .../org/mariotaku/querybuilder/Tables.java | 43 + .../org/mariotaku/querybuilder/Utils.java | 62 + .../org/mariotaku/querybuilder/Where.java | 103 + .../querybuilder/query/IBuilder.java | 16 + .../query/SQLAlterTableQuery.java | 71 + .../query/SQLCreateTableQuery.java | 111 + .../query/SQLCreateViewQuery.java | 90 + .../querybuilder/query/SQLDeleteQuery.java | 57 + .../querybuilder/query/SQLDropQuery.java | 19 + .../querybuilder/query/SQLDropTableQuery.java | 22 + .../querybuilder/query/SQLDropViewQuery.java | 22 + .../query/SQLInsertIntoQuery.java | 106 + .../querybuilder/query/SQLSelectQuery.java | 260 ++ .../java/org/mariotaku/twidere/Constants.java | 183 + .../org/mariotaku/twidere/IMediaUploader.aidl | 29 + .../mariotaku/twidere/IStatusShortener.aidl | 28 + .../twidere/ITimelineSyncHelper.aidl | 28 + .../mariotaku/twidere/TwidereConstants.java | 236 + .../activity/AssistLauncherActivity.java | 33 + .../twidere/activity/BaseActivity.java | 114 + .../activity/BasePreferenceActivity.java | 163 + .../twidere/activity/BaseThemedActivity.java | 183 + .../twidere/activity/CameraCropActivity.java | 124 + .../CreateComposeShortcutActivity.java | 44 + .../twidere/activity/CustomTabsActivity.java | 81 + .../DataProfilingSettingsActivity.java | 47 + .../twidere/activity/FiltersActivity.java | 276 ++ .../twidere/activity/MainActivity.java | 57 + .../activity/MainHondaJOJOActivity.java | 24 + .../twidere/activity/NyanActivity.java | 128 + .../twidere/activity/SettingsActivity.java | 297 ++ .../activity/SettingsWizardActivity.java | 600 +++ .../twidere/activity/TestActivity.java | 37 + .../activity/TwitterLinkHandlerActivity.java | 193 + .../activity/iface/IThemedActivity.java | 49 + .../activity/support/APIEditorActivity.java | 245 ++ .../support/AccountSelectorActivity.java | 188 + .../support/ActivityPickerActivity.java | 73 + .../activity/support/BaseSupportActivity.java | 142 + .../support/BaseSupportDialogActivity.java | 82 + .../support/BaseSupportThemedActivity.java | 159 + .../BaseSupportThemedSwipeBackActivity.java | 92 + .../activity/support/BrowserActivity.java | 68 + .../support/BrowserSignInActivity.java | 308 ++ .../support/ColorPickerDialogActivity.java | 78 + .../activity/support/ComposeActivity.java | 1484 +++++++ .../support/CustomTabEditorActivity.java | 540 +++ .../activity/support/DataExportActivity.java | 143 + .../activity/support/DataImportActivity.java | 205 + .../activity/support/DraftsActivity.java | 341 ++ .../support/FileSelectorActivity.java | 87 + .../activity/support/HomeActivity.java | 1044 +++++ .../activity/support/ImagePickerActivity.java | 248 ++ .../activity/support/LinkHandlerActivity.java | 286 ++ .../activity/support/MapViewerActivity.java | 103 + .../activity/support/MenuDialogFragment.java | 71 + .../support/RequestPermissionsActivity.java | 155 + .../activity/support/SignInActivity.java | 798 ++++ .../support/TwidereSwipeBackActivity.java | 97 + .../support/UserListSelectorActivity.java | 339 ++ .../support/UserProfileEditorActivity.java | 596 +++ .../twidere/adapter/AccountsAdapter.java | 123 + .../adapter/AccountsSpinnerAdapter.java | 87 + .../twidere/adapter/ArrayAdapter.java | 140 + .../twidere/adapter/BaseArrayAdapter.java | 151 + .../twidere/adapter/BaseCursorAdapter.java | 167 + .../BaseParcelableActivitiesAdapter.java | 236 + .../adapter/CursorStatusesAdapter.java | 496 +++ ...rectMessageConversationEntriesAdapter.java | 203 + .../DirectMessagesConversationAdapter.java | 252 ++ .../twidere/adapter/DraftsAdapter.java | 126 + .../twidere/adapter/ExtensionsAdapter.java | 83 + .../twidere/adapter/ListActionAdapter.java | 59 + .../twidere/adapter/MediaPreviewAdapter.java | 72 + .../ParcelableActivitiesAboutMeAdapter.java | 300 ++ .../ParcelableActivitiesByFriendsAdapter.java | 173 + .../adapter/ParcelableStatusesAdapter.java | 442 ++ .../adapter/ParcelableUserListsAdapter.java | 191 + .../adapter/ParcelableUsersAdapter.java | 191 + .../adapter/ResolveInfoListAdapter.java | 35 + .../twidere/adapter/SectionArrayAdapter.java | 17 + .../twidere/adapter/SeparatedListAdapter.java | 157 + .../SimpleParcelableUserListsAdapter.java | 99 + .../adapter/SimpleParcelableUsersAdapter.java | 104 + .../adapter/SourceAutoCompleteAdapter.java | 87 + .../twidere/adapter/TabsAdapter.java | 124 + .../UserHashtagAutoCompleteAdapter.java | 237 + .../twidere/adapter/iface/IBaseAdapter.java | 60 + .../adapter/iface/IBaseCardAdapter.java | 35 + .../adapter/iface/IDirectMessagesAdapter.java | 31 + .../adapter/iface/IStatusesAdapter.java | 67 + ...SupportFixedFragmentStatePagerAdapter.java | 45 + .../adapter/support/SupportTabsAdapter.java | 143 + .../twidere/animation/CardItemAnimation.java | 38 + .../twidere/animation/Rotate3dAnimation.java | 106 + .../twidere/annotation/Preference.java | 61 + .../twidere/app/TwidereApplication.java | 343 ++ .../backup/TwidereBackupAgentHelper.java | 42 + .../twidere/constant/IntentConstants.java | 221 + .../constant/SharedPreferenceConstants.java | 306 ++ .../content/TwidereContextThemeWrapper.java | 72 + .../content/TwidereContextWrapper.java | 87 + .../content/iface/ITwidereContextWrapper.java | 6 + .../content/res/TwidereAccentResources.java | 33 + .../twidere/content/res/TwidereResources.java | 30 + .../content/res/iface/IThemedResources.java | 52 + .../twidere/dialog/ColorPickerDialog.java | 202 + .../AccountNotificationSettingsFragment.java | 55 + .../AccountRefreshSettingsFragment.java | 40 + .../BaseAccountPreferenceFragment.java | 114 + .../twidere/fragment/BaseDialogFragment.java | 79 + .../twidere/fragment/BaseFiltersFragment.java | 305 ++ .../twidere/fragment/BaseFragment.java | 99 + .../twidere/fragment/BaseListFragment.java | 209 + .../fragment/BasePreferenceFragment.java | 34 + .../twidere/fragment/BaseWebViewFragment.java | 45 + .../twidere/fragment/BrowserFragment.java | 38 + .../twidere/fragment/CustomTabsFragment.java | 389 ++ .../DataProfilingSettingsFragment.java | 115 + .../fragment/ExtensionsListFragment.java | 164 + .../fragment/HostMappingsListFragment.java | 307 ++ .../fragment/PayPalDonateFragment.java | 114 + .../fragment/ProgressDialogFragment.java | 45 + .../fragment/SettingsDetailsFragment.java | 51 + .../fragment/SettingsEntriesFragment.java | 37 + .../twidere/fragment/iface/IBaseFragment.java | 28 + .../iface/IBasePullToRefreshFragment.java | 31 + .../iface/IDialogFragmentCallback.java | 28 + .../twidere/fragment/iface/IMapFragment.java | 26 + .../iface/ISupportDialogFragmentCallback.java | 30 + .../iface/RefreshScrollTopInterface.java | 28 + .../iface/SupportFragmentCallback.java | 34 + .../support/AccountsDrawerFragment.java | 632 +++ .../support/ActivitiesAboutMeFragment.java | 119 + .../support/ActivitiesByFriendsFragment.java | 124 + .../AddStatusFilterDialogFragment.java | 229 + .../AddUserListMemberDialogFragment.java | 98 + .../support/BaseActivitiesListFragment.java | 139 + .../BasePullToRefreshListFragment.java | 213 + ...asePullToRefreshStaggeredGridFragment.java | 211 + .../support/BaseStatusesListFragment.java | 594 +++ .../BaseStatusesStaggeredGridFragment.java | 578 +++ .../support/BaseSupportDialogFragment.java | 85 + .../fragment/support/BaseSupportFragment.java | 108 + .../support/BaseSupportListFragment.java | 365 ++ .../BaseSupportStaggeredGridFragment.java | 254 ++ .../support/BaseSupportWebViewFragment.java | 44 + .../support/BaseUserListsListFragment.java | 251 ++ .../support/BaseUsersListFragment.java | 301 ++ .../support/ColorPickerDialogFragment.java | 107 + .../CreateUserBlockDialogFragment.java | 82 + .../support/CreateUserListDialogFragment.java | 98 + .../support/CursorStatusesListFragment.java | 226 + .../CursorStatusesStaggeredGridFragment.java | 213 + .../CursorSupportUsersListFragment.java | 88 + ...xportImportTypeSelectorDialogFragment.java | 204 + .../DeleteUserListMembersDialogFragment.java | 110 + .../DestroyFriendshipDialogFragment.java | 83 + .../DestroySavedSearchDialogFragment.java | 96 + .../support/DestroyStatusDialogFragment.java | 77 + .../DestroyUserListDialogFragment.java | 80 + ...royUserListSubscriptionDialogFragment.java | 82 + .../DirectMessagesConversationFragment.java | 535 +++ .../support/DirectMessagesFragment.java | 376 ++ .../support/FileSelectorDialogFragment.java | 309 ++ .../support/HomeTimelineFragment.java | 98 + .../support/IncomingFriendshipsFragment.java | 66 + ...IncomingFriendshipsMenuDialogFragment.java | 24 + .../fragment/support/InvalidTabFragment.java | 17 + .../support/MentionsTimelineFragment.java | 113 + .../support/MutesUsersListFragment.java | 37 + .../fragment/support/NativeMapFragment.java | 69 + .../ParcelableStatusesListFragment.java | 229 + .../PhishingLinkWarningDialogFragment.java | 66 + .../fragment/support/QuickMenuFragment.java | 108 + .../support/ReportSpamDialogFragment.java | 82 + .../support/RetweetsOfMeFragment.java | 67 + .../support/SavedSearchesListFragment.java | 168 + .../fragment/support/SearchFragment.java | 180 + .../support/SearchStatusesFragment.java | 86 + .../fragment/support/SearchUsersFragment.java | 85 + ...SensitiveContentWarningDialogFragment.java | 65 + .../SetUserNicknameDialogFragment.java | 102 + .../support/StaggeredGridFragment.java | 364 ++ .../StaggeredHomeTimelineFragment.java | 98 + .../support/StatusFavoritersListFragment.java | 38 + .../fragment/support/StatusFragment.java | 1268 ++++++ .../support/StatusMenuDialogFragment.java | 29 + .../support/StatusRepliesListFragment.java | 63 + .../support/StatusRetweetersListFragment.java | 38 + .../StatusTranslateDialogFragment.java | 237 + .../support/StatusesListFragment.java | 59 + .../SupportProgressDialogFragment.java | 45 + .../support/SupportWebViewFragment.java | 43 + .../support/TrendsSuggectionsFragment.java | 148 + .../support/UserBlocksListFragment.java | 37 + .../support/UserFavoritesFragment.java | 118 + .../support/UserFollowersFragment.java | 78 + .../fragment/support/UserFriendsFragment.java | 39 + .../support/UserListDetailsFragment.java | 614 +++ .../support/UserListMembersFragment.java | 176 + .../UserListMembershipsListFragment.java | 37 + .../support/UserListMenuDialogFragment.java | 42 + .../support/UserListSubscribersFragment.java | 42 + .../support/UserListTimelineFragment.java | 75 + .../support/UserListsListFragment.java | 123 + .../support/UserMentionsFragment.java | 60 + .../support/UserMenuDialogFragment.java | 37 + .../fragment/support/UserProfileFragment.java | 1129 +++++ .../support/UserTimelineFragment.java | 93 + .../fragment/support/UsersListFragment.java | 40 + .../fragment/support/WebMapFragment.java | 116 + .../twidere/graphic/AlphaPatternDrawable.java | 95 + .../twidere/graphic/ColorPreviewDrawable.java | 69 + .../twidere/graphic/DropShadowDrawable.java | 113 + .../twidere/graphic/EmptyDrawable.java | 30 + .../twidere/graphic/PaddingDrawable.java | 92 + .../twidere/graphic/TextDrawable.java | 449 ++ .../twidere/graphic/icon/TwidereIcon.java | 62 + .../twidere/loader/ExtensionsListLoader.java | 198 + .../support/ActivitiesAboutMeLoader.java | 46 + .../support/ActivitiesByFriendsLoader.java | 46 + .../support/BaseCursorSupportUsersLoader.java | 70 + .../loader/support/BaseUserListsLoader.java | 108 + .../support/CursorSupportUsersLoader.java | 57 + .../DummyParcelableStatusesLoader.java | 46 + .../support/DummyParcelableUsersLoader.java | 40 + .../loader/support/IDsUsersLoader.java | 56 + .../support/IncomingFriendshipsLoader.java | 46 + .../support/IntentActivitiesLoader.java | 175 + .../support/IntentExtrasStatusesLoader.java | 52 + .../support/IntentExtrasUsersLoader.java | 52 + .../loader/support/MutesUsersLoader.java | 48 + .../support/ParcelableStatusesLoader.java | 91 + .../loader/support/ParcelableUserLoader.java | 145 + .../loader/support/ParcelableUsersLoader.java | 60 + .../loader/support/RetweetsOfMeLoader.java | 68 + .../loader/support/SavedSearchesLoader.java | 58 + .../support/StatusConversationLoader.java | 65 + .../support/StatusFavoritersLoader.java | 50 + .../loader/support/StatusRepliesLoader.java | 72 + .../support/StatusRetweetersLoader.java | 50 + .../loader/support/TweetSearchLoader.java | 72 + .../support/Twitter4JActivitiesLoader.java | 151 + .../support/Twitter4JStatusesLoader.java | 164 + .../loader/support/Twitter4JUsersLoader.java | 71 + .../loader/support/UserBlocksLoader.java | 48 + .../loader/support/UserFavoritesLoader.java | 66 + .../loader/support/UserFollowersLoader.java | 56 + .../loader/support/UserFriendsLoader.java | 56 + .../loader/support/UserListMembersLoader.java | 62 + .../support/UserListMembershipsLoader.java | 54 + .../support/UserListSubscribersLoader.java | 63 + .../support/UserListTimelineLoader.java | 75 + .../loader/support/UserListsLoader.java | 60 + .../loader/support/UserMentionsLoader.java | 43 + .../loader/support/UserSearchLoader.java | 53 + .../loader/support/UserTimelineLoader.java | 82 + .../twidere/menu/AccountActionProvider.java | 61 + .../org/mariotaku/twidere/model/Account.aidl | 22 + .../org/mariotaku/twidere/model/Account.java | 300 ++ .../twidere/model/AccountPreferences.java | 170 + .../twidere/model/ConsumerKeySecret.java | 88 + .../twidere/model/CustomTabConfiguration.java | 181 + .../model/CustomTabConfiguration2.java | 175 + .../mariotaku/twidere/model/DraftItem.java | 143 + .../mariotaku/twidere/model/ListAction.java | 52 + .../mariotaku/twidere/model/ListResponse.java | 40 + .../twidere/model/MediaUploadResult.aidl | 22 + .../twidere/model/MediaUploadResult.java | 73 + .../twidere/model/MediaUploaderParameter.aidl | 22 + .../org/mariotaku/twidere/model/Panes.java | 31 + .../twidere/model/ParcelableActivity.java | 177 + .../model/ParcelableDirectMessage.java | 265 ++ .../twidere/model/ParcelableLocation.aidl | 22 + .../twidere/model/ParcelableLocation.java | 176 + .../twidere/model/ParcelableMedia.java | 147 + .../twidere/model/ParcelableMediaUpdate.java | 88 + .../twidere/model/ParcelableStatus.aidl | 22 + .../twidere/model/ParcelableStatus.java | 498 +++ .../twidere/model/ParcelableStatusUpdate.aidl | 22 + .../twidere/model/ParcelableStatusUpdate.java | 162 + .../twidere/model/ParcelableUser.java | 323 ++ .../twidere/model/ParcelableUserList.java | 201 + .../twidere/model/ParcelableUserMention.java | 162 + .../model/RawSharedPreferencesData.java | 149 + .../twidere/model/SharedPreferencesData.java | 182 + .../twidere/model/SingleResponse.java | 102 + .../twidere/model/StatusShortenResult.aidl | 22 + .../twidere/model/StatusShortenResult.java | 71 + .../twidere/model/SupportTabSpec.java | 78 + .../org/mariotaku/twidere/model/TabSpec.java | 64 + .../twidere/model/TwidereParcelable.java | 9 + .../mariotaku/twidere/model/UnreadItem.java | 81 + .../twidere/model/UploaderMediaItem.aidl | 22 + .../twidere/model/UploaderMediaItem.java | 73 + .../preference/AccountsListPreference.java | 231 + .../preference/AppVersionPreference.java | 79 + .../preference/AsyncTaskPreference.java | 100 + .../preference/AutoFixCheckBoxPreference.java | 34 + .../preference/AutoFixEditTextPreference.java | 34 + .../preference/AutoFixListPreference.java | 30 + .../AutoInvalidateListPreference.java | 42 + .../AutoRefreshAccountsListPreference.java | 51 + .../AutoRefreshContentPreference.java | 64 + .../preference/CardPreviewPreference.java | 143 + .../preference/ClearCachePreference.java | 69 + .../preference/ClearDatabasesPreference.java | 72 + .../ClearSearchHistoryPreference.java | 53 + .../preference/ColorPickerPreference.java | 165 + .../preference/ComponentStatePreference.java | 77 + .../preference/DefaultAPIPreference.java | 206 + .../HomeRefreshContentPreference.java | 61 + .../preference/ImagePreloadPreference.java | 57 + .../LeftsideComposeButtonPreference.java | 47 + .../preference/LinkHighlightPreference.java | 70 + .../preference/MediaUploaderPreference.java | 48 + .../preference/MultiSelectListPreference.java | 102 + .../NotificationAccountsListPreference.java | 51 + .../NotificationContentPreference.java | 59 + .../NotificationTypePreference.java | 102 + .../preference/RingtonePreference.java | 113 + .../preference/SeekBarDialogPreference.java | 254 ++ .../preference/ServicePickerPreference.java | 76 + .../SilentNotificationsPreference.java | 72 + .../preference/StatusShortenerPreference.java | 48 + .../preference/SummaryEditTextPreference.java | 31 + .../preference/SummaryListPreference.java | 21 + .../preference/ThemeFontFamilyPreference.java | 78 + .../preference/ThemePreviewPreference.java | 164 + .../preference/TimelineSyncPreference.java | 48 + .../TranslationDestinationPreference.java | 224 + .../preference/TrendsLocationPreference.java | 215 + .../ValueDependencyCheckBoxPreference.java | 92 + .../ValueDependencyDialogPreference.java | 92 + ...alueDependencySeekBarDialogPreference.java | 92 + .../WizardPageHeaderPreference.java | 55 + .../preference/WizardPageNavPreference.java | 52 + .../provider/RecentSearchProvider.java | 31 + .../twidere/provider/TweetStore.java | 784 ++++ .../provider/TwidereCommandProvider.java | 166 + .../twidere/provider/TwidereCommands.java | 133 + .../twidere/provider/TwidereDataProvider.java | 1371 ++++++ .../receiver/ConnectivityStateReceiver.java | 47 + .../receiver/SecretCodeBroadcastReceiver.java | 49 + .../service/BackgroundOperationService.java | 701 +++ .../DashClockHomeUnreadCountService.java | 56 + .../DashClockMentionsUnreadCountService.java | 56 + .../DashClockMessagesUnreadCountService.java | 56 + .../twidere/service/NyanDaydreamService.java | 84 + .../twidere/service/NyanWallpaperService.java | 137 + .../twidere/service/RefreshService.java | 317 ++ .../org/mariotaku/twidere/task/AsyncTask.java | 176 + .../twidere/task/CacheUsersStatusesTask.java | 133 + .../twidere/task/ManagedAsyncTask.java | 78 + .../twidere/text/TwidereHighLightStyle.java | 44 + .../twidere/text/TwidereURLSpan.java | 72 + .../method/StatusContentMovementMethod.java | 103 + .../mariotaku/twidere/util/ArrayUtils.java | 265 ++ .../twidere/util/AsyncTaskManager.java | 138 + .../twidere/util/AsyncTwitterWrapper.java | 2349 ++++++++++ .../twidere/util/BitmapDecodeHelper.java | 87 + .../twidere/util/BuildProperties.java | 67 + .../twidere/util/ClipboardUtils.java | 38 + .../mariotaku/twidere/util/ColorAnalyser.java | 100 + .../mariotaku/twidere/util/CompareUtils.java | 53 + .../twidere/util/ContentValuesCreator.java | 355 ++ .../twidere/util/CustomTabUtils.java | 338 ++ .../twidere/util/DataImportExportUtils.java | 182 + .../java/org/mariotaku/twidere/util/Exif.java | 188 + .../org/mariotaku/twidere/util/FileUtils.java | 244 ++ .../twidere/util/FlowTextHelper.java | 77 + .../mariotaku/twidere/util/FlymeUtils.java | 68 + .../twidere/util/HostsFileParser.java | 88 + .../mariotaku/twidere/util/HotKeyHandler.java | 27 + .../mariotaku/twidere/util/HtmlBuilder.java | 214 + .../twidere/util/HtmlEscapeHelper.java | 46 + .../twidere/util/HtmlLinkExtractor.java | 86 + .../twidere/util/ImageLoaderUtils.java | 113 + .../twidere/util/ImageLoaderWrapper.java | 104 + .../twidere/util/ImageLoadingHandler.java | 120 + .../twidere/util/ImagePreloader.java | 81 + .../twidere/util/ImageValidator.java | 101 + .../org/mariotaku/twidere/util/ListUtils.java | 62 + .../twidere/util/LongSparseArrayUtils.java | 53 + .../org/mariotaku/twidere/util/MIUIUtils.java | 21 + .../org/mariotaku/twidere/util/MathUtils.java | 31 + .../twidere/util/MediaPreviewUtils.java | 475 ++ .../twidere/util/MediaUploaderInterface.java | 98 + .../twidere/util/MessagesManager.java | 173 + .../twidere/util/MultiSelectEventHandler.java | 254 ++ .../twidere/util/MultiSelectManager.java | 194 + .../twidere/util/NameValuePairImpl.java | 43 + .../twidere/util/NyanDrawingHelper.java | 597 +++ .../twidere/util/NyanSurfaceHelper.java | 126 + .../util/OAuthPasswordAuthenticator.java | 192 + .../util/OnDirectMessageLinkClickHandler.java | 68 + .../twidere/util/OnLinkClickHandler.java | 107 + .../mariotaku/twidere/util/ParseUtils.java | 193 + .../twidere/util/PermissionsManager.java | 148 + .../twidere/util/PositionManager.java | 49 + .../twidere/util/SQLiteDatabaseWrapper.java | 94 + .../mariotaku/twidere/util/SaveImageTask.java | 124 + .../mariotaku/twidere/util/ServiceUtils.java | 93 + .../util/SharedPreferencesWrapper.java | 109 + .../twidere/util/SpannableStringBuilder.java | 5 + .../twidere/util/StatusCodeMessageUtils.java | 85 + .../util/StatusShortenerInterface.java | 98 + .../twidere/util/StrictModeUtils.java | 81 + .../twidere/util/SwipebackActivityUtils.java | 164 + .../mariotaku/twidere/util/ThemeUtils.java | 908 ++++ .../twidere/util/TwidereLinkify.java | 338 ++ .../twidere/util/TwidereQueryBuilder.java | 176 + .../twidere/util/TwidereValidator.java | 39 + .../twidere/util/TwitterWrapper.java | 227 + .../twidere/util/UnreadCountUtils.java | 66 + .../twidere/util/UserColorNicknameUtils.java | 184 + .../org/mariotaku/twidere/util/Utils.java | 3819 +++++++++++++++++ .../util/accessor/BackStackEntryAccessor.java | 43 + .../util/accessor/ConfigurationAccessor.java | 45 + .../accessor/FragmentManagerAccessor.java | 40 + .../twidere/util/accessor/ViewAccessor.java | 52 + .../util/accessor/ViewDragHelperAccessor.java | 43 + .../util/accessor/WebSettingsAccessor.java | 41 + .../collection/NoDuplicatesArrayList.java | 70 + .../NoDuplicatesCopyOnWriteArrayList.java | 72 + .../collection/NoDuplicatesLinkedList.java | 66 + .../util/content/ContentResolverUtils.java | 108 + .../util/content/ContentValuesUtils.java | 46 + .../util/content/DatabaseUpgradeHelper.java | 190 + .../SupportActivityReloadCursorObserver.java | 61 + .../SupportFragmentReloadCursorObserver.java | 61 + .../util/content/TwidereSQLiteOpenHelper.java | 162 + .../util/imageloader/AccountExtra.java | 11 + .../imageloader/TwidereImageDownloader.java | 157 + .../imageloader/URLFileNameGenerator.java | 39 + .../util/io/ContentLengthInputStream.java | 112 + .../net/ApacheHttpClientHttpResponseImpl.java | 86 + .../util/net/HttpParameterFormEntity.java | 19 + .../util/net/TwidereHostAddressResolver.java | 216 + .../util/net/TwidereHostResolverFactory.java | 22 + .../util/net/TwidereHttpClientFactory.java | 22 + .../util/net/TwidereHttpClientImpl.java | 214 + .../ssl/AbstractCheckSignatureVerifier.java | 347 ++ ...ostResolvedSSLConnectionSocketFactory.java | 194 + .../net/ssl/TrustAllX509TrustManager.java | 20 + .../util/net/ssl/TwidereHostnameVerifier.java | 34 + .../util/net/ssl/TwidereSSLSocketFactory.java | 66 + .../util/theme/ActionIconsInterceptor.java | 115 + .../util/theme/ActivityIconsInterceptor.java | 74 + .../util/theme/TwidereAccentHelper.java | 66 + .../util/theme/TwidereResourceHelper.java | 42 + .../util/theme/WhiteDrawableInterceptor.java | 41 + .../util/webkit/DefaultWebViewClient.java | 84 + .../view/ActionBarHomeAsUpIndicator.java | 47 + .../view/ActionBarSplitThemedContainer.java | 64 + .../twidere/view/ActionBarSubtitleView.java | 46 + .../twidere/view/ActionBarTabView.java | 91 + .../view/ActionBarThemedContainer.java | 52 + .../twidere/view/ActionBarTitleView.java | 46 + .../twidere/view/ActivatedCheckBox.java | 46 + .../view/AutoAdjustHeightImageView.java | 59 + .../twidere/view/CardItemFrameLayout.java | 140 + .../twidere/view/CardItemLinearLayout.java | 140 + .../twidere/view/CardItemRelativeLayout.java | 139 + .../twidere/view/ColorLabelFrameLayout.java | 92 + .../twidere/view/ColorLabelLinearLayout.java | 92 + .../view/ColorLabelRelativeLayout.java | 93 + .../twidere/view/ColorPickerView.java | 948 ++++ .../twidere/view/ExtendedFrameLayout.java | 91 + .../twidere/view/ExtendedImageView.java | 82 + .../twidere/view/ExtendedLinearLayout.java | 92 + .../twidere/view/ExtendedRelativeLayout.java | 91 + .../twidere/view/ExtendedViewPager.java | 49 + .../twidere/view/ForegroundColorView.java | 175 + .../twidere/view/ForegroundImageView.java | 114 + .../twidere/view/HalfWidthSpace.java | 48 + .../twidere/view/HandleSpanClickTextView.java | 86 + .../twidere/view/HighlightImageView.java | 43 + .../twidere/view/HomeActionsActionView.java | 78 + .../twidere/view/ImagePreviewContainer.java | 48 + .../twidere/view/LeftDrawerFrameLayout.java | 94 + .../twidere/view/LinePageIndicator.java | 435 ++ .../twidere/view/ListMenuOverflowButton.java | 90 + .../mariotaku/twidere/view/MapImageView.java | 47 + .../view/MessageCardItemFrameLayout.java | 66 + .../twidere/view/NavigationArrowButton.java | 60 + .../twidere/view/NyanDaydreamView.java | 118 + .../twidere/view/ProfileBannerImageView.java | 126 + .../view/ProfileImageBannerLayout.java | 116 + .../twidere/view/ProfileImageView.java | 80 + .../view/RefreshNowStaggeredGridView.java | 96 + .../twidere/view/RightDrawerFrameLayout.java | 94 + .../mariotaku/twidere/view/ShortTimeView.java | 123 + .../twidere/view/SquareFrameLayout.java | 62 + .../view/SquareHighlightImageView.java | 61 + .../twidere/view/SquareImageView.java | 62 + .../twidere/view/SquareRelativeLayout.java | 62 + .../mariotaku/twidere/view/SquareView.java | 62 + .../twidere/view/SquareViewPager.java | 58 + .../twidere/view/StatusComposeEditText.java | 142 + .../twidere/view/StatusTextCountView.java | 77 + .../twidere/view/StatusTextView.java | 80 + .../twidere/view/TabPageIndicator.java | 430 ++ .../view/holder/AccountViewHolder.java | 54 + .../view/holder/ActivityViewHolder.java | 59 + .../twidere/view/holder/CardViewHolder.java | 39 + .../CheckableTwoLineWithIconViewHolder.java | 33 + .../DirectMessageConversationViewHolder.java | 75 + .../holder/DirectMessageEntryViewHolder.java | 87 + .../twidere/view/holder/DraftViewHolder.java | 51 + .../twidere/view/holder/StatusViewHolder.java | 187 + .../holder/TwoLineWithIconViewHolder.java | 37 + .../view/holder/UserListViewHolder.java | 53 + .../twidere/view/holder/UserViewHolder.java | 83 + .../twidere/view/holder/ViewHolder.java | 48 + .../twidere/view/iface/ICardItemView.java | 428 ++ .../twidere/view/iface/IColorLabelView.java | 164 + .../twidere/view/iface/IExtendedView.java | 44 + .../twidere/view/iface/IForegroundView.java | 229 + .../twidere/view/iface/PagerIndicator.java | 67 + .../themed/ThemedAutoCompleteTextView.java | 46 + .../twidere/view/themed/ThemedCheckBox.java | 47 + .../twidere/view/themed/ThemedEditText.java | 46 + .../ThemedMultiAutoCompleteTextView.java | 46 + .../view/themed/ThemedRadioButton.java | 47 + .../twidere/view/themed/ThemedSwitch.java | 49 + .../twidere/view/themed/ThemedTextView.java | 47 + .../main/java/twitter4j/AccountSettings.java | 88 + .../main/java/twitter4j/AccountTotals.java | 53 + twidere/src/main/java/twitter4j/Activity.java | 66 + twidere/src/main/java/twitter4j/Category.java | 31 + .../src/main/java/twitter4j/CursorPaging.java | 139 + .../main/java/twitter4j/CursorSupport.java | 47 + .../main/java/twitter4j/DirectMessage.java | 52 + .../main/java/twitter4j/EntitySupport.java | 61 + .../java/twitter4j/ExceptionDiagnosis.java | 106 + .../src/main/java/twitter4j/Friendship.java | 33 + .../src/main/java/twitter4j/GeoLocation.java | 97 + twidere/src/main/java/twitter4j/GeoQuery.java | 195 + .../main/java/twitter4j/HashtagEntity.java | 48 + twidere/src/main/java/twitter4j/IDs.java | 38 + twidere/src/main/java/twitter4j/Location.java | 36 + .../src/main/java/twitter4j/MediaEntity.java | 78 + .../java/twitter4j/MediaUploadResponse.java | 19 + twidere/src/main/java/twitter4j/OEmbed.java | 91 + .../main/java/twitter4j/OEmbedRequest.java | 186 + .../java/twitter4j/PagableResponseList.java | 26 + twidere/src/main/java/twitter4j/Paging.java | 240 ++ twidere/src/main/java/twitter4j/Place.java | 33 + twidere/src/main/java/twitter4j/Query.java | 528 +++ .../src/main/java/twitter4j/QueryResult.java | 42 + .../main/java/twitter4j/RateLimitStatus.java | 61 + .../java/twitter4j/RateLimitStatusEvent.java | 55 + .../twitter4j/RateLimitStatusListener.java | 38 + .../src/main/java/twitter4j/Relationship.java | 110 + twidere/src/main/java/twitter4j/ReportAs.java | 23 + .../src/main/java/twitter4j/ResponseList.java | 35 + .../src/main/java/twitter4j/SavedSearch.java | 39 + .../main/java/twitter4j/SimilarPlaces.java | 35 + twidere/src/main/java/twitter4j/Status.java | 167 + .../java/twitter4j/StatusActivitySummary.java | 19 + .../src/main/java/twitter4j/StatusUpdate.java | 275 ++ twidere/src/main/java/twitter4j/TimeZone.java | 30 + .../java/twitter4j/TranslationResult.java | 15 + twidere/src/main/java/twitter4j/Trend.java | 34 + twidere/src/main/java/twitter4j/Trends.java | 48 + twidere/src/main/java/twitter4j/Twitter.java | 48 + .../twitter4j/TwitterAPIConfiguration.java | 40 + .../java/twitter4j/TwitterAPIMonitor.java | 68 + .../src/main/java/twitter4j/TwitterBase.java | 92 + .../main/java/twitter4j/TwitterBaseImpl.java | 520 +++ .../main/java/twitter4j/TwitterConstants.java | 143 + .../main/java/twitter4j/TwitterException.java | 307 ++ .../main/java/twitter4j/TwitterFactory.java | 108 + .../src/main/java/twitter4j/TwitterImpl.java | 1662 +++++++ .../main/java/twitter4j/TwitterResponse.java | 56 + .../src/main/java/twitter4j/URLEntity.java | 68 + twidere/src/main/java/twitter4j/User.java | 204 + twidere/src/main/java/twitter4j/UserList.java | 104 + .../java/twitter4j/UserMentionEntity.java | 62 + twidere/src/main/java/twitter4j/Version.java | 42 + .../api/DirectMessagesResources.java | 151 + .../twitter4j/api/FavoritesResources.java | 148 + .../api/FriendsFollowersResources.java | 308 ++ .../java/twitter4j/api/HelpResources.java | 163 + .../java/twitter4j/api/ListsResources.java | 477 ++ .../java/twitter4j/api/MediaResources.java | 16 + .../twitter4j/api/PlacesGeoResources.java | 148 + .../twitter4j/api/SavedSearchesResources.java | 86 + .../java/twitter4j/api/SearchResource.java | 41 + .../twitter4j/api/SpamReportingResources.java | 55 + .../twitter4j/api/TimelinesResources.java | 281 ++ .../java/twitter4j/api/TrendsResources.java | 139 + .../java/twitter4j/api/TweetResources.java | 174 + .../api/UndocumentedActivityResources.java | 16 + ...UndocumentedFriendsFollowersResources.java | 16 + .../twitter4j/api/UndocumentedResources.java | 5 + .../api/UndocumentedTimelinesResources.java | 21 + .../api/UndocumentedTweetResources.java | 21 + .../java/twitter4j/api/UsersResources.java | 588 +++ .../main/java/twitter4j/auth/AccessToken.java | 104 + .../java/twitter4j/auth/Authorization.java | 36 + .../auth/AuthorizationConfiguration.java | 38 + .../twitter4j/auth/AuthorizationFactory.java | 59 + .../twitter4j/auth/BasicAuthorization.java | 88 + .../twitter4j/auth/NullAuthorization.java | 63 + .../twitter4j/auth/OAuthAuthorization.java | 562 +++ .../java/twitter4j/auth/OAuthSupport.java | 191 + .../main/java/twitter4j/auth/OAuthToken.java | 100 + .../java/twitter4j/auth/RequestToken.java | 79 + .../auth/TwipOModeAuthorization.java | 54 + .../twitter4j/auth/XAuthAuthorization.java | 65 + .../conf/BaseConfigurationFactory.java | 50 + .../java/twitter4j/conf/Configuration.java | 71 + .../twitter4j/conf/ConfigurationBase.java | 887 ++++ .../twitter4j/conf/ConfigurationBuilder.java | 309 ++ .../twitter4j/conf/ConfigurationContext.java | 32 + .../twitter4j/conf/ConfigurationFactory.java | 34 + .../java/twitter4j/http/BASE64Encoder.java | 81 + .../java/twitter4j/http/FactoryUtils.java | 17 + .../main/java/twitter4j/http/HTMLEntity.java | 905 ++++ .../twitter4j/http/HostAddressResolver.java | 9 + .../http/HostAddressResolverFactory.java | 7 + .../main/java/twitter4j/http/HttpClient.java | 31 + .../java/twitter4j/http/HttpClientBase.java | 50 + .../http/HttpClientConfiguration.java | 59 + .../twitter4j/http/HttpClientFactory.java | 27 + .../java/twitter4j/http/HttpClientImpl.java | 350 ++ .../twitter4j/http/HttpClientWrapper.java | 220 + .../http/HttpClientWrapperConfiguration.java | 26 + .../java/twitter4j/http/HttpParameter.java | 301 ++ .../main/java/twitter4j/http/HttpRequest.java | 129 + .../java/twitter4j/http/HttpResponse.java | 224 + .../java/twitter4j/http/HttpResponseCode.java | 89 + .../twitter4j/http/HttpResponseEvent.java | 93 + .../java/twitter4j/http/HttpResponseImpl.java | 68 + .../twitter4j/http/HttpResponseListener.java | 10 + .../java/twitter4j/http/RequestMethod.java | 74 + .../http/StreamingGZIPInputStream.java | 48 + .../json/AccountSettingsJSONImpl.java | 130 + .../internal/json/AccountTotalsJSONImpl.java | 108 + .../internal/json/ActivityJSONImpl.java | 210 + .../internal/json/CategoryJSONImpl.java | 119 + .../internal/json/DirectMessageJSONImpl.java | 274 ++ .../internal/json/FriendshipJSONImpl.java | 139 + .../internal/json/HashtagEntityJSONImpl.java | 123 + .../twitter4j/internal/json/IDsJSONImpl.java | 149 + .../internal/json/InternalJSONFactory.java | 138 + .../json/InternalJSONFactoryImpl.java | 390 ++ .../internal/json/LanguageJSONImpl.java | 93 + .../internal/json/LocationJSONImpl.java | 168 + .../internal/json/MediaEntityJSONImpl.java | 274 ++ .../json/MediaUploadResponseJSONImpl.java | 111 + .../internal/json/OEmbedJSONImpl.java | 143 + .../json/PagableResponseListImpl.java | 73 + .../internal/json/PlaceJSONImpl.java | 260 ++ .../internal/json/QueryResultJSONImpl.java | 203 + .../json/RateLimitStatusJSONImpl.java | 191 + .../internal/json/RelationshipJSONImpl.java | 263 ++ .../internal/json/ResponseListImpl.java | 59 + .../internal/json/SavedSearchJSONImpl.java | 161 + .../internal/json/SimilarPlacesImpl.java | 65 + .../json/StatusActivitySummaryJSONImpl.java | 125 + .../internal/json/StatusJSONImpl.java | 464 ++ .../internal/json/TimeZoneJSONImpl.java | 65 + .../json/TranslationResultJSONImpl.java | 107 + .../internal/json/TrendJSONImpl.java | 96 + .../internal/json/TrendsJSONImpl.java | 217 + .../json/TwitterAPIConfigurationJSONImpl.java | 181 + .../internal/json/TwitterResponseImpl.java | 69 + .../internal/json/URLEntityJSONImpl.java | 178 + .../twitter4j/internal/json/UserJSONImpl.java | 569 +++ .../internal/json/UserListJSONImpl.java | 258 ++ .../json/UserMentionEntityJSONImpl.java | 154 + .../internal/logging/AndroidLogger.java | 152 + .../logging/AndroidLoggerFactory.java | 39 + .../twitter4j/internal/logging/Logger.java | 117 + .../internal/logging/LoggerFactory.java | 45 + .../internal/logging/NullLogger.java | 111 + .../internal/logging/NullLoggerFactory.java | 43 + .../internal/util/InternalParseUtil.java | 227 + .../internal/util/InternalStringUtil.java | 101 + .../twitter4j/management/APIStatistics.java | 121 + .../management/APIStatisticsMBean.java | 35 + .../management/InvocationStatistics.java | 34 + .../InvocationStatisticsCalculator.java | 106 + .../java/twitter4j/util/CharacterUtil.java | 47 + .../twitter4j/util/TimeSpanConverter.java | 155 + .../main/res/anim/activity_close_enter.xml | 24 + .../src/main/res/anim/activity_close_exit.xml | 12 + .../src/main/res/anim/activity_open_enter.xml | 12 + .../src/main/res/anim/activity_open_exit.xml | 24 + .../drawable-hdpi/ab_solid_dark_holo.9.png | Bin 0 -> 146 bytes .../drawable-hdpi/ab_solid_light_holo.9.png | Bin 0 -> 145 bytes .../ab_solid_light_nocolor_holo.9.png | Bin 0 -> 211 bytes .../ab_transparent_noclor_holo.9.png | Bin 0 -> 207 bytes .../res/drawable-hdpi/bg_card_item_dark.9.png | Bin 0 -> 350 bytes .../drawable-hdpi/bg_card_item_light.9.png | Bin 0 -> 253 bytes ...d_item_message_incoming_focused_dark.9.png | Bin 0 -> 485 bytes ..._item_message_incoming_focused_light.9.png | Bin 0 -> 481 bytes ...rd_item_message_incoming_normal_dark.9.png | Bin 0 -> 472 bytes ...d_item_message_incoming_normal_light.9.png | Bin 0 -> 350 bytes ...d_item_message_incoming_pressed_dark.9.png | Bin 0 -> 454 bytes ..._item_message_incoming_pressed_light.9.png | Bin 0 -> 452 bytes ...d_item_message_outgoing_focused_dark.9.png | Bin 0 -> 468 bytes ..._item_message_outgoing_focused_light.9.png | Bin 0 -> 468 bytes ...rd_item_message_outgoing_normal_dark.9.png | Bin 0 -> 455 bytes ...d_item_message_outgoing_normal_light.9.png | Bin 0 -> 336 bytes ...d_item_message_outgoing_pressed_dark.9.png | Bin 0 -> 437 bytes ..._item_message_outgoing_pressed_light.9.png | Bin 0 -> 437 bytes ..._message_profile_image_incoming_dark.9.png | Bin 0 -> 349 bytes ...message_profile_image_incoming_light.9.png | Bin 0 -> 345 bytes ..._message_profile_image_outgoing_dark.9.png | Bin 0 -> 342 bytes ...message_profile_image_outgoing_light.9.png | Bin 0 -> 343 bytes .../drawable-hdpi/dialog_full_holo_dark.9.png | Bin 0 -> 1482 bytes .../dialog_full_holo_light.9.png | Bin 0 -> 1515 bytes ...dialog_full_holo_light_darkactionbar.9.png | Bin 0 -> 1469 bytes .../res/drawable-hdpi/expander_close_holo.png | Bin 0 -> 790 bytes .../res/drawable-hdpi/expander_open_holo.png | Bin 0 -> 796 bytes .../ic_action_assist_twidere_activated.png | Bin 0 -> 10871 bytes .../ic_action_assist_twidere_normal.png | Bin 0 -> 5595 bytes .../res/drawable-hdpi/ic_action_profile.png | Bin 0 -> 487 bytes .../res/drawable-hdpi/ic_action_reply.png | Bin 0 -> 796 bytes .../ic_indicator_conversation.png | Bin 0 -> 546 bytes .../drawable-hdpi/ic_indicator_followers.png | Bin 0 -> 518 bytes .../drawable-hdpi/ic_indicator_following.png | Bin 0 -> 459 bytes .../drawable-hdpi/ic_indicator_location.png | Bin 0 -> 476 bytes .../res/drawable-hdpi/ic_indicator_media.png | Bin 0 -> 303 bytes .../drawable-hdpi/ic_indicator_protected.png | Bin 0 -> 498 bytes .../drawable-hdpi/ic_indicator_received.png | Bin 0 -> 443 bytes .../res/drawable-hdpi/ic_indicator_reply.png | Bin 0 -> 403 bytes .../ic_indicator_reported_media.png | Bin 0 -> 483 bytes .../drawable-hdpi/ic_indicator_retweet.png | Bin 0 -> 418 bytes .../res/drawable-hdpi/ic_indicator_sent.png | Bin 0 -> 444 bytes .../drawable-hdpi/ic_indicator_starred.png | Bin 0 -> 474 bytes .../drawable-hdpi/ic_indicator_twitter.png | Bin 0 -> 503 bytes .../drawable-hdpi/ic_indicator_verified.png | Bin 0 -> 847 bytes .../res/drawable-hdpi/ic_indicator_web.png | Bin 0 -> 730 bytes .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 4815 bytes .../drawable-hdpi/ic_launcher_hondajojo.png | Bin 0 -> 11223 bytes .../ic_menu_moreoverflow_card_dark.png | Bin 0 -> 193 bytes .../ic_menu_moreoverflow_card_light.png | Bin 0 -> 181 bytes .../ic_profile_image_default.png | Bin 0 -> 2929 bytes .../main/res/drawable-hdpi/ic_stat_info.png | Bin 0 -> 841 bytes .../res/drawable-hdpi/ic_stat_mention.png | Bin 0 -> 1323 bytes .../res/drawable-hdpi/ic_stat_message.png | Bin 0 -> 543 bytes .../main/res/drawable-hdpi/ic_stat_send.png | Bin 0 -> 571 bytes .../res/drawable-hdpi/ic_stat_twitter.png | Bin 0 -> 876 bytes .../drawable-hdpi/ic_user_type_protected.png | Bin 0 -> 494 bytes .../drawable-hdpi/ic_user_type_verified.png | Bin 0 -> 739 bytes .../main/res/drawable-hdpi/image_shadow.9.png | Bin 0 -> 388 bytes .../res/drawable-hdpi/list_drag_handle.9.png | Bin 0 -> 128 bytes .../res/drawable-hdpi/overscroll_edge.png | Bin 0 -> 1169 bytes .../res/drawable-hdpi/overscroll_glow.png | Bin 0 -> 33123 bytes .../drawable-hdpi/tab_background_dark.9.png | Bin 0 -> 146 bytes ...ab_vpi_selected_focused_nocolor_holo.9.png | Bin 0 -> 175 bytes .../tab_vpi_selected_nocolor_holo.9.png | Bin 0 -> 171 bytes ...ab_vpi_selected_pressed_nocolor_holo.9.png | Bin 0 -> 177 bytes ..._vpi_unselected_focused_nocolor_holo.9.png | Bin 0 -> 167 bytes ..._vpi_unselected_pressed_nocolor_holo.9.png | Bin 0 -> 167 bytes .../vpi__tab_unselected_holo.9.png | Bin 0 -> 172 bytes .../ic_action_assist_twidere_activated.png | Bin 0 -> 6506 bytes .../ic_action_assist_twidere_normal.png | Bin 0 -> 3333 bytes .../res/drawable-mdpi/ic_action_profile.png | Bin 0 -> 349 bytes .../res/drawable-mdpi/ic_action_reply.png | Bin 0 -> 517 bytes .../drawable-mdpi/ic_extension_mentions.png | Bin 0 -> 1815 bytes .../drawable-mdpi/ic_extension_messages.png | Bin 0 -> 719 bytes .../drawable-mdpi/ic_extension_twidere.png | Bin 0 -> 900 bytes .../ic_indicator_conversation.png | Bin 0 -> 342 bytes .../drawable-mdpi/ic_indicator_followers.png | Bin 0 -> 332 bytes .../drawable-mdpi/ic_indicator_following.png | Bin 0 -> 310 bytes .../drawable-mdpi/ic_indicator_location.png | Bin 0 -> 300 bytes .../res/drawable-mdpi/ic_indicator_media.png | Bin 0 -> 222 bytes .../drawable-mdpi/ic_indicator_protected.png | Bin 0 -> 327 bytes .../drawable-mdpi/ic_indicator_received.png | Bin 0 -> 304 bytes .../res/drawable-mdpi/ic_indicator_reply.png | Bin 0 -> 271 bytes .../ic_indicator_reported_media.png | Bin 0 -> 327 bytes .../drawable-mdpi/ic_indicator_retweet.png | Bin 0 -> 285 bytes .../res/drawable-mdpi/ic_indicator_sent.png | Bin 0 -> 284 bytes .../drawable-mdpi/ic_indicator_starred.png | Bin 0 -> 321 bytes .../drawable-mdpi/ic_indicator_twitter.png | Bin 0 -> 344 bytes .../drawable-mdpi/ic_indicator_verified.png | Bin 0 -> 462 bytes .../res/drawable-mdpi/ic_indicator_web.png | Bin 0 -> 470 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 2777 bytes .../drawable-mdpi/ic_launcher_hondajojo.png | Bin 0 -> 5542 bytes ...ist_menu_moreoverflow_normal_holo_dark.png | Bin 0 -> 161 bytes ...st_menu_moreoverflow_normal_holo_light.png | Bin 0 -> 198 bytes .../ic_menu_moreoverflow_card_dark.png | Bin 0 -> 169 bytes .../ic_menu_moreoverflow_card_light.png | Bin 0 -> 180 bytes .../ic_profile_image_default.png | Bin 0 -> 1840 bytes .../main/res/drawable-mdpi/ic_stat_info.png | Bin 0 -> 502 bytes .../res/drawable-mdpi/ic_stat_mention.png | Bin 0 -> 797 bytes .../res/drawable-mdpi/ic_stat_message.png | Bin 0 -> 371 bytes .../main/res/drawable-mdpi/ic_stat_send.png | Bin 0 -> 366 bytes .../res/drawable-mdpi/ic_stat_twitter.png | Bin 0 -> 587 bytes .../drawable-mdpi/ic_user_type_protected.png | Bin 0 -> 465 bytes .../drawable-mdpi/ic_user_type_verified.png | Bin 0 -> 640 bytes .../res/drawable-mdpi/list_drag_handle.9.png | Bin 0 -> 112 bytes .../nyan_stars_background_tile.png | Bin 0 -> 458 bytes .../res/drawable-mdpi/overscroll_edge.png | Bin 0 -> 753 bytes .../res/drawable-mdpi/overscroll_glow.png | Bin 0 -> 18300 bytes .../main/res/drawable-nodpi/bg_map_bitmap.png | Bin 0 -> 729 bytes .../res/drawable-nodpi/ic_launcher_web.png | Bin 0 -> 97040 bytes .../image_preview_error_bitmap.png | Bin 0 -> 2500 bytes .../image_preview_nsfw_bitmap.png | Bin 0 -> 2148 bytes .../image_preview_refresh_bitmap.png | Bin 0 -> 2773 bytes .../nyan_rainbow_frame00_tile.png | Bin 0 -> 209 bytes .../nyan_rainbow_frame01_tile.png | Bin 0 -> 235 bytes .../nyan_rainbow_frame02_tile.png | Bin 0 -> 214 bytes .../nyan_rainbow_frame03_tile.png | Bin 0 -> 230 bytes .../nyan_rainbow_frame04_tile.png | Bin 0 -> 209 bytes .../nyan_rainbow_frame05_tile.png | Bin 0 -> 235 bytes .../nyan_rainbow_frame06_tile.png | Bin 0 -> 214 bytes .../nyan_rainbow_frame07_tile.png | Bin 0 -> 230 bytes .../nyan_rainbow_frame08_tile.png | Bin 0 -> 209 bytes .../nyan_rainbow_frame09_tile.png | Bin 0 -> 235 bytes .../nyan_rainbow_frame10_tile.png | Bin 0 -> 214 bytes .../nyan_rainbow_frame11_tile.png | Bin 0 -> 230 bytes .../drawable-nodpi/nyan_sakamoto_frame00.png | Bin 0 -> 311 bytes .../drawable-nodpi/nyan_sakamoto_frame01.png | Bin 0 -> 314 bytes .../drawable-nodpi/nyan_sakamoto_frame02.png | Bin 0 -> 312 bytes .../drawable-nodpi/nyan_sakamoto_frame03.png | Bin 0 -> 313 bytes .../drawable-nodpi/nyan_sakamoto_frame04.png | Bin 0 -> 309 bytes .../drawable-nodpi/nyan_sakamoto_frame05.png | Bin 0 -> 312 bytes .../drawable-nodpi/nyan_sakamoto_frame06.png | Bin 0 -> 311 bytes .../drawable-nodpi/nyan_sakamoto_frame07.png | Bin 0 -> 315 bytes .../drawable-nodpi/nyan_sakamoto_frame08.png | Bin 0 -> 311 bytes .../drawable-nodpi/nyan_sakamoto_frame09.png | Bin 0 -> 314 bytes .../drawable-nodpi/nyan_sakamoto_frame10.png | Bin 0 -> 314 bytes .../drawable-nodpi/nyan_sakamoto_frame11.png | Bin 0 -> 315 bytes .../nyan_sakamoto_thumbnail_bitmap.png | Bin 0 -> 892 bytes .../phishing_site_warning_example.png | Bin 0 -> 58630 bytes .../twidere_feature_graphic.png | Bin 0 -> 47172 bytes .../drawable-xhdpi/ab_solid_dark_holo.9.png | Bin 0 -> 163 bytes .../drawable-xhdpi/ab_solid_light_holo.9.png | Bin 0 -> 163 bytes .../ab_solid_light_nocolor_holo.9.png | Bin 0 -> 230 bytes .../ab_transparent_noclor_holo.9.png | Bin 0 -> 224 bytes .../drawable-xhdpi/bg_card_item_dark.9.png | Bin 0 -> 415 bytes .../drawable-xhdpi/bg_card_item_light.9.png | Bin 0 -> 312 bytes ...d_item_message_incoming_focused_dark.9.png | Bin 0 -> 592 bytes ..._item_message_incoming_focused_light.9.png | Bin 0 -> 587 bytes ...em_message_incoming_longpressed_dark.9.png | Bin 0 -> 564 bytes ...m_message_incoming_longpressed_light.9.png | Bin 0 -> 563 bytes ...rd_item_message_incoming_normal_dark.9.png | Bin 0 -> 584 bytes ...d_item_message_incoming_normal_light.9.png | Bin 0 -> 474 bytes ...d_item_message_incoming_pressed_dark.9.png | Bin 0 -> 565 bytes ..._item_message_incoming_pressed_light.9.png | Bin 0 -> 563 bytes ...d_item_message_outgoing_focused_dark.9.png | Bin 0 -> 564 bytes ..._item_message_outgoing_focused_light.9.png | Bin 0 -> 563 bytes ...em_message_outgoing_longpressed_dark.9.png | Bin 0 -> 525 bytes ...m_message_outgoing_longpressed_light.9.png | Bin 0 -> 525 bytes ...rd_item_message_outgoing_normal_dark.9.png | Bin 0 -> 543 bytes ...d_item_message_outgoing_normal_light.9.png | Bin 0 -> 460 bytes ...d_item_message_outgoing_pressed_dark.9.png | Bin 0 -> 525 bytes ..._item_message_outgoing_pressed_light.9.png | Bin 0 -> 525 bytes ..._message_profile_image_incoming_dark.9.png | Bin 0 -> 406 bytes ...message_profile_image_incoming_light.9.png | Bin 0 -> 402 bytes ..._message_profile_image_outgoing_dark.9.png | Bin 0 -> 410 bytes ...message_profile_image_outgoing_light.9.png | Bin 0 -> 413 bytes .../dialog_full_holo_dark.9.png | Bin 0 -> 2201 bytes .../dialog_full_holo_light.9.png | Bin 0 -> 2231 bytes ...dialog_full_holo_light_darkactionbar.9.png | Bin 0 -> 2210 bytes .../drawable-xhdpi/expander_close_holo.png | Bin 0 -> 1061 bytes .../res/drawable-xhdpi/expander_open_holo.png | Bin 0 -> 1067 bytes .../ic_action_assist_twidere_activated.png | Bin 0 -> 16125 bytes .../ic_action_assist_twidere_normal.png | Bin 0 -> 8355 bytes .../res/drawable-xhdpi/ic_action_profile.png | Bin 0 -> 649 bytes .../res/drawable-xhdpi/ic_action_reply.png | Bin 0 -> 1047 bytes .../src/main/res/drawable-xhdpi/ic_file.png | Bin 0 -> 330 bytes .../src/main/res/drawable-xhdpi/ic_folder.png | Bin 0 -> 334 bytes .../ic_indicator_conversation.png | Bin 0 -> 789 bytes .../drawable-xhdpi/ic_indicator_followers.png | Bin 0 -> 751 bytes .../drawable-xhdpi/ic_indicator_following.png | Bin 0 -> 553 bytes .../drawable-xhdpi/ic_indicator_location.png | Bin 0 -> 640 bytes .../res/drawable-xhdpi/ic_indicator_media.png | Bin 0 -> 341 bytes .../drawable-xhdpi/ic_indicator_protected.png | Bin 0 -> 512 bytes .../drawable-xhdpi/ic_indicator_received.png | Bin 0 -> 471 bytes .../res/drawable-xhdpi/ic_indicator_reply.png | Bin 0 -> 459 bytes .../ic_indicator_reported_media.png | Bin 0 -> 619 bytes .../drawable-xhdpi/ic_indicator_retweet.png | Bin 0 -> 499 bytes .../res/drawable-xhdpi/ic_indicator_sent.png | Bin 0 -> 482 bytes .../drawable-xhdpi/ic_indicator_starred.png | Bin 0 -> 644 bytes .../drawable-xhdpi/ic_indicator_twitter.png | Bin 0 -> 682 bytes .../drawable-xhdpi/ic_indicator_verified.png | Bin 0 -> 892 bytes .../res/drawable-xhdpi/ic_indicator_web.png | Bin 0 -> 1086 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 7753 bytes .../drawable-xhdpi/ic_launcher_hondajojo.png | Bin 0 -> 17642 bytes ...ist_menu_moreoverflow_normal_holo_dark.png | Bin 0 -> 182 bytes ...st_menu_moreoverflow_normal_holo_light.png | Bin 0 -> 225 bytes .../main/res/drawable-xhdpi/ic_map_marker.png | Bin 0 -> 3398 bytes .../ic_menu_moreoverflow_card_dark.png | Bin 0 -> 193 bytes .../ic_menu_moreoverflow_card_light.png | Bin 0 -> 209 bytes .../ic_profile_image_default.png | Bin 0 -> 4029 bytes .../drawable-xhdpi/ic_stat_direct_message.png | Bin 0 -> 724 bytes .../main/res/drawable-xhdpi/ic_stat_info.png | Bin 0 -> 1017 bytes .../res/drawable-xhdpi/ic_stat_mention.png | Bin 0 -> 1769 bytes .../res/drawable-xhdpi/ic_stat_message.png | Bin 0 -> 720 bytes .../main/res/drawable-xhdpi/ic_stat_send.png | Bin 0 -> 733 bytes .../res/drawable-xhdpi/ic_stat_twitter.png | Bin 0 -> 1186 bytes .../drawable-xhdpi/ic_user_type_protected.png | Bin 0 -> 735 bytes .../drawable-xhdpi/ic_user_type_verified.png | Bin 0 -> 1251 bytes .../res/drawable-xhdpi/image_shadow.9.png | Bin 0 -> 777 bytes .../res/drawable-xhdpi/list_drag_handle.9.png | Bin 0 -> 139 bytes .../res/drawable-xhdpi/overscroll_edge.png | Bin 0 -> 1693 bytes .../res/drawable-xhdpi/overscroll_glow.png | Bin 0 -> 46001 bytes .../drawable-xhdpi/tab_background_dark.9.png | Bin 0 -> 163 bytes ...ab_vpi_selected_focused_nocolor_holo.9.png | Bin 0 -> 177 bytes .../tab_vpi_selected_nocolor_holo.9.png | Bin 0 -> 172 bytes ...ab_vpi_selected_pressed_nocolor_holo.9.png | Bin 0 -> 178 bytes ..._vpi_unselected_focused_nocolor_holo.9.png | Bin 0 -> 160 bytes ..._vpi_unselected_pressed_nocolor_holo.9.png | Bin 0 -> 160 bytes .../drawable-xxhdpi/bg_card_item_dark.9.png | Bin 0 -> 615 bytes .../drawable-xxhdpi/bg_card_item_light.9.png | Bin 0 -> 603 bytes .../ic_action_assist_twidere_activated.png | Bin 0 -> 27908 bytes .../ic_action_assist_twidere_normal.png | Bin 0 -> 14694 bytes .../res/drawable-xxhdpi/ic_action_profile.png | Bin 0 -> 1012 bytes .../res/drawable-xxhdpi/ic_action_reply.png | Bin 0 -> 1669 bytes .../drawable-xxhdpi/ic_extension_mentions.png | Bin 0 -> 6266 bytes .../drawable-xxhdpi/ic_extension_messages.png | Bin 0 -> 2435 bytes .../drawable-xxhdpi/ic_extension_twidere.png | Bin 0 -> 3215 bytes .../ic_indicator_conversation.png | Bin 0 -> 1250 bytes .../ic_indicator_followers.png | Bin 0 -> 1238 bytes .../ic_indicator_following.png | Bin 0 -> 739 bytes .../drawable-xxhdpi/ic_indicator_location.png | Bin 0 -> 979 bytes .../drawable-xxhdpi/ic_indicator_media.png | Bin 0 -> 503 bytes .../ic_indicator_protected.png | Bin 0 -> 784 bytes .../drawable-xxhdpi/ic_indicator_received.png | Bin 0 -> 777 bytes .../drawable-xxhdpi/ic_indicator_reply.png | Bin 0 -> 767 bytes .../ic_indicator_reported_media.png | Bin 0 -> 907 bytes .../drawable-xxhdpi/ic_indicator_retweet.png | Bin 0 -> 733 bytes .../res/drawable-xxhdpi/ic_indicator_sent.png | Bin 0 -> 733 bytes .../drawable-xxhdpi/ic_indicator_starred.png | Bin 0 -> 968 bytes .../drawable-xxhdpi/ic_indicator_twitter.png | Bin 0 -> 1039 bytes .../drawable-xxhdpi/ic_indicator_verified.png | Bin 0 -> 1680 bytes .../res/drawable-xxhdpi/ic_indicator_web.png | Bin 0 -> 1882 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 14902 bytes .../drawable-xxhdpi/ic_launcher_hondajojo.png | Bin 0 -> 36108 bytes ...ist_menu_moreoverflow_normal_holo_dark.png | Bin 0 -> 205 bytes ...st_menu_moreoverflow_normal_holo_light.png | Bin 0 -> 264 bytes .../ic_menu_moreoverflow_card_dark.png | Bin 0 -> 252 bytes .../ic_menu_moreoverflow_card_light.png | Bin 0 -> 268 bytes .../ic_profile_image_default.png | Bin 0 -> 6520 bytes .../main/res/drawable-xxhdpi/ic_stat_info.png | Bin 0 -> 1802 bytes .../res/drawable-xxhdpi/ic_stat_mention.png | Bin 0 -> 2784 bytes .../res/drawable-xxhdpi/ic_stat_message.png | Bin 0 -> 894 bytes .../main/res/drawable-xxhdpi/ic_stat_send.png | Bin 0 -> 1238 bytes .../res/drawable-xxhdpi/ic_stat_twitter.png | Bin 0 -> 1273 bytes .../ic_user_type_protected.png | Bin 0 -> 784 bytes .../drawable-xxhdpi/ic_user_type_verified.png | Bin 0 -> 1373 bytes .../drawable-xxhdpi/list_drag_handle.9.png | Bin 0 -> 172 bytes .../main/res/drawable-xxxhdpi/ic_launcher.png | Bin 0 -> 22502 bytes .../drawable/ab_twidere_solid_color_holo.xml | 8 + .../drawable/ab_twidere_solid_dark_holo.xml | 9 + .../drawable/ab_twidere_solid_light_holo.xml | 9 + .../bg_card_item_message_incoming_dark.xml | 9 + .../bg_card_item_message_incoming_light.xml | 9 + ..._message_incoming_transition_holo_dark.xml | 7 + ...message_incoming_transition_holo_light.xml | 7 + .../bg_card_item_message_outgoing_dark.xml | 9 + .../bg_card_item_message_outgoing_light.xml | 9 + ..._message_outgoing_transition_holo_dark.xml | 7 + ...message_outgoing_transition_holo_light.xml | 7 + twidere/src/main/res/drawable/bg_map.xml | 7 + .../divider_horizontal_small_holo.xml | 8 + .../drawable/divider_vertical_small_holo.xml | 8 + .../res/drawable/focused_background_white.xml | 21 + .../main/res/drawable/ic_assist_twidere.xml | 23 + .../drawable/ic_launcher_twidere_iconic.xml | 6 + .../src/main/res/drawable/ic_menu_gallery.xml | 6 + .../main/res/drawable/ic_menu_preferences.xml | 6 + .../src/main/res/drawable/ic_menu_send.xml | 6 + .../ic_profile_image_default_iconic.xml | 6 + .../main/res/drawable/image_preview_error.xml | 7 + .../main/res/drawable/image_preview_nsfw.xml | 7 + .../res/drawable/image_preview_refresh.xml | 7 + .../drawable/list_selector_light_white.xml | 28 + .../list_selector_transition_white.xml | 22 + .../main/res/drawable/list_selector_white.xml | 28 + .../src/main/res/drawable/nyan_sakamoto.xml | 78 + .../res/drawable/nyan_sakamoto_thumbnail.xml | 6 + .../res/drawable/nyan_stars_background.xml | 6 + .../src/main/res/drawable/shadow_bottom.xml | 9 + twidere/src/main/res/drawable/shadow_left.xml | 9 + .../src/main/res/drawable/shadow_right.xml | 9 + twidere/src/main/res/drawable/shadow_top.xml | 10 + .../drawable/tab_indicator_vpi_nocolor.xml | 37 + .../src/main/res/layout-large/home_tabs.xml | 29 + .../main/res/layout/action_item_compose.xml | 28 + .../res/layout/action_item_home_actions.xml | 20 + .../action_item_home_progress_smartbar.xml | 13 + .../main/res/layout/action_item_switch.xml | 14 + .../res/layout/activity_account_selector.xml | 39 + .../res/layout/activity_activity_picker.xml | 12 + .../main/res/layout/activity_api_editor.xml | 29 + .../res/layout/activity_browser_sign_in.xml | 24 + .../src/main/res/layout/activity_compose.xml | 63 + .../res/layout/activity_compose_actionbar.xml | 87 + .../res/layout/activity_compose_bottombar.xml | 50 + .../res/layout/activity_custom_tab_editor.xml | 145 + .../src/main/res/layout/activity_filters.xml | 11 + twidere/src/main/res/layout/activity_home.xml | 13 + .../activity_image_viewer_bottombar.xml | 5 + .../res/layout/activity_settings_wizard.xml | 22 + .../src/main/res/layout/activity_sign_in.xml | 98 + .../layout/activity_user_list_selector.xml | 101 + .../res/layout/api_editor_advanced_fields.xml | 47 + .../main/res/layout/api_editor_content.xml | 103 + .../res/layout/auto_complete_textview.xml | 14 + .../res/layout/base_multi_column_list.xml | 6 + .../main/res/layout/card_item_activity.xml | 201 + .../res/layout/card_item_activity_compact.xml | 208 + .../src/main/res/layout/card_item_draft.xml | 62 + .../layout/card_item_message_conversation.xml | 180 + .../res/layout/card_item_message_entry.xml | 94 + .../card_item_message_entry_compact.xml | 97 + .../src/main/res/layout/card_item_status.xml | 152 + .../res/layout/card_item_status_compact.xml | 160 + .../src/main/res/layout/card_item_user.xml | 131 + .../res/layout/card_item_user_compact.xml | 143 + .../main/res/layout/card_item_user_list.xml | 101 + .../layout/card_item_user_list_compact.xml | 109 + .../main/res/layout/dialog_color_picker.xml | 22 + .../main/res/layout/dialog_host_mapping.xml | 25 + .../res/layout/dialog_scrollable_status.xml | 9 + .../res/layout/dialog_translate_status.xml | 33 + .../main/res/layout/drawer_home_accounts.xml | 15 + .../res/layout/drawer_home_quick_menu.xml | 15 + .../main/res/layout/edit_user_list_detail.xml | 39 + .../src/main/res/layout/edit_user_profile.xml | 134 + .../src/main/res/layout/empty_tab_hint.xml | 14 + .../res/layout/fragment_accounts_drawer.xml | 7 + .../res/layout/fragment_characters_grid.xml | 9 + .../main/res/layout/fragment_custom_tabs.xml | 20 + .../fragment_data_profiling_settings.xml | 69 + .../res/layout/fragment_details_bottombar.xml | 6 + .../main/res/layout/fragment_details_page.xml | 58 + .../src/main/res/layout/fragment_donate.xml | 80 + .../layout/fragment_messages_conversation.xml | 65 + ...gment_messages_conversation_input_send.xml | 61 + .../main/res/layout/fragment_quick_menu.xml | 65 + .../src/main/res/layout/fragment_search.xml | 20 + .../src/main/res/layout/fragment_webview.xml | 5 + .../layout/fragment_wizard_page_finished.xml | 62 + .../gallery_item_color_picker_preset.xml | 17 + .../layout/gallery_item_compose_account.xml | 26 + .../res/layout/gallery_item_image_preview.xml | 25 + twidere/src/main/res/layout/gl_root_group.xml | 31 + .../res/layout/grid_item_image_preview.xml | 25 + .../res/layout/grid_item_media_editor.xml | 6 + .../res/layout/header_hidden_settings.xml | 24 + twidere/src/main/res/layout/header_status.xml | 245 ++ .../res/layout/header_user_list_details.xml | 94 + .../main/res/layout/header_user_profile.xml | 300 ++ .../res/layout/header_user_profile_banner.xml | 7 + .../main/res/layout/header_wizard_page.xml | 34 + .../res/layout/home_actions_button_layout.xml | 8 + twidere/src/main/res/layout/home_tabs.xml | 40 + .../main/res/layout/image_preview_grid.xml | 31 + .../src/main/res/layout/image_viewer_gl.xml | 26 + twidere/src/main/res/layout/invalid_tab.xml | 13 + .../res/layout/link_handler_actionbar.xml | 24 + .../src/main/res/layout/list_action_item.xml | 44 + .../src/main/res/layout/list_item_account.xml | 82 + .../main/res/layout/list_item_custom_tab.xml | 52 + .../res/layout/list_item_drawer_accounts.xml | 106 + .../res/layout/list_item_extra_config.xml | 33 + .../src/main/res/layout/list_item_menu.xml | 39 + .../layout/list_item_preference_header.xml | 65 + .../res/layout/list_item_section_header.xml | 8 + .../main/res/layout/list_item_separator.xml | 6 + .../main/res/layout/list_item_two_line.xml | 39 + .../res/layout/list_item_two_line_checked.xml | 53 + .../res/layout/list_item_two_line_small.xml | 39 + twidere/src/main/res/layout/nyan_daydream.xml | 6 + .../main/res/layout/phishing_link_warning.xml | 28 + .../res/layout/preference_seek_bar_dialog.xml | 32 + .../layout/refreshnow_staggered_gridview.xml | 7 + .../main/res/layout/request_permissions.xml | 101 + .../settings_layout_click_to_config.xml | 16 + .../settings_layout_wizard_page_nav.xml | 19 + .../layout/spinner_item_custom_tab_icon.xml | 13 + .../main/res/layout/staggered_gridview.xml | 7 + twidere/src/main/res/layout/surface_view.xml | 5 + .../src/main/res/layout/swipeback_layout.xml | 6 + twidere/src/main/res/layout/tab_item_ab.xml | 22 + twidere/src/main/res/layout/tab_item_vpi.xml | 31 + twidere/src/main/res/layout/theme_preview.xml | 20 + .../main/res/layout/theme_preview_content.xml | 57 + .../layout/theme_preview_status_content.xml | 32 + .../res/layout/theme_preview_status_list.xml | 9 + .../main/res/menu/action_direct_message.xml | 23 + .../src/main/res/menu/action_extension.xml | 17 + .../res/menu/action_incoming_friendship.xml | 13 + .../res/menu/action_multi_select_contents.xml | 61 + .../res/menu/action_multi_select_drafts.xml | 15 + .../res/menu/action_multi_select_items.xml | 10 + .../res/menu/action_profile_banner_image.xml | 27 + .../main/res/menu/action_profile_image.xml | 13 + twidere/src/main/res/menu/action_status.xml | 46 + .../res/menu/action_status_text_selection.xml | 10 + .../src/main/res/menu/action_user_list.xml | 13 + .../main/res/menu/action_user_list_member.xml | 9 + twidere/src/main/res/menu/menu_compose.xml | 37 + .../src/main/res/menu/menu_custom_tabs.xml | 12 + .../main/res/menu/menu_edit_user_profile.xml | 10 + .../src/main/res/menu/menu_file_picker.xml | 10 + twidere/src/main/res/menu/menu_filters.xml | 28 + twidere/src/main/res/menu/menu_home.xml | 15 + .../src/main/res/menu/menu_host_mapping.xml | 10 + .../src/main/res/menu/menu_image_viewer.xml | 22 + .../res/menu/menu_image_viewer_action_bar.xml | 10 + twidere/src/main/res/menu/menu_map_viewer.xml | 10 + twidere/src/main/res/menu/menu_search.xml | 10 + twidere/src/main/res/menu/menu_settings.xml | 21 + twidere/src/main/res/menu/menu_sign_in.xml | 20 + twidere/src/main/res/menu/menu_status.xml | 67 + .../main/res/menu/menu_switch_preference.xml | 10 + twidere/src/main/res/menu/menu_user_list.xml | 27 + .../main/res/menu/menu_user_list_created.xml | 11 + .../src/main/res/menu/menu_user_profile.xml | 59 + twidere/src/main/res/values-ar/strings.xml | 524 +++ .../res/values-ar/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-ca/strings.xml | 613 +++ .../res/values-ca/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-cs/strings.xml | 97 + .../res/values-cs/strings_twitter_errors.xml | 11 + twidere/src/main/res/values-de/strings.xml | 606 +++ .../res/values-de/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-el/strings.xml | 16 + twidere/src/main/res/values-es/strings.xml | 625 +++ .../res/values-es/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-fi/strings.xml | 625 +++ .../res/values-fi/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-fr/strings.xml | 612 +++ .../res/values-fr/strings_twitter_errors.xml | 18 + .../res/values-hdpi/strings_notranslate.xml | 6 + twidere/src/main/res/values-hi/strings.xml | 155 + twidere/src/main/res/values-hu/strings.xml | 280 ++ twidere/src/main/res/values-in/strings.xml | 612 +++ .../res/values-in/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-it/strings.xml | 584 +++ .../res/values-it/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-iw/strings.xml | 133 + twidere/src/main/res/values-ja/strings.xml | 625 +++ .../res/values-ja/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-ko/strings.xml | 608 +++ .../res/values-ko/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-land/bools.xml | 8 + twidere/src/main/res/values-land/dimens.xml | 7 + twidere/src/main/res/values-land/integers.xml | 8 + .../src/main/res/values-large-land/bools.xml | 6 + .../src/main/res/values-large-land/dimens.xml | 7 + .../main/res/values-large-land/integers.xml | 8 + twidere/src/main/res/values-large/bools.xml | 7 + .../src/main/res/values-large/defaults.xml | 6 + twidere/src/main/res/values-large/dimens.xml | 24 + .../src/main/res/values-large/integers.xml | 17 + twidere/src/main/res/values-ms/strings.xml | 39 + twidere/src/main/res/values-nl/strings.xml | 606 +++ .../res/values-nl/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-no/strings.xml | 79 + .../res/values-no/strings_twitter_errors.xml | 5 + twidere/src/main/res/values-pl/strings.xml | 585 +++ .../res/values-pl/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-pt/strings.xml | 627 +++ .../res/values-pt/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-ru/strings.xml | 589 +++ .../res/values-ru/strings_twitter_errors.xml | 19 + twidere/src/main/res/values-sw600dp/bools.xml | 7 + twidere/src/main/res/values-th/strings.xml | 388 ++ .../res/values-th/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-tr/strings.xml | 460 ++ twidere/src/main/res/values-uk/strings.xml | 602 +++ .../res/values-uk/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-v16/bools.xml | 6 + .../res/values-xhdpi/strings_notranslate.xml | 6 + .../main/res/values-xlarge-land/dimens.xml | 7 + .../main/res/values-xlarge-land/integers.xml | 8 + twidere/src/main/res/values-xlarge/dimens.xml | 18 + .../src/main/res/values-xlarge/integers.xml | 14 + .../src/main/res/values-zh-rCN/strings.xml | 643 +++ .../values-zh-rCN/strings_twitter_errors.xml | 18 + twidere/src/main/res/values-zh/strings.xml | 604 +++ .../res/values-zh/strings_twitter_errors.xml | 18 + twidere/src/main/res/values/arrays.xml | 160 + twidere/src/main/res/values/attrs.xml | 58 + twidere/src/main/res/values/bools.xml | 11 + twidere/src/main/res/values/colors.xml | 18 + twidere/src/main/res/values/defaults.xml | 6 + twidere/src/main/res/values/dimens.xml | 68 + .../src/main/res/values/drawables_iconic.xml | 65 + .../main/res/values/drawables_intercepted.xml | 8 + twidere/src/main/res/values/fractions.xml | 7 + twidere/src/main/res/values/gallery_attrs.xml | 20 + twidere/src/main/res/values/ids.xml | 78 + twidere/src/main/res/values/integers.xml | 23 + twidere/src/main/res/values/plurals.xml | 42 + twidere/src/main/res/values/strings.xml | 646 +++ .../main/res/values/strings_http_errors.xml | 7 + .../main/res/values/strings_notranslate.xml | 17 + .../res/values/strings_twitter_errors.xml | 33 + twidere/src/main/res/values/styles.xml | 145 + .../src/main/res/values/swipeback_attrs.xml | 22 + twidere/src/main/res/values/themes.xml | 493 +++ twidere/src/main/res/values/vpi__defaults.xml | 13 + twidere/src/main/res/xml/about.xml | 120 + twidere/src/main/res/xml/nyan_wallpaper.xml | 3 + twidere/src/main/res/xml/searchable.xml | 10 + .../xml/settings_account_notifications.xml | 54 + .../main/res/xml/settings_account_refresh.xml | 26 + twidere/src/main/res/xml/settings_cards.xml | 99 + twidere/src/main/res/xml/settings_content.xml | 118 + twidere/src/main/res/xml/settings_headers.xml | 104 + twidere/src/main/res/xml/settings_hidden.xml | 24 + .../src/main/res/xml/settings_interface.xml | 53 + twidere/src/main/res/xml/settings_network.xml | 86 + .../main/res/xml/settings_notifications.xml | 27 + twidere/src/main/res/xml/settings_other.xml | 49 + twidere/src/main/res/xml/settings_refresh.xml | 44 + twidere/src/main/res/xml/settings_storage.xml | 33 + twidere/src/main/res/xml/settings_theme.xml | 69 + .../res/xml/settings_wizard_page_hints.xml | 26 + .../main/res/xml/settings_wizard_page_tab.xml | 14 + .../res/xml/settings_wizard_page_welcome.xml | 18 + twidere/twidere.iml | 87 + 1378 files changed, 146052 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/libraries/acra_4_5_0.xml create mode 100644 .idea/libraries/dashclock_api_2_0_0.xml create mode 100644 .idea/libraries/dnsjava_ipv6_1_0_with_sources.xml create mode 100644 .idea/libraries/drag_sort_listview_apklib_1_0.xml create mode 100644 .idea/libraries/httpclient_android_4_3_3.xml create mode 100644 .idea/libraries/httpmime_4_3_3.xml create mode 100644 .idea/libraries/jsonserializer_1_0.xml create mode 100644 .idea/libraries/library_1_0.xml create mode 100644 .idea/libraries/library_1_0_5.xml create mode 100644 .idea/libraries/library_2_0_0.xml create mode 100644 .idea/libraries/library_2_4_0.xml create mode 100644 .idea/libraries/library_2_4_1.xml create mode 100644 .idea/libraries/merge_1_0_1.xml create mode 100644 .idea/libraries/sacklist_1_0_0.xml create mode 100644 .idea/libraries/snakeyaml_1_12.xml create mode 100644 .idea/libraries/support_annotations_20_0_0.xml create mode 100644 .idea/libraries/support_v13_20_0_0.xml create mode 100644 .idea/libraries/support_v4_20_0_0.xml create mode 100644 .idea/libraries/support_v4_r7.xml create mode 100644 .idea/libraries/twitter_text_1_9_5.xml create mode 100644 .idea/libraries/universal_image_loader_1_9_2.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 Twidere-Android.iml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 twidere/.gitignore create mode 100644 twidere/build.gradle create mode 100644 twidere/libs/dnsjava-ipv6-1.0-with-sources.jar create mode 100644 twidere/libs/jsonserializer-1.0.jar create mode 100644 twidere/libs/merge-1.0.1.jar create mode 100644 twidere/libs/sacklist-1.0.0.jar create mode 100644 twidere/proguard-rules.pro create mode 100644 twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java create mode 100644 twidere/src/assets/fonts/TwidereIconic.ttf create mode 100644 twidere/src/assets/gpl-3.0-standalone.html create mode 100644 twidere/src/assets/images/loading_tile.png create mode 100644 twidere/src/assets/mapview.html create mode 100644 twidere/src/main/AndroidManifest.xml create mode 100755 twidere/src/main/java/android/support/v4/app/BackStackEntryTrojan.java create mode 100755 twidere/src/main/java/android/support/v4/app/FragmentManagerTrojan.java create mode 100644 twidere/src/main/java/android/support/v4/app/FragmentTrojan.java create mode 100755 twidere/src/main/java/android/support/v4/app/ListFragmentTrojan.java create mode 100644 twidere/src/main/java/com/atermenji/android/iconicdroid/IconicFontDrawable.java create mode 100644 twidere/src/main/java/com/atermenji/android/iconicdroid/icon/Icon.java create mode 100644 twidere/src/main/java/com/atermenji/android/iconicdroid/util/TypefaceManager.java create mode 100644 twidere/src/main/java/com/readystatesoftware/viewbadger/BadgeView.java create mode 100644 twidere/src/main/java/com/scvngr/levelup/views/gallery/AbsSpinner.java create mode 100644 twidere/src/main/java/com/scvngr/levelup/views/gallery/AdapterView.java create mode 100644 twidere/src/main/java/com/scvngr/levelup/views/gallery/Gallery.java create mode 100644 twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/Crouton.java create mode 100644 twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonConfiguration.java create mode 100644 twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonLifecycleCallback.java create mode 100644 twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonManager.java create mode 100644 twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonStyle.java create mode 100644 twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/DefaultAnimationsBuilder.java create mode 100644 twidere/src/main/java/edu/ucdavis/earlybird/CSVFileFilter.java create mode 100644 twidere/src/main/java/edu/ucdavis/earlybird/ProfilingUtil.java create mode 100644 twidere/src/main/java/edu/ucdavis/earlybird/UCDService.java create mode 100644 twidere/src/main/java/edu/ucdavis/earlybird/UploadReceiver.java create mode 100644 twidere/src/main/java/edu/ucdavis/earlybird/UploadTask.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/ScaleGestureDetector.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java create mode 100644 twidere/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java create mode 100644 twidere/src/main/java/me/imid/swipebacklayout/lib/SwipeBackLayout.java create mode 100644 twidere/src/main/java/me/imid/swipebacklayout/lib/ViewDragHelper.java create mode 100644 twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivity.java create mode 100644 twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityBase.java create mode 100644 twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityHelper.java create mode 100644 twidere/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java create mode 100644 twidere/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java create mode 100644 twidere/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java create mode 100644 twidere/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java create mode 100644 twidere/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java create mode 100644 twidere/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java create mode 100644 twidere/src/main/java/org/mariotaku/dynamicgridview/DraggableAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/dynamicgridview/DraggableArrayAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/dynamicgridview/DynamicGridView.java create mode 100644 twidere/src/main/java/org/mariotaku/dynamicgridview/DynamicListView.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/GLImageLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/PhotoViewAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/anim/Animation.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/anim/CanvasAnimation.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/AnimationTime.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapScreenNail.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/BitmapTexture.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/DownUpDetector.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeEffect.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/EdgeView.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/FlingScroller.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvas.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLCanvasImpl.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLId.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRoot.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLRootView.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GLView.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GalleryEGLConfigChooser.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/GestureRecognizer.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/PositionController.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/ResourceTexture.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/ScreenNail.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/SynchronizedHandler.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/Texture.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/ui/UploadedTexture.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/ApiHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapPool.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/DecodeUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/Future.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/FutureListener.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/IntArray.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/LongSparseArray.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/MotionEventHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/PriorityThreadFactory.java create mode 100644 twidere/src/main/java/org/mariotaku/gallery3d/util/ThreadPool.java create mode 100644 twidere/src/main/java/org/mariotaku/harmony/view/HorizontalGridView.java create mode 100644 twidere/src/main/java/org/mariotaku/harmony/view/HorizontalListView.java create mode 100644 twidere/src/main/java/org/mariotaku/harmony/view/HorizontalWrapperAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/jsonserializer/JSONFileIO.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/AllColumns.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/Columns.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/NewColumn.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/OrderBy.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/RawItemArray.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/SQLFunctions.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/SQLLang.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/SQLQueryBuilder.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/SQLQueryException.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/Selectable.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/Tables.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/Utils.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/Where.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/IBuilder.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLAlterTableQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLCreateTableQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLCreateViewQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLDeleteQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLDropQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLDropTableQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLDropViewQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLInsertIntoQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/querybuilder/query/SQLSelectQuery.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/Constants.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/IMediaUploader.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/IStatusShortener.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/ITimelineSyncHelper.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/TwidereConstants.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/AssistLauncherActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/BaseActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/BasePreferenceActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/BaseThemedActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/CameraCropActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/CreateComposeShortcutActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/CustomTabsActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/DataProfilingSettingsActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/FiltersActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/MainActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/MainHondaJOJOActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/NyanActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/SettingsActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/SettingsWizardActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/TestActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/TwitterLinkHandlerActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/iface/IThemedActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/APIEditorActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/AccountSelectorActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/ActivityPickerActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportDialogActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedSwipeBackActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/BrowserActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/BrowserSignInActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/ColorPickerDialogActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/CustomTabEditorActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/DataExportActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/DataImportActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/DraftsActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/FileSelectorActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/ImagePickerActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/LinkHandlerActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/MapViewerActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/MenuDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/RequestPermissionsActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/SignInActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/TwidereSwipeBackActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/UserProfileEditorActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/AccountsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/AccountsSpinnerAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ArrayAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/BaseArrayAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/BaseCursorAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/BaseParcelableActivitiesAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/CursorStatusesAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/DirectMessageConversationEntriesAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/DirectMessagesConversationAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/DraftsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ExtensionsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ListActionAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/MediaPreviewAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAboutMeAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesByFriendsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableUserListsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableUsersAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/ResolveInfoListAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/SectionArrayAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/SeparatedListAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/SimpleParcelableUserListsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/SimpleParcelableUsersAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/SourceAutoCompleteAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/TabsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/UserHashtagAutoCompleteAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IBaseAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IBaseCardAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IDirectMessagesAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/support/SupportFixedFragmentStatePagerAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/adapter/support/SupportTabsAdapter.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/animation/CardItemAnimation.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/animation/Rotate3dAnimation.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/annotation/Preference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/app/TwidereApplication.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/backup/TwidereBackupAgentHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/constant/IntentConstants.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/constant/SharedPreferenceConstants.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/content/TwidereContextThemeWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/content/TwidereContextWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/content/iface/ITwidereContextWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/content/res/TwidereAccentResources.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/content/res/TwidereResources.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/content/res/iface/IThemedResources.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/dialog/ColorPickerDialog.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/AccountNotificationSettingsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/AccountRefreshSettingsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BaseAccountPreferenceFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BaseDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BaseFiltersFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BaseFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BaseListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BasePreferenceFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BaseWebViewFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/BrowserFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/CustomTabsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/DataProfilingSettingsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/ExtensionsListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/HostMappingsListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/PayPalDonateFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/ProgressDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/SettingsDetailsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/SettingsEntriesFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/IBaseFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/IBasePullToRefreshFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/IDialogFragmentCallback.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/IMapFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/ISupportDialogFragmentCallback.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/RefreshScrollTopInterface.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/iface/SupportFragmentCallback.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDrawerFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/ActivitiesAboutMeFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/ActivitiesByFriendsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/AddStatusFilterDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/AddUserListMemberDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseActivitiesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BasePullToRefreshListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BasePullToRefreshStaggeredGridFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseStatusesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseStatusesStaggeredGridFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportStaggeredGridFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportWebViewFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUsersListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/ColorPickerDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/CreateUserBlockDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/CreateUserListDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorStatusesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorStatusesStaggeredGridFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorSupportUsersListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DataExportImportTypeSelectorDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DeleteUserListMembersDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DestroyFriendshipDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DestroySavedSearchDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DestroyStatusDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DestroyUserListDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DestroyUserListSubscriptionDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesConversationFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/FileSelectorDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/HomeTimelineFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/IncomingFriendshipsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/IncomingFriendshipsMenuDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/InvalidTabFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/MentionsTimelineFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/MutesUsersListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/NativeMapFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableStatusesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/PhishingLinkWarningDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/QuickMenuFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/ReportSpamDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetsOfMeFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SavedSearchesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SearchFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SearchStatusesFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SearchUsersFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SensitiveContentWarningDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SetUserNicknameDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StaggeredGridFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StaggeredHomeTimelineFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFavoritersListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusMenuDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusRepliesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusRetweetersListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusTranslateDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusesListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SupportProgressDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/SupportWebViewFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/TrendsSuggectionsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserBlocksListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFavoritesFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFollowersFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFriendsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListDetailsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembersFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembershipsListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMenuDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListSubscribersFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListTimelineFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserMentionsFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserMenuDialogFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserProfileFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserTimelineFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/UsersListFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/WebMapFragment.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/AlphaPatternDrawable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/ColorPreviewDrawable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/DropShadowDrawable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/EmptyDrawable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/PaddingDrawable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/TextDrawable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/graphic/icon/TwidereIcon.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/ExtensionsListLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesAboutMeLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesByFriendsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/BaseCursorSupportUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/BaseUserListsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/CursorSupportUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/DummyParcelableStatusesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/DummyParcelableUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/IDsUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/IncomingFriendshipsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/IntentActivitiesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/IntentExtrasStatusesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/IntentExtrasUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/MutesUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableStatusesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableUserLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/RetweetsOfMeLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/SavedSearchesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/StatusConversationLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/StatusFavoritersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/StatusRepliesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/StatusRetweetersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/TweetSearchLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/Twitter4JActivitiesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/Twitter4JStatusesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/Twitter4JUsersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserBlocksLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFavoritesLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFollowersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserFriendsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListMembershipsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListSubscribersLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListTimelineLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserMentionsLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserSearchLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/loader/support/UserTimelineLoader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/menu/AccountActionProvider.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/Account.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/Account.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/AccountPreferences.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ConsumerKeySecret.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/CustomTabConfiguration.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/CustomTabConfiguration2.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/DraftItem.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ListAction.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ListResponse.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/MediaUploadResult.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/MediaUploadResult.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/MediaUploaderParameter.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/Panes.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableDirectMessage.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableLocation.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableLocation.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableMedia.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableMediaUpdate.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableStatusUpdate.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableStatusUpdate.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableUser.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableUserList.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/ParcelableUserMention.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/RawSharedPreferencesData.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/SharedPreferencesData.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/SingleResponse.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/StatusShortenResult.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/StatusShortenResult.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/SupportTabSpec.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/TabSpec.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/TwidereParcelable.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/UnreadItem.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/UploaderMediaItem.aidl create mode 100644 twidere/src/main/java/org/mariotaku/twidere/model/UploaderMediaItem.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AccountsListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AppVersionPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AsyncTaskPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AutoFixCheckBoxPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AutoFixEditTextPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AutoFixListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AutoInvalidateListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AutoRefreshAccountsListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/AutoRefreshContentPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/CardPreviewPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ClearCachePreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ClearDatabasesPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ClearSearchHistoryPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ColorPickerPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ComponentStatePreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/DefaultAPIPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/HomeRefreshContentPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ImagePreloadPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/LeftsideComposeButtonPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/LinkHighlightPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/MediaUploaderPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/MultiSelectListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/NotificationAccountsListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/NotificationContentPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/NotificationTypePreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/RingtonePreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/SeekBarDialogPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ServicePickerPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/SilentNotificationsPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/StatusShortenerPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/SummaryEditTextPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/SummaryListPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ThemeFontFamilyPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ThemePreviewPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/TimelineSyncPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/TranslationDestinationPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/TrendsLocationPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ValueDependencyCheckBoxPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ValueDependencyDialogPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ValueDependencySeekBarDialogPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/WizardPageHeaderPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/WizardPageNavPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/provider/RecentSearchProvider.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/provider/TweetStore.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/provider/TwidereCommandProvider.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/provider/TwidereCommands.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/receiver/ConnectivityStateReceiver.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/receiver/SecretCodeBroadcastReceiver.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/BackgroundOperationService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/DashClockHomeUnreadCountService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/DashClockMentionsUnreadCountService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/DashClockMessagesUnreadCountService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/NyanDaydreamService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/NyanWallpaperService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/service/RefreshService.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/task/AsyncTask.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/task/CacheUsersStatusesTask.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/task/ManagedAsyncTask.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/text/TwidereHighLightStyle.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/text/TwidereURLSpan.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/text/method/StatusContentMovementMethod.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ArrayUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/AsyncTaskManager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/BitmapDecodeHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/BuildProperties.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ClipboardUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ColorAnalyser.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/CompareUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ContentValuesCreator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/CustomTabUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/DataImportExportUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/Exif.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/FileUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/FlowTextHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/FlymeUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HostsFileParser.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HotKeyHandler.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HtmlBuilder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HtmlEscapeHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/HtmlLinkExtractor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ImageLoaderUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ImageLoaderWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ImageLoadingHandler.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ImagePreloader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ImageValidator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ListUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/LongSparseArrayUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MIUIUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MathUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MediaPreviewUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MediaUploaderInterface.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MessagesManager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MultiSelectEventHandler.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/MultiSelectManager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/NameValuePairImpl.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/NyanDrawingHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/NyanSurfaceHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/OAuthPasswordAuthenticator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/OnDirectMessageLinkClickHandler.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/OnLinkClickHandler.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ParseUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/PermissionsManager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/PositionManager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/SQLiteDatabaseWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/SaveImageTask.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ServiceUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/SharedPreferencesWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/SpannableStringBuilder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/StatusCodeMessageUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/StatusShortenerInterface.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/StrictModeUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/SwipebackActivityUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/TwidereLinkify.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/TwidereQueryBuilder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/TwidereValidator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/TwitterWrapper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/UnreadCountUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/UserColorNicknameUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/Utils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/accessor/BackStackEntryAccessor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/accessor/ConfigurationAccessor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/accessor/FragmentManagerAccessor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/accessor/ViewAccessor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/accessor/ViewDragHelperAccessor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/accessor/WebSettingsAccessor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/collection/NoDuplicatesArrayList.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/collection/NoDuplicatesCopyOnWriteArrayList.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/collection/NoDuplicatesLinkedList.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/content/ContentResolverUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/content/ContentValuesUtils.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/content/DatabaseUpgradeHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/content/SupportActivityReloadCursorObserver.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/content/SupportFragmentReloadCursorObserver.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/content/TwidereSQLiteOpenHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/imageloader/AccountExtra.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/imageloader/TwidereImageDownloader.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/imageloader/URLFileNameGenerator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/io/ContentLengthInputStream.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/ApacheHttpClientHttpResponseImpl.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/HttpParameterFormEntity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/TwidereHostAddressResolver.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/TwidereHostResolverFactory.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/TwidereHttpClientFactory.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/TwidereHttpClientImpl.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/ssl/AbstractCheckSignatureVerifier.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/ssl/HostResolvedSSLConnectionSocketFactory.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/ssl/TrustAllX509TrustManager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/ssl/TwidereHostnameVerifier.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/net/ssl/TwidereSSLSocketFactory.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/theme/ActionIconsInterceptor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/theme/ActivityIconsInterceptor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/theme/TwidereAccentHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/theme/TwidereResourceHelper.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/theme/WhiteDrawableInterceptor.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/webkit/DefaultWebViewClient.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActionBarHomeAsUpIndicator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActionBarSplitThemedContainer.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActionBarSubtitleView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActionBarTabView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActionBarThemedContainer.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActionBarTitleView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ActivatedCheckBox.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/AutoAdjustHeightImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/CardItemFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/CardItemLinearLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/CardItemRelativeLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ColorLabelFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ColorLabelLinearLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ColorLabelRelativeLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ColorPickerView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ExtendedFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ExtendedImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ExtendedLinearLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ExtendedRelativeLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ExtendedViewPager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ForegroundColorView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ForegroundImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/HalfWidthSpace.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/HandleSpanClickTextView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/HighlightImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/HomeActionsActionView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ImagePreviewContainer.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/LeftDrawerFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/LinePageIndicator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ListMenuOverflowButton.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/MapImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/MessageCardItemFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/NavigationArrowButton.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/NyanDaydreamView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ProfileBannerImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ProfileImageBannerLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ProfileImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/RefreshNowStaggeredGridView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/RightDrawerFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/ShortTimeView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/SquareFrameLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/SquareHighlightImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/SquareImageView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/SquareRelativeLayout.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/SquareView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/SquareViewPager.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/StatusComposeEditText.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/StatusTextCountView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/StatusTextView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/TabPageIndicator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/AccountViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/CardViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/CheckableTwoLineWithIconViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageConversationViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageEntryViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/DraftViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/TwoLineWithIconViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/UserViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/ViewHolder.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/iface/ICardItemView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/iface/IColorLabelView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/iface/IExtendedView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/iface/IForegroundView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/iface/PagerIndicator.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedAutoCompleteTextView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedCheckBox.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedEditText.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedMultiAutoCompleteTextView.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedRadioButton.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedSwitch.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/themed/ThemedTextView.java create mode 100644 twidere/src/main/java/twitter4j/AccountSettings.java create mode 100644 twidere/src/main/java/twitter4j/AccountTotals.java create mode 100644 twidere/src/main/java/twitter4j/Activity.java create mode 100644 twidere/src/main/java/twitter4j/Category.java create mode 100644 twidere/src/main/java/twitter4j/CursorPaging.java create mode 100644 twidere/src/main/java/twitter4j/CursorSupport.java create mode 100644 twidere/src/main/java/twitter4j/DirectMessage.java create mode 100644 twidere/src/main/java/twitter4j/EntitySupport.java create mode 100644 twidere/src/main/java/twitter4j/ExceptionDiagnosis.java create mode 100644 twidere/src/main/java/twitter4j/Friendship.java create mode 100644 twidere/src/main/java/twitter4j/GeoLocation.java create mode 100644 twidere/src/main/java/twitter4j/GeoQuery.java create mode 100644 twidere/src/main/java/twitter4j/HashtagEntity.java create mode 100644 twidere/src/main/java/twitter4j/IDs.java create mode 100644 twidere/src/main/java/twitter4j/Location.java create mode 100644 twidere/src/main/java/twitter4j/MediaEntity.java create mode 100644 twidere/src/main/java/twitter4j/MediaUploadResponse.java create mode 100644 twidere/src/main/java/twitter4j/OEmbed.java create mode 100644 twidere/src/main/java/twitter4j/OEmbedRequest.java create mode 100644 twidere/src/main/java/twitter4j/PagableResponseList.java create mode 100644 twidere/src/main/java/twitter4j/Paging.java create mode 100644 twidere/src/main/java/twitter4j/Place.java create mode 100644 twidere/src/main/java/twitter4j/Query.java create mode 100644 twidere/src/main/java/twitter4j/QueryResult.java create mode 100644 twidere/src/main/java/twitter4j/RateLimitStatus.java create mode 100644 twidere/src/main/java/twitter4j/RateLimitStatusEvent.java create mode 100644 twidere/src/main/java/twitter4j/RateLimitStatusListener.java create mode 100644 twidere/src/main/java/twitter4j/Relationship.java create mode 100644 twidere/src/main/java/twitter4j/ReportAs.java create mode 100644 twidere/src/main/java/twitter4j/ResponseList.java create mode 100644 twidere/src/main/java/twitter4j/SavedSearch.java create mode 100644 twidere/src/main/java/twitter4j/SimilarPlaces.java create mode 100644 twidere/src/main/java/twitter4j/Status.java create mode 100644 twidere/src/main/java/twitter4j/StatusActivitySummary.java create mode 100644 twidere/src/main/java/twitter4j/StatusUpdate.java create mode 100644 twidere/src/main/java/twitter4j/TimeZone.java create mode 100644 twidere/src/main/java/twitter4j/TranslationResult.java create mode 100644 twidere/src/main/java/twitter4j/Trend.java create mode 100644 twidere/src/main/java/twitter4j/Trends.java create mode 100644 twidere/src/main/java/twitter4j/Twitter.java create mode 100644 twidere/src/main/java/twitter4j/TwitterAPIConfiguration.java create mode 100644 twidere/src/main/java/twitter4j/TwitterAPIMonitor.java create mode 100644 twidere/src/main/java/twitter4j/TwitterBase.java create mode 100644 twidere/src/main/java/twitter4j/TwitterBaseImpl.java create mode 100644 twidere/src/main/java/twitter4j/TwitterConstants.java create mode 100644 twidere/src/main/java/twitter4j/TwitterException.java create mode 100644 twidere/src/main/java/twitter4j/TwitterFactory.java create mode 100644 twidere/src/main/java/twitter4j/TwitterImpl.java create mode 100644 twidere/src/main/java/twitter4j/TwitterResponse.java create mode 100644 twidere/src/main/java/twitter4j/URLEntity.java create mode 100644 twidere/src/main/java/twitter4j/User.java create mode 100644 twidere/src/main/java/twitter4j/UserList.java create mode 100644 twidere/src/main/java/twitter4j/UserMentionEntity.java create mode 100644 twidere/src/main/java/twitter4j/Version.java create mode 100644 twidere/src/main/java/twitter4j/api/DirectMessagesResources.java create mode 100644 twidere/src/main/java/twitter4j/api/FavoritesResources.java create mode 100644 twidere/src/main/java/twitter4j/api/FriendsFollowersResources.java create mode 100644 twidere/src/main/java/twitter4j/api/HelpResources.java create mode 100644 twidere/src/main/java/twitter4j/api/ListsResources.java create mode 100644 twidere/src/main/java/twitter4j/api/MediaResources.java create mode 100644 twidere/src/main/java/twitter4j/api/PlacesGeoResources.java create mode 100644 twidere/src/main/java/twitter4j/api/SavedSearchesResources.java create mode 100644 twidere/src/main/java/twitter4j/api/SearchResource.java create mode 100644 twidere/src/main/java/twitter4j/api/SpamReportingResources.java create mode 100644 twidere/src/main/java/twitter4j/api/TimelinesResources.java create mode 100644 twidere/src/main/java/twitter4j/api/TrendsResources.java create mode 100644 twidere/src/main/java/twitter4j/api/TweetResources.java create mode 100644 twidere/src/main/java/twitter4j/api/UndocumentedActivityResources.java create mode 100644 twidere/src/main/java/twitter4j/api/UndocumentedFriendsFollowersResources.java create mode 100644 twidere/src/main/java/twitter4j/api/UndocumentedResources.java create mode 100644 twidere/src/main/java/twitter4j/api/UndocumentedTimelinesResources.java create mode 100644 twidere/src/main/java/twitter4j/api/UndocumentedTweetResources.java create mode 100644 twidere/src/main/java/twitter4j/api/UsersResources.java create mode 100644 twidere/src/main/java/twitter4j/auth/AccessToken.java create mode 100644 twidere/src/main/java/twitter4j/auth/Authorization.java create mode 100644 twidere/src/main/java/twitter4j/auth/AuthorizationConfiguration.java create mode 100644 twidere/src/main/java/twitter4j/auth/AuthorizationFactory.java create mode 100644 twidere/src/main/java/twitter4j/auth/BasicAuthorization.java create mode 100644 twidere/src/main/java/twitter4j/auth/NullAuthorization.java create mode 100644 twidere/src/main/java/twitter4j/auth/OAuthAuthorization.java create mode 100644 twidere/src/main/java/twitter4j/auth/OAuthSupport.java create mode 100644 twidere/src/main/java/twitter4j/auth/OAuthToken.java create mode 100644 twidere/src/main/java/twitter4j/auth/RequestToken.java create mode 100644 twidere/src/main/java/twitter4j/auth/TwipOModeAuthorization.java create mode 100644 twidere/src/main/java/twitter4j/auth/XAuthAuthorization.java create mode 100644 twidere/src/main/java/twitter4j/conf/BaseConfigurationFactory.java create mode 100644 twidere/src/main/java/twitter4j/conf/Configuration.java create mode 100644 twidere/src/main/java/twitter4j/conf/ConfigurationBase.java create mode 100644 twidere/src/main/java/twitter4j/conf/ConfigurationBuilder.java create mode 100644 twidere/src/main/java/twitter4j/conf/ConfigurationContext.java create mode 100644 twidere/src/main/java/twitter4j/conf/ConfigurationFactory.java create mode 100644 twidere/src/main/java/twitter4j/http/BASE64Encoder.java create mode 100644 twidere/src/main/java/twitter4j/http/FactoryUtils.java create mode 100644 twidere/src/main/java/twitter4j/http/HTMLEntity.java create mode 100644 twidere/src/main/java/twitter4j/http/HostAddressResolver.java create mode 100644 twidere/src/main/java/twitter4j/http/HostAddressResolverFactory.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClient.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClientBase.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClientConfiguration.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClientFactory.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClientImpl.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClientWrapper.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpClientWrapperConfiguration.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpParameter.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpRequest.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpResponse.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpResponseCode.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpResponseEvent.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpResponseImpl.java create mode 100644 twidere/src/main/java/twitter4j/http/HttpResponseListener.java create mode 100644 twidere/src/main/java/twitter4j/http/RequestMethod.java create mode 100644 twidere/src/main/java/twitter4j/http/StreamingGZIPInputStream.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/AccountSettingsJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/AccountTotalsJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/ActivityJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/CategoryJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/DirectMessageJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/FriendshipJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/HashtagEntityJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/IDsJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/InternalJSONFactory.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/InternalJSONFactoryImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/LanguageJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/LocationJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/MediaEntityJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/MediaUploadResponseJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/OEmbedJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/PagableResponseListImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/PlaceJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/QueryResultJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/RateLimitStatusJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/RelationshipJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/ResponseListImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/SavedSearchJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/SimilarPlacesImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/StatusActivitySummaryJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/StatusJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/TimeZoneJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/TranslationResultJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/TrendJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/TrendsJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/TwitterAPIConfigurationJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/TwitterResponseImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/URLEntityJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/UserJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/UserListJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/json/UserMentionEntityJSONImpl.java create mode 100644 twidere/src/main/java/twitter4j/internal/logging/AndroidLogger.java create mode 100644 twidere/src/main/java/twitter4j/internal/logging/AndroidLoggerFactory.java create mode 100644 twidere/src/main/java/twitter4j/internal/logging/Logger.java create mode 100644 twidere/src/main/java/twitter4j/internal/logging/LoggerFactory.java create mode 100644 twidere/src/main/java/twitter4j/internal/logging/NullLogger.java create mode 100644 twidere/src/main/java/twitter4j/internal/logging/NullLoggerFactory.java create mode 100644 twidere/src/main/java/twitter4j/internal/util/InternalParseUtil.java create mode 100644 twidere/src/main/java/twitter4j/internal/util/InternalStringUtil.java create mode 100644 twidere/src/main/java/twitter4j/management/APIStatistics.java create mode 100644 twidere/src/main/java/twitter4j/management/APIStatisticsMBean.java create mode 100644 twidere/src/main/java/twitter4j/management/InvocationStatistics.java create mode 100644 twidere/src/main/java/twitter4j/management/InvocationStatisticsCalculator.java create mode 100644 twidere/src/main/java/twitter4j/util/CharacterUtil.java create mode 100644 twidere/src/main/java/twitter4j/util/TimeSpanConverter.java create mode 100644 twidere/src/main/res/anim/activity_close_enter.xml create mode 100644 twidere/src/main/res/anim/activity_close_exit.xml create mode 100644 twidere/src/main/res/anim/activity_open_enter.xml create mode 100644 twidere/src/main/res/anim/activity_open_exit.xml create mode 100644 twidere/src/main/res/drawable-hdpi/ab_solid_dark_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/ab_solid_light_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/ab_solid_light_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/ab_transparent_noclor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_incoming_focused_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_incoming_focused_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_incoming_normal_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_incoming_normal_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_incoming_pressed_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_incoming_pressed_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_outgoing_focused_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_outgoing_focused_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_outgoing_normal_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_outgoing_normal_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_outgoing_pressed_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_outgoing_pressed_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_profile_image_incoming_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_profile_image_incoming_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_profile_image_outgoing_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/bg_card_item_message_profile_image_outgoing_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/dialog_full_holo_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/dialog_full_holo_light.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/dialog_full_holo_light_darkactionbar.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/expander_close_holo.png create mode 100644 twidere/src/main/res/drawable-hdpi/expander_open_holo.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_action_assist_twidere_activated.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_action_assist_twidere_normal.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_action_profile.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_action_reply.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_conversation.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_followers.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_following.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_location.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_media.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_protected.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_received.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_reply.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_reported_media.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_retweet.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_sent.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_starred.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_twitter.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_verified.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_indicator_web.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_launcher_hondajojo.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_menu_moreoverflow_card_dark.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_menu_moreoverflow_card_light.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_profile_image_default.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_stat_info.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_stat_mention.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_stat_message.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_stat_send.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_stat_twitter.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_user_type_protected.png create mode 100644 twidere/src/main/res/drawable-hdpi/ic_user_type_verified.png create mode 100644 twidere/src/main/res/drawable-hdpi/image_shadow.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/list_drag_handle.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/overscroll_edge.png create mode 100644 twidere/src/main/res/drawable-hdpi/overscroll_glow.png create mode 100644 twidere/src/main/res/drawable-hdpi/tab_background_dark.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/tab_vpi_selected_focused_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/tab_vpi_selected_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/tab_vpi_selected_pressed_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/tab_vpi_unselected_focused_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/tab_vpi_unselected_pressed_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-hdpi/vpi__tab_unselected_holo.9.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_action_assist_twidere_activated.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_action_assist_twidere_normal.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_action_profile.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_action_reply.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_extension_mentions.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_extension_messages.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_extension_twidere.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_conversation.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_followers.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_following.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_location.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_media.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_protected.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_received.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_reply.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_reported_media.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_retweet.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_sent.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_starred.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_twitter.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_verified.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_indicator_web.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_launcher_hondajojo.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_list_menu_moreoverflow_normal_holo_dark.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_list_menu_moreoverflow_normal_holo_light.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_menu_moreoverflow_card_dark.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_menu_moreoverflow_card_light.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_profile_image_default.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_stat_info.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_stat_mention.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_stat_message.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_stat_send.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_stat_twitter.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_user_type_protected.png create mode 100644 twidere/src/main/res/drawable-mdpi/ic_user_type_verified.png create mode 100644 twidere/src/main/res/drawable-mdpi/list_drag_handle.9.png create mode 100644 twidere/src/main/res/drawable-mdpi/nyan_stars_background_tile.png create mode 100644 twidere/src/main/res/drawable-mdpi/overscroll_edge.png create mode 100644 twidere/src/main/res/drawable-mdpi/overscroll_glow.png create mode 100644 twidere/src/main/res/drawable-nodpi/bg_map_bitmap.png create mode 100644 twidere/src/main/res/drawable-nodpi/ic_launcher_web.png create mode 100644 twidere/src/main/res/drawable-nodpi/image_preview_error_bitmap.png create mode 100644 twidere/src/main/res/drawable-nodpi/image_preview_nsfw_bitmap.png create mode 100644 twidere/src/main/res/drawable-nodpi/image_preview_refresh_bitmap.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame00_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame01_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame02_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame03_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame04_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame05_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame06_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame07_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame08_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame09_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame10_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_rainbow_frame11_tile.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame00.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame01.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame02.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame03.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame04.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame05.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame06.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame07.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame08.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame09.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame10.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_frame11.png create mode 100644 twidere/src/main/res/drawable-nodpi/nyan_sakamoto_thumbnail_bitmap.png create mode 100644 twidere/src/main/res/drawable-nodpi/phishing_site_warning_example.png create mode 100644 twidere/src/main/res/drawable-nodpi/twidere_feature_graphic.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ab_solid_dark_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ab_solid_light_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ab_solid_light_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ab_transparent_noclor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_focused_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_focused_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_longpressed_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_longpressed_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_normal_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_normal_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_pressed_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_incoming_pressed_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_focused_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_focused_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_longpressed_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_longpressed_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_normal_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_normal_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_pressed_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_outgoing_pressed_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_profile_image_incoming_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_profile_image_incoming_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_profile_image_outgoing_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/bg_card_item_message_profile_image_outgoing_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/dialog_full_holo_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/dialog_full_holo_light.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/dialog_full_holo_light_darkactionbar.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/expander_close_holo.png create mode 100644 twidere/src/main/res/drawable-xhdpi/expander_open_holo.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_action_assist_twidere_activated.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_action_assist_twidere_normal.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_action_profile.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_action_reply.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_file.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_folder.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_conversation.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_followers.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_following.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_location.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_media.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_protected.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_received.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_reply.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_reported_media.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_retweet.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_sent.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_starred.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_twitter.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_verified.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_indicator_web.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_launcher_hondajojo.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_list_menu_moreoverflow_normal_holo_dark.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_list_menu_moreoverflow_normal_holo_light.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_map_marker.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_card_dark.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_menu_moreoverflow_card_light.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_profile_image_default.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_stat_direct_message.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_stat_info.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_stat_mention.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_stat_message.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_stat_send.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_stat_twitter.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_user_type_protected.png create mode 100644 twidere/src/main/res/drawable-xhdpi/ic_user_type_verified.png create mode 100644 twidere/src/main/res/drawable-xhdpi/image_shadow.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/list_drag_handle.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/overscroll_edge.png create mode 100644 twidere/src/main/res/drawable-xhdpi/overscroll_glow.png create mode 100644 twidere/src/main/res/drawable-xhdpi/tab_background_dark.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/tab_vpi_selected_focused_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/tab_vpi_selected_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/tab_vpi_selected_pressed_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/tab_vpi_unselected_focused_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xhdpi/tab_vpi_unselected_pressed_nocolor_holo.9.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/bg_card_item_dark.9.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/bg_card_item_light.9.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_action_assist_twidere_activated.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_action_assist_twidere_normal.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_action_profile.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_action_reply.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_extension_mentions.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_extension_messages.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_extension_twidere.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_conversation.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_followers.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_following.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_location.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_media.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_protected.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_received.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_reply.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_reported_media.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_retweet.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_sent.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_starred.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_twitter.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_verified.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_indicator_web.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_launcher_hondajojo.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_list_menu_moreoverflow_normal_holo_dark.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_list_menu_moreoverflow_normal_holo_light.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_menu_moreoverflow_card_dark.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_menu_moreoverflow_card_light.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_profile_image_default.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_stat_info.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_stat_mention.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_stat_message.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_stat_send.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_stat_twitter.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_user_type_protected.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/ic_user_type_verified.png create mode 100644 twidere/src/main/res/drawable-xxhdpi/list_drag_handle.9.png create mode 100644 twidere/src/main/res/drawable-xxxhdpi/ic_launcher.png create mode 100644 twidere/src/main/res/drawable/ab_twidere_solid_color_holo.xml create mode 100644 twidere/src/main/res/drawable/ab_twidere_solid_dark_holo.xml create mode 100644 twidere/src/main/res/drawable/ab_twidere_solid_light_holo.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_incoming_dark.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_incoming_light.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_incoming_transition_holo_dark.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_incoming_transition_holo_light.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_outgoing_dark.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_outgoing_light.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_outgoing_transition_holo_dark.xml create mode 100644 twidere/src/main/res/drawable/bg_card_item_message_outgoing_transition_holo_light.xml create mode 100644 twidere/src/main/res/drawable/bg_map.xml create mode 100644 twidere/src/main/res/drawable/divider_horizontal_small_holo.xml create mode 100644 twidere/src/main/res/drawable/divider_vertical_small_holo.xml create mode 100644 twidere/src/main/res/drawable/focused_background_white.xml create mode 100644 twidere/src/main/res/drawable/ic_assist_twidere.xml create mode 100644 twidere/src/main/res/drawable/ic_launcher_twidere_iconic.xml create mode 100644 twidere/src/main/res/drawable/ic_menu_gallery.xml create mode 100644 twidere/src/main/res/drawable/ic_menu_preferences.xml create mode 100644 twidere/src/main/res/drawable/ic_menu_send.xml create mode 100644 twidere/src/main/res/drawable/ic_profile_image_default_iconic.xml create mode 100644 twidere/src/main/res/drawable/image_preview_error.xml create mode 100644 twidere/src/main/res/drawable/image_preview_nsfw.xml create mode 100644 twidere/src/main/res/drawable/image_preview_refresh.xml create mode 100644 twidere/src/main/res/drawable/list_selector_light_white.xml create mode 100644 twidere/src/main/res/drawable/list_selector_transition_white.xml create mode 100644 twidere/src/main/res/drawable/list_selector_white.xml create mode 100644 twidere/src/main/res/drawable/nyan_sakamoto.xml create mode 100644 twidere/src/main/res/drawable/nyan_sakamoto_thumbnail.xml create mode 100644 twidere/src/main/res/drawable/nyan_stars_background.xml create mode 100644 twidere/src/main/res/drawable/shadow_bottom.xml create mode 100644 twidere/src/main/res/drawable/shadow_left.xml create mode 100644 twidere/src/main/res/drawable/shadow_right.xml create mode 100644 twidere/src/main/res/drawable/shadow_top.xml create mode 100644 twidere/src/main/res/drawable/tab_indicator_vpi_nocolor.xml create mode 100644 twidere/src/main/res/layout-large/home_tabs.xml create mode 100644 twidere/src/main/res/layout/action_item_compose.xml create mode 100644 twidere/src/main/res/layout/action_item_home_actions.xml create mode 100644 twidere/src/main/res/layout/action_item_home_progress_smartbar.xml create mode 100644 twidere/src/main/res/layout/action_item_switch.xml create mode 100644 twidere/src/main/res/layout/activity_account_selector.xml create mode 100644 twidere/src/main/res/layout/activity_activity_picker.xml create mode 100644 twidere/src/main/res/layout/activity_api_editor.xml create mode 100644 twidere/src/main/res/layout/activity_browser_sign_in.xml create mode 100644 twidere/src/main/res/layout/activity_compose.xml create mode 100644 twidere/src/main/res/layout/activity_compose_actionbar.xml create mode 100644 twidere/src/main/res/layout/activity_compose_bottombar.xml create mode 100644 twidere/src/main/res/layout/activity_custom_tab_editor.xml create mode 100644 twidere/src/main/res/layout/activity_filters.xml create mode 100644 twidere/src/main/res/layout/activity_home.xml create mode 100644 twidere/src/main/res/layout/activity_image_viewer_bottombar.xml create mode 100644 twidere/src/main/res/layout/activity_settings_wizard.xml create mode 100644 twidere/src/main/res/layout/activity_sign_in.xml create mode 100644 twidere/src/main/res/layout/activity_user_list_selector.xml create mode 100644 twidere/src/main/res/layout/api_editor_advanced_fields.xml create mode 100644 twidere/src/main/res/layout/api_editor_content.xml create mode 100644 twidere/src/main/res/layout/auto_complete_textview.xml create mode 100644 twidere/src/main/res/layout/base_multi_column_list.xml create mode 100644 twidere/src/main/res/layout/card_item_activity.xml create mode 100644 twidere/src/main/res/layout/card_item_activity_compact.xml create mode 100644 twidere/src/main/res/layout/card_item_draft.xml create mode 100644 twidere/src/main/res/layout/card_item_message_conversation.xml create mode 100644 twidere/src/main/res/layout/card_item_message_entry.xml create mode 100644 twidere/src/main/res/layout/card_item_message_entry_compact.xml create mode 100644 twidere/src/main/res/layout/card_item_status.xml create mode 100644 twidere/src/main/res/layout/card_item_status_compact.xml create mode 100644 twidere/src/main/res/layout/card_item_user.xml create mode 100644 twidere/src/main/res/layout/card_item_user_compact.xml create mode 100644 twidere/src/main/res/layout/card_item_user_list.xml create mode 100644 twidere/src/main/res/layout/card_item_user_list_compact.xml create mode 100644 twidere/src/main/res/layout/dialog_color_picker.xml create mode 100644 twidere/src/main/res/layout/dialog_host_mapping.xml create mode 100644 twidere/src/main/res/layout/dialog_scrollable_status.xml create mode 100644 twidere/src/main/res/layout/dialog_translate_status.xml create mode 100644 twidere/src/main/res/layout/drawer_home_accounts.xml create mode 100644 twidere/src/main/res/layout/drawer_home_quick_menu.xml create mode 100644 twidere/src/main/res/layout/edit_user_list_detail.xml create mode 100644 twidere/src/main/res/layout/edit_user_profile.xml create mode 100644 twidere/src/main/res/layout/empty_tab_hint.xml create mode 100644 twidere/src/main/res/layout/fragment_accounts_drawer.xml create mode 100644 twidere/src/main/res/layout/fragment_characters_grid.xml create mode 100644 twidere/src/main/res/layout/fragment_custom_tabs.xml create mode 100644 twidere/src/main/res/layout/fragment_data_profiling_settings.xml create mode 100644 twidere/src/main/res/layout/fragment_details_bottombar.xml create mode 100644 twidere/src/main/res/layout/fragment_details_page.xml create mode 100644 twidere/src/main/res/layout/fragment_donate.xml create mode 100644 twidere/src/main/res/layout/fragment_messages_conversation.xml create mode 100644 twidere/src/main/res/layout/fragment_messages_conversation_input_send.xml create mode 100644 twidere/src/main/res/layout/fragment_quick_menu.xml create mode 100644 twidere/src/main/res/layout/fragment_search.xml create mode 100644 twidere/src/main/res/layout/fragment_webview.xml create mode 100644 twidere/src/main/res/layout/fragment_wizard_page_finished.xml create mode 100644 twidere/src/main/res/layout/gallery_item_color_picker_preset.xml create mode 100644 twidere/src/main/res/layout/gallery_item_compose_account.xml create mode 100644 twidere/src/main/res/layout/gallery_item_image_preview.xml create mode 100644 twidere/src/main/res/layout/gl_root_group.xml create mode 100644 twidere/src/main/res/layout/grid_item_image_preview.xml create mode 100644 twidere/src/main/res/layout/grid_item_media_editor.xml create mode 100644 twidere/src/main/res/layout/header_hidden_settings.xml create mode 100644 twidere/src/main/res/layout/header_status.xml create mode 100644 twidere/src/main/res/layout/header_user_list_details.xml create mode 100644 twidere/src/main/res/layout/header_user_profile.xml create mode 100644 twidere/src/main/res/layout/header_user_profile_banner.xml create mode 100644 twidere/src/main/res/layout/header_wizard_page.xml create mode 100644 twidere/src/main/res/layout/home_actions_button_layout.xml create mode 100644 twidere/src/main/res/layout/home_tabs.xml create mode 100644 twidere/src/main/res/layout/image_preview_grid.xml create mode 100644 twidere/src/main/res/layout/image_viewer_gl.xml create mode 100644 twidere/src/main/res/layout/invalid_tab.xml create mode 100644 twidere/src/main/res/layout/link_handler_actionbar.xml create mode 100644 twidere/src/main/res/layout/list_action_item.xml create mode 100644 twidere/src/main/res/layout/list_item_account.xml create mode 100644 twidere/src/main/res/layout/list_item_custom_tab.xml create mode 100644 twidere/src/main/res/layout/list_item_drawer_accounts.xml create mode 100644 twidere/src/main/res/layout/list_item_extra_config.xml create mode 100644 twidere/src/main/res/layout/list_item_menu.xml create mode 100644 twidere/src/main/res/layout/list_item_preference_header.xml create mode 100644 twidere/src/main/res/layout/list_item_section_header.xml create mode 100644 twidere/src/main/res/layout/list_item_separator.xml create mode 100644 twidere/src/main/res/layout/list_item_two_line.xml create mode 100644 twidere/src/main/res/layout/list_item_two_line_checked.xml create mode 100644 twidere/src/main/res/layout/list_item_two_line_small.xml create mode 100644 twidere/src/main/res/layout/nyan_daydream.xml create mode 100644 twidere/src/main/res/layout/phishing_link_warning.xml create mode 100644 twidere/src/main/res/layout/preference_seek_bar_dialog.xml create mode 100644 twidere/src/main/res/layout/refreshnow_staggered_gridview.xml create mode 100644 twidere/src/main/res/layout/request_permissions.xml create mode 100644 twidere/src/main/res/layout/settings_layout_click_to_config.xml create mode 100644 twidere/src/main/res/layout/settings_layout_wizard_page_nav.xml create mode 100644 twidere/src/main/res/layout/spinner_item_custom_tab_icon.xml create mode 100644 twidere/src/main/res/layout/staggered_gridview.xml create mode 100644 twidere/src/main/res/layout/surface_view.xml create mode 100644 twidere/src/main/res/layout/swipeback_layout.xml create mode 100644 twidere/src/main/res/layout/tab_item_ab.xml create mode 100644 twidere/src/main/res/layout/tab_item_vpi.xml create mode 100644 twidere/src/main/res/layout/theme_preview.xml create mode 100644 twidere/src/main/res/layout/theme_preview_content.xml create mode 100644 twidere/src/main/res/layout/theme_preview_status_content.xml create mode 100644 twidere/src/main/res/layout/theme_preview_status_list.xml create mode 100644 twidere/src/main/res/menu/action_direct_message.xml create mode 100644 twidere/src/main/res/menu/action_extension.xml create mode 100644 twidere/src/main/res/menu/action_incoming_friendship.xml create mode 100644 twidere/src/main/res/menu/action_multi_select_contents.xml create mode 100644 twidere/src/main/res/menu/action_multi_select_drafts.xml create mode 100644 twidere/src/main/res/menu/action_multi_select_items.xml create mode 100644 twidere/src/main/res/menu/action_profile_banner_image.xml create mode 100644 twidere/src/main/res/menu/action_profile_image.xml create mode 100644 twidere/src/main/res/menu/action_status.xml create mode 100644 twidere/src/main/res/menu/action_status_text_selection.xml create mode 100644 twidere/src/main/res/menu/action_user_list.xml create mode 100644 twidere/src/main/res/menu/action_user_list_member.xml create mode 100644 twidere/src/main/res/menu/menu_compose.xml create mode 100644 twidere/src/main/res/menu/menu_custom_tabs.xml create mode 100644 twidere/src/main/res/menu/menu_edit_user_profile.xml create mode 100644 twidere/src/main/res/menu/menu_file_picker.xml create mode 100644 twidere/src/main/res/menu/menu_filters.xml create mode 100644 twidere/src/main/res/menu/menu_home.xml create mode 100644 twidere/src/main/res/menu/menu_host_mapping.xml create mode 100644 twidere/src/main/res/menu/menu_image_viewer.xml create mode 100644 twidere/src/main/res/menu/menu_image_viewer_action_bar.xml create mode 100644 twidere/src/main/res/menu/menu_map_viewer.xml create mode 100644 twidere/src/main/res/menu/menu_search.xml create mode 100644 twidere/src/main/res/menu/menu_settings.xml create mode 100644 twidere/src/main/res/menu/menu_sign_in.xml create mode 100644 twidere/src/main/res/menu/menu_status.xml create mode 100644 twidere/src/main/res/menu/menu_switch_preference.xml create mode 100644 twidere/src/main/res/menu/menu_user_list.xml create mode 100644 twidere/src/main/res/menu/menu_user_list_created.xml create mode 100644 twidere/src/main/res/menu/menu_user_profile.xml create mode 100644 twidere/src/main/res/values-ar/strings.xml create mode 100644 twidere/src/main/res/values-ar/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-ca/strings.xml create mode 100644 twidere/src/main/res/values-ca/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-cs/strings.xml create mode 100644 twidere/src/main/res/values-cs/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-de/strings.xml create mode 100644 twidere/src/main/res/values-de/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-el/strings.xml create mode 100644 twidere/src/main/res/values-es/strings.xml create mode 100644 twidere/src/main/res/values-es/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-fi/strings.xml create mode 100644 twidere/src/main/res/values-fi/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-fr/strings.xml create mode 100644 twidere/src/main/res/values-fr/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-hdpi/strings_notranslate.xml create mode 100644 twidere/src/main/res/values-hi/strings.xml create mode 100644 twidere/src/main/res/values-hu/strings.xml create mode 100644 twidere/src/main/res/values-in/strings.xml create mode 100644 twidere/src/main/res/values-in/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-it/strings.xml create mode 100644 twidere/src/main/res/values-it/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-iw/strings.xml create mode 100644 twidere/src/main/res/values-ja/strings.xml create mode 100644 twidere/src/main/res/values-ja/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-ko/strings.xml create mode 100644 twidere/src/main/res/values-ko/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-land/bools.xml create mode 100644 twidere/src/main/res/values-land/dimens.xml create mode 100644 twidere/src/main/res/values-land/integers.xml create mode 100644 twidere/src/main/res/values-large-land/bools.xml create mode 100644 twidere/src/main/res/values-large-land/dimens.xml create mode 100644 twidere/src/main/res/values-large-land/integers.xml create mode 100644 twidere/src/main/res/values-large/bools.xml create mode 100644 twidere/src/main/res/values-large/defaults.xml create mode 100644 twidere/src/main/res/values-large/dimens.xml create mode 100644 twidere/src/main/res/values-large/integers.xml create mode 100644 twidere/src/main/res/values-ms/strings.xml create mode 100644 twidere/src/main/res/values-nl/strings.xml create mode 100644 twidere/src/main/res/values-nl/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-no/strings.xml create mode 100644 twidere/src/main/res/values-no/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-pl/strings.xml create mode 100644 twidere/src/main/res/values-pl/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-pt/strings.xml create mode 100644 twidere/src/main/res/values-pt/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-ru/strings.xml create mode 100644 twidere/src/main/res/values-ru/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-sw600dp/bools.xml create mode 100644 twidere/src/main/res/values-th/strings.xml create mode 100644 twidere/src/main/res/values-th/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-tr/strings.xml create mode 100644 twidere/src/main/res/values-uk/strings.xml create mode 100644 twidere/src/main/res/values-uk/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-v16/bools.xml create mode 100644 twidere/src/main/res/values-xhdpi/strings_notranslate.xml create mode 100644 twidere/src/main/res/values-xlarge-land/dimens.xml create mode 100644 twidere/src/main/res/values-xlarge-land/integers.xml create mode 100644 twidere/src/main/res/values-xlarge/dimens.xml create mode 100644 twidere/src/main/res/values-xlarge/integers.xml create mode 100644 twidere/src/main/res/values-zh-rCN/strings.xml create mode 100644 twidere/src/main/res/values-zh-rCN/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values-zh/strings.xml create mode 100644 twidere/src/main/res/values-zh/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values/arrays.xml create mode 100644 twidere/src/main/res/values/attrs.xml create mode 100644 twidere/src/main/res/values/bools.xml create mode 100644 twidere/src/main/res/values/colors.xml create mode 100644 twidere/src/main/res/values/defaults.xml create mode 100644 twidere/src/main/res/values/dimens.xml create mode 100644 twidere/src/main/res/values/drawables_iconic.xml create mode 100644 twidere/src/main/res/values/drawables_intercepted.xml create mode 100644 twidere/src/main/res/values/fractions.xml create mode 100644 twidere/src/main/res/values/gallery_attrs.xml create mode 100644 twidere/src/main/res/values/ids.xml create mode 100644 twidere/src/main/res/values/integers.xml create mode 100644 twidere/src/main/res/values/plurals.xml create mode 100644 twidere/src/main/res/values/strings.xml create mode 100644 twidere/src/main/res/values/strings_http_errors.xml create mode 100644 twidere/src/main/res/values/strings_notranslate.xml create mode 100644 twidere/src/main/res/values/strings_twitter_errors.xml create mode 100644 twidere/src/main/res/values/styles.xml create mode 100644 twidere/src/main/res/values/swipeback_attrs.xml create mode 100644 twidere/src/main/res/values/themes.xml create mode 100644 twidere/src/main/res/values/vpi__defaults.xml create mode 100644 twidere/src/main/res/xml/about.xml create mode 100644 twidere/src/main/res/xml/nyan_wallpaper.xml create mode 100644 twidere/src/main/res/xml/searchable.xml create mode 100644 twidere/src/main/res/xml/settings_account_notifications.xml create mode 100644 twidere/src/main/res/xml/settings_account_refresh.xml create mode 100644 twidere/src/main/res/xml/settings_cards.xml create mode 100644 twidere/src/main/res/xml/settings_content.xml create mode 100644 twidere/src/main/res/xml/settings_headers.xml create mode 100644 twidere/src/main/res/xml/settings_hidden.xml create mode 100644 twidere/src/main/res/xml/settings_interface.xml create mode 100644 twidere/src/main/res/xml/settings_network.xml create mode 100644 twidere/src/main/res/xml/settings_notifications.xml create mode 100644 twidere/src/main/res/xml/settings_other.xml create mode 100644 twidere/src/main/res/xml/settings_refresh.xml create mode 100644 twidere/src/main/res/xml/settings_storage.xml create mode 100644 twidere/src/main/res/xml/settings_theme.xml create mode 100644 twidere/src/main/res/xml/settings_wizard_page_hints.xml create mode 100644 twidere/src/main/res/xml/settings_wizard_page_tab.xml create mode 100644 twidere/src/main/res/xml/settings_wizard_page_welcome.xml create mode 100644 twidere/twidere.iml diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..0f885ac89 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle +/local.properties +/.idea/workspace.xml +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..fee7e0cf4 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Twidere \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..217af471a --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000..e7bedf337 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..e206d70d8 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..51ed5d644 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/.idea/libraries/acra_4_5_0.xml b/.idea/libraries/acra_4_5_0.xml new file mode 100644 index 000000000..a96207864 --- /dev/null +++ b/.idea/libraries/acra_4_5_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/dashclock_api_2_0_0.xml b/.idea/libraries/dashclock_api_2_0_0.xml new file mode 100644 index 000000000..4eeb15894 --- /dev/null +++ b/.idea/libraries/dashclock_api_2_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/dnsjava_ipv6_1_0_with_sources.xml b/.idea/libraries/dnsjava_ipv6_1_0_with_sources.xml new file mode 100644 index 000000000..fc83cd751 --- /dev/null +++ b/.idea/libraries/dnsjava_ipv6_1_0_with_sources.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/drag_sort_listview_apklib_1_0.xml b/.idea/libraries/drag_sort_listview_apklib_1_0.xml new file mode 100644 index 000000000..1a9429da5 --- /dev/null +++ b/.idea/libraries/drag_sort_listview_apklib_1_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/httpclient_android_4_3_3.xml b/.idea/libraries/httpclient_android_4_3_3.xml new file mode 100644 index 000000000..50b9d6c47 --- /dev/null +++ b/.idea/libraries/httpclient_android_4_3_3.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/httpmime_4_3_3.xml b/.idea/libraries/httpmime_4_3_3.xml new file mode 100644 index 000000000..2a373f6b4 --- /dev/null +++ b/.idea/libraries/httpmime_4_3_3.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/jsonserializer_1_0.xml b/.idea/libraries/jsonserializer_1_0.xml new file mode 100644 index 000000000..ce5def89b --- /dev/null +++ b/.idea/libraries/jsonserializer_1_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/library_1_0.xml b/.idea/libraries/library_1_0.xml new file mode 100644 index 000000000..232baa2c9 --- /dev/null +++ b/.idea/libraries/library_1_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/library_1_0_5.xml b/.idea/libraries/library_1_0_5.xml new file mode 100644 index 000000000..5aaa6d25f --- /dev/null +++ b/.idea/libraries/library_1_0_5.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/library_2_0_0.xml b/.idea/libraries/library_2_0_0.xml new file mode 100644 index 000000000..1efd41818 --- /dev/null +++ b/.idea/libraries/library_2_0_0.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/library_2_4_0.xml b/.idea/libraries/library_2_4_0.xml new file mode 100644 index 000000000..eedfc0da4 --- /dev/null +++ b/.idea/libraries/library_2_4_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/library_2_4_1.xml b/.idea/libraries/library_2_4_1.xml new file mode 100644 index 000000000..59a59d242 --- /dev/null +++ b/.idea/libraries/library_2_4_1.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/merge_1_0_1.xml b/.idea/libraries/merge_1_0_1.xml new file mode 100644 index 000000000..6e8e5b08e --- /dev/null +++ b/.idea/libraries/merge_1_0_1.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/sacklist_1_0_0.xml b/.idea/libraries/sacklist_1_0_0.xml new file mode 100644 index 000000000..a18da6e73 --- /dev/null +++ b/.idea/libraries/sacklist_1_0_0.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/snakeyaml_1_12.xml b/.idea/libraries/snakeyaml_1_12.xml new file mode 100644 index 000000000..3f86d4bd7 --- /dev/null +++ b/.idea/libraries/snakeyaml_1_12.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_annotations_20_0_0.xml b/.idea/libraries/support_annotations_20_0_0.xml new file mode 100644 index 000000000..9e4620b69 --- /dev/null +++ b/.idea/libraries/support_annotations_20_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v13_20_0_0.xml b/.idea/libraries/support_v13_20_0_0.xml new file mode 100644 index 000000000..4fe54c1f6 --- /dev/null +++ b/.idea/libraries/support_v13_20_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v4_20_0_0.xml b/.idea/libraries/support_v4_20_0_0.xml new file mode 100644 index 000000000..083d19540 --- /dev/null +++ b/.idea/libraries/support_v4_20_0_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/support_v4_r7.xml b/.idea/libraries/support_v4_r7.xml new file mode 100644 index 000000000..ba0506653 --- /dev/null +++ b/.idea/libraries/support_v4_r7.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/twitter_text_1_9_5.xml b/.idea/libraries/twitter_text_1_9_5.xml new file mode 100644 index 000000000..f625da61f --- /dev/null +++ b/.idea/libraries/twitter_text_1_9_5.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/universal_image_loader_1_9_2.xml b/.idea/libraries/universal_image_loader_1_9_2.xml new file mode 100644 index 000000000..be34e1944 --- /dev/null +++ b/.idea/libraries/universal_image_loader_1_9_2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..b153e48ac --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..b1b80aa35 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000..922003b84 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..def6a6a18 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Twidere-Android.iml b/Twidere-Android.iml new file mode 100644 index 000000000..0bb6048ae --- /dev/null +++ b/Twidere-Android.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..a75fc8180 --- /dev/null +++ b/build.gradle @@ -0,0 +1,19 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.12.+' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..5d08ba75b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..aec99730b --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..52ad62771 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':twidere' diff --git a/twidere/.gitignore b/twidere/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/twidere/.gitignore @@ -0,0 +1 @@ +/build diff --git a/twidere/build.gradle b/twidere/build.gradle new file mode 100644 index 000000000..5abbe8739 --- /dev/null +++ b/twidere/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 19 + buildToolsVersion "20.0.0" + + defaultConfig { + applicationId "org.mariotaku.twidere" + minSdkVersion 14 + targetSdkVersion 19 + versionCode 95 + versionName "0.2.9.14" + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile 'com.android.support:support-v13:20.0.0' + compile 'com.google.android.gms:play-services:5.0.77' + compile 'com.negusoft.holoaccent:library:+' + compile 'com.etsy.android.grid:library:1.0.5' + compile 'com.viewpagerindicator:library:2.4.1' + compile 'com.sothree.slidinguppanel:library:2.0.0' + compile 'com.twitter:twitter-text:1.9.5' + compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.2' + compile 'org.apache.httpcomponents:httpclient-android:4.3.3' + compile 'org.apache.httpcomponents:httpmime:4.3.3' + compile 'ch.acra:acra:4.5.0' + compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0' + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/twidere/libs/dnsjava-ipv6-1.0-with-sources.jar b/twidere/libs/dnsjava-ipv6-1.0-with-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..b0938aaf0b096e0c5cc6da48174c9950fd7d78dc GIT binary patch literal 141136 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 literal 0 HcmV?d00001 diff --git a/twidere/libs/jsonserializer-1.0.jar b/twidere/libs/jsonserializer-1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..663bb286350369093f01b2e39adf00d3c4ca1e82 GIT binary patch literal 12154 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 E0b4f)Q2+n{ literal 0 HcmV?d00001 diff --git a/twidere/libs/merge-1.0.1.jar b/twidere/libs/merge-1.0.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..fb49c1530e930d1ecd8d3e5670605574c9ef0883 GIT binary patch literal 9144 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 Lp^gwpe}4Nf5CZB@ literal 0 HcmV?d00001 diff --git a/twidere/libs/sacklist-1.0.0.jar b/twidere/libs/sacklist-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..3a49a76111a23f313ed0f4c3926d77ea1d6ad4da GIT binary patch literal 2628 zcmWIWW@h1HVBp|j*c{U5%m4&TAOZ+Df!NnI#8KDN&rP41Apk|;rh2A#(m(~0KrDi+ z(AUw=)6F$FM9Elt5rC=zs>{vKD=tqgO2w;Q6pQ-g^2B7kn&fb4Do#w!&dDq;!O%UA=Us0((EU-s zkTWBwI~Zt;e_B{(YI(6^N@77tYLQ-YPGWKK)R5Tx+aVHleRIunXVtBb_ihxsF>Q&w zWU9fn^=q~7-qMRs)YR4GIqAFSobp!X+nJi%57;uP=sjRlIl|kacyN^w=MLvb`#-P? zB-tD!O{4N44 zx2?Rd@jmC}w^iFhmn_xJe!BJ1tTgFQ)&^Y7=Ygj(@U-`&tl}_uY1XvT^4=e7pAVM`ps@t z%KnliYLRc_>@zzB?_RsB8h&5!MdH1>h(P1*#@f;4U-#&w1)nV9xxT?C@1@QA?vTlv z%WgY1vrN8o(6O4KBIO&m*L}TdiR&V!q@QcjY!$Pa;rs3MiijoOm#z19;y9x^{nd)~ zrm*E?I@L>t8c<0vDWF@W1fJmyM;%V@fRNi^!_%66uIr_QHDB zIih#hr}lig^!n+y_p1t>j!d;)A8(j4`*+syj2JV6zPL5VF1F@9i;`@y zynXH4Q)_i4m+C-c58+|zftFh@buT}ZR>oYXywv_iu^-#voIXGe%zL`qJcowIA> zV*NdnuY3QSz>^p8bm81hkNF~XcIJwFS*UP7B`YH6zL3T5`t|IV{V8uHS;Op#Ba6R$ zn2>xVGOxPiMdmdwR?8b(lXasP3bm~*?kSJ-zB*Ap{YHP({axFegr8=$y$pHyJm$8+ z_R6gWXY3=?|5q_}<*?pY|FLudd*QpAjO+exscGZ*-+Z9r7lV>+UCX38hEnCi29EEq z&LlkYp5yVK!Bu9Fb-|}qjT*uk$3#A+8=UBy`>>j4dcmaS z&R^atr#Gp0Jvk)Y)4aX9%gm8KVC|;~d08Tdofztcytry-DF&@Lww)($J%{YWpukJ6 zya$pNq)6uOc((ETuSCW4>VW?A9n-gd(6vo$o%i&eh#&jcoe~$eosd%DySU=Pyyt3~ z(tAH#O*YY0^yT(e2o^3{$GoBVp1Hu}g!T@un&8&b(}zE$9%a&LEa{lQDIj&}`^6C1 zn%@xx&Js3F7ZrZE=1WOTUeTu`>+Q%H9B@eF*bPnjWD|h}E3QiXK7M2B1l#7#SKpXU zeE*mIck!DQ{U0r>?<$0b?E0uZOI?|H;{6Nr{d(@Y=NLVj%j?2-aZysOLXrNQ332-0 zoWK26{`M<*QT?Gqb+0zg|6J(&?P5dhyLr!7K6)FMAhycf`*~(}Me%KxkK#drdtBag z&)}IdkN=sQZ_S6?&|UE|-r>BQR}JoJ`dpXZH_`dX)LD%`Ul|@Yf6N>0^3R%igKN-< z*>cql*Lv4&W8U&VsP$`XMYQ%|^>X*A9hE<|@5Fyndj5|QQM{v1P75CLHz+YhNp!Wi zKW=0ya8y67q;cX}kM896i;~)!_#Z5mNO5%ZUMTza-o1NwpI`sY-jMu8g=2$gvQYAD zfw}Q#-mT~O;vjL(_i9Ak&7Rj`Z0C5L{)dE~nkU$__ivBDIbTK2#UY`ecIg&J-v1b5 zojOVCVCk{!s~_VHxA?D8lv#FY%9*{Fcq2YW8Et&jWEx|WeKjKN#H9_9t4ki5RfKd4 zI~8T$iaOza^=QxkE6cq99b%6-Y?b(He}woIQ{%I%S$ChD9uy(7u>FAiu5N+A@(szh zN8Y*L6>0Abp7GqFXN(5CGCK z3&;fNK(4>>=?2x`5CGDB3dls)4X(}c83C%zApm5=e;^a95%9Vl-7NIF9cJ2=Mq6O| z#%~s`njYN_keRT?7bw#~0LZ-@Y=rCpXF+rW(X$}T$SsZD#2JW@Ap^WwfjU8LLr#W% Kpj+p&gLnWyLdL29 literal 0 HcmV?d00001 diff --git a/twidere/proguard-rules.pro b/twidere/proguard-rules.pro new file mode 100644 index 000000000..bb65c6fe8 --- /dev/null +++ b/twidere/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java b/twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java new file mode 100644 index 000000000..2436044f1 --- /dev/null +++ b/twidere/src/androidTest/java/org/mariotaku/twidere/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.mariotaku.myapplication; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/twidere/src/assets/fonts/TwidereIconic.ttf b/twidere/src/assets/fonts/TwidereIconic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0dd32fc3afdb7819a6ead3d7340f072041047caa GIT binary patch literal 13420 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 V7p~dRz~WbJ_u(xE^?z=x{{s;A!ZH8= literal 0 HcmV?d00001 diff --git a/twidere/src/assets/gpl-3.0-standalone.html b/twidere/src/assets/gpl-3.0-standalone.html new file mode 100644 index 000000000..c0392120d --- /dev/null +++ b/twidere/src/assets/gpl-3.0-standalone.html @@ -0,0 +1,694 @@ + + + + + GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF) + + + +

GNU GENERAL PUBLIC LICENSE

+

Version 3, 29 June 2007

+ +

Copyright © 2007 Free Software Foundation, Inc. + <http://fsf.org/>

+ Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed.

+ +

Preamble

+ +

The GNU General Public License is a free, copyleft license for +software and other kinds of works.

+ +

The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too.

+ +

When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things.

+ +

To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others.

+ +

For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights.

+ +

Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it.

+ +

For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions.

+ +

Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users.

+ +

Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free.

+ +

The precise terms and conditions for copying, distribution and +modification follow.

+ +

TERMS AND CONDITIONS

+ +

0. Definitions.

+ +

“This License” refers to version 3 of the GNU General Public License.

+ +

“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks.

+ +

“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations.

+ +

To “modify” a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a “modified version” of the +earlier work or a work “based on” the earlier work.

+ +

A “covered work” means either the unmodified Program or a work based +on the Program.

+ +

To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well.

+ +

To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying.

+ +

An interactive user interface displays “Appropriate Legal Notices” +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion.

+ +

1. Source Code.

+ +

The “source code” for a work means the preferred form of the work +for making modifications to it. “Object code” means any non-source +form of a work.

+ +

A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language.

+ +

The “System Libraries” of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +“Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it.

+ +

The “Corresponding Source” for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work.

+ +

The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source.

+ +

The Corresponding Source for a work in source code form is that +same work.

+ +

2. Basic Permissions.

+ +

All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law.

+ +

You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you.

+ +

Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary.

+ +

3. Protecting Users' Legal Rights From Anti-Circumvention Law.

+ +

No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures.

+ +

When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures.

+ +

4. Conveying Verbatim Copies.

+ +

You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program.

+ +

You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee.

+ +

5. Conveying Modified Source Versions.

+ +

You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions:

+ +
    +
  • a) The work must carry prominent notices stating that you modified + it, and giving a relevant date.
  • + +
  • b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + “keep intact all notices”.
  • + +
  • c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it.
  • + +
  • d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so.
  • +
+ +

A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +“aggregate” if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate.

+ +

6. Conveying Non-Source Forms.

+ +

You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways:

+ +
    +
  • a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange.
  • + +
  • b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge.
  • + +
  • c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b.
  • + +
  • d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements.
  • + +
  • e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d.
  • +
+ +

A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work.

+ +

A “User Product” is either (1) a “consumer product”, which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, “normally used” refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product.

+ +

“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made.

+ +

If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM).

+ +

The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network.

+ +

Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying.

+ +

7. Additional Terms.

+ +

“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions.

+ +

When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission.

+ +

Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms:

+ +
    +
  • a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or
  • + +
  • b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or
  • + +
  • c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or
  • + +
  • d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or
  • + +
  • e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or
  • + +
  • f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors.
  • +
+ +

All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying.

+ +

If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms.

+ +

Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way.

+ +

8. Termination.

+ +

You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11).

+ +

However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation.

+ +

Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice.

+ +

Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10.

+ +

9. Acceptance Not Required for Having Copies.

+ +

You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so.

+ +

10. Automatic Licensing of Downstream Recipients.

+ +

Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License.

+ +

An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts.

+ +

You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it.

+ +

11. Patents.

+ +

A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's “contributor version”.

+ +

A contributor's “essential patent claims” are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License.

+ +

Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version.

+ +

In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To “grant” such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party.

+ +

If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. “Knowingly relying” means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid.

+ +

If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it.

+ +

A patent license is “discriminatory” if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007.

+ +

Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law.

+ +

12. No Surrender of Others' Freedom.

+ +

If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program.

+ +

13. Use with the GNU Affero General Public License.

+ +

Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such.

+ +

14. Revised Versions of this License.

+ +

The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns.

+ +

Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License “or any later version” applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation.

+ +

If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program.

+ +

Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version.

+ +

15. Disclaimer of Warranty.

+ +

THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

+ +

16. Limitation of Liability.

+ +

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES.

+ +

17. Interpretation of Sections 15 and 16.

+ +

If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee.

+ +

END OF TERMS AND CONDITIONS

+ +

How to Apply These Terms to Your New Programs

+ +

If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms.

+ +

To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the “copyright” line and a pointer to where the full notice is found.

+ +
    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+ +

Also add information on how to contact you by electronic and paper mail.

+ +

If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode:

+ +
    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+ +

The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an “about box”.

+ +

You should also get your employer (if you work as a programmer) or school, +if any, to sign a “copyright disclaimer” for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<http://www.gnu.org/licenses/>.

+ +

The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<http://www.gnu.org/philosophy/why-not-lgpl.html>.

+ + diff --git a/twidere/src/assets/images/loading_tile.png b/twidere/src/assets/images/loading_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..f5a80c953979fb0329699a7fe825c9d817ddf849 GIT binary patch literal 729 zcmeAS@N?(olHy`uVBq!ia0vp^4InJQ1|%o1+uH%8*pj^6UH*dsXT(OT^vIyZoR#;k+<1Fg!SNIUx)vD6=fH3$UHp2tFY1dX1|%UzxakZ z&*%KEi!DA^DU+OYd)wOA5_#Ky|NUN6&%id}bEQ^IedCv}U+2G$W)MhYeDS*dw-`f9 zB*U*?5BIS$B#JfctJ{&!=rDtI!Fu_3Se5K&{qWzlX!qUs`*j&OKC|IeM}$%T*dLU& z=W{b0+Cz>*;1>L6+;R8eZKei;d=k6?*A4UOvwDWS@*mq66wXK!4g?I{@SyxD-*8)Y f-|z~WZ>-a~inGp4xVICS8W}uY{an^LB{Ts5?rJ + + + Google Maps + + + + + + + +
+ + diff --git a/twidere/src/main/AndroidManifest.xml b/twidere/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7e2fd642d --- /dev/null +++ b/twidere/src/main/AndroidManifest.xml @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/java/android/support/v4/app/BackStackEntryTrojan.java b/twidere/src/main/java/android/support/v4/app/BackStackEntryTrojan.java new file mode 100755 index 000000000..497271271 --- /dev/null +++ b/twidere/src/main/java/android/support/v4/app/BackStackEntryTrojan.java @@ -0,0 +1,9 @@ +package android.support.v4.app; + +public class BackStackEntryTrojan { + + public static Fragment getFragmentInBackStackRecord(final FragmentManager.BackStackEntry entry) { + if (entry instanceof BackStackRecord) return ((BackStackRecord) entry).mHead.fragment; + return null; + } +} diff --git a/twidere/src/main/java/android/support/v4/app/FragmentManagerTrojan.java b/twidere/src/main/java/android/support/v4/app/FragmentManagerTrojan.java new file mode 100755 index 000000000..07f165613 --- /dev/null +++ b/twidere/src/main/java/android/support/v4/app/FragmentManagerTrojan.java @@ -0,0 +1,9 @@ +package android.support.v4.app; + +public class FragmentManagerTrojan { + + public static boolean isStateSaved(final FragmentManager fm) { + if (fm instanceof FragmentManagerImpl) return ((FragmentManagerImpl) fm).mStateSaved; + return false; + } +} diff --git a/twidere/src/main/java/android/support/v4/app/FragmentTrojan.java b/twidere/src/main/java/android/support/v4/app/FragmentTrojan.java new file mode 100644 index 000000000..f47c5d39b --- /dev/null +++ b/twidere/src/main/java/android/support/v4/app/FragmentTrojan.java @@ -0,0 +1,11 @@ +package android.support.v4.app; + +import android.os.Bundle; + +public class FragmentTrojan { + + public static Bundle getSavedFragmentState(final Fragment f) { + return f.mSavedFragmentState; + } + +} diff --git a/twidere/src/main/java/android/support/v4/app/ListFragmentTrojan.java b/twidere/src/main/java/android/support/v4/app/ListFragmentTrojan.java new file mode 100755 index 000000000..ea123bf9a --- /dev/null +++ b/twidere/src/main/java/android/support/v4/app/ListFragmentTrojan.java @@ -0,0 +1,27 @@ +/* + * Tweetings - Twitter client for Android + * + * Copyright (C) 2012 Mariotaku Lee + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package android.support.v4.app; + +public interface ListFragmentTrojan { + + public static final int INTERNAL_EMPTY_ID = ListFragment.INTERNAL_EMPTY_ID; + public static final int INTERNAL_PROGRESS_CONTAINER_ID = ListFragment.INTERNAL_PROGRESS_CONTAINER_ID; + public static final int INTERNAL_LIST_CONTAINER_ID = ListFragment.INTERNAL_LIST_CONTAINER_ID; +} diff --git a/twidere/src/main/java/com/atermenji/android/iconicdroid/IconicFontDrawable.java b/twidere/src/main/java/com/atermenji/android/iconicdroid/IconicFontDrawable.java new file mode 100644 index 000000000..36f43f1ec --- /dev/null +++ b/twidere/src/main/java/com/atermenji/android/iconicdroid/IconicFontDrawable.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2013 Artur Termenji + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.atermenji.android.iconicdroid; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Paint.FontMetrics; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +import com.atermenji.android.iconicdroid.icon.Icon; + +/** + * A custom {@link Drawable} which can display icons from icon fonts. + */ +public class IconicFontDrawable extends Drawable { + + private final Context mContext; + + private final Paint mIconPaint; + private final Paint mContourPaint; + + private final Rect mPaddingBounds; + private final RectF mPathBounds; + + private final Path mPath; + + private int mIconPadding; + private int mContourWidth; + + private int mIntrinsicWidth; + private int mIntrinsicHeight; + + private boolean mDrawContour; + + private Icon mIcon; + private char[] mIconUtfChars; + + public IconicFontDrawable(final Context context) { + mContext = context.getApplicationContext(); + + mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + mContourPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mContourPaint.setStyle(Paint.Style.STROKE); + + mPath = new Path(); + + mPathBounds = new RectF(); + mPaddingBounds = new Rect(); + } + + public IconicFontDrawable(final Context context, final Icon icon) { + this(context); + updateIcon(icon); + } + + @Override + public void draw(final Canvas canvas) { + if (mIcon != null) { + final Rect viewBounds = getBounds(); + + updatePaddingBounds(viewBounds); + updateTextSize(viewBounds); + offsetIcon(viewBounds); + + mPath.close(); + + if (mDrawContour) { + canvas.drawPath(mPath, mContourPaint); + } + + canvas.drawPath(mPath, mIconPaint); + + } + } + + /** + * Enable/disable contour drawing. + * + * @param drawContour + */ + public void drawContour(final boolean drawContour) { + mDrawContour = drawContour; + + if (mDrawContour) { + mIconPadding += mContourWidth; + } else { + mIconPadding -= mContourWidth; + } + + invalidateSelf(); + } + + @Override + public int getIntrinsicHeight() { + return mIntrinsicHeight; + } + + @Override + public int getIntrinsicWidth() { + return mIntrinsicWidth; + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + @Override + public void setAlpha(final int alpha) { + mIconPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(final ColorFilter cf) { + mIconPaint.setColorFilter(cf); + } + + /** + * Set contour params for the {@link Icon}. You should call + * {@link #drawContour(boolean)} method to enable contour. + * + * @param contourColor + * @param contourWidth + */ + public void setContour(final int contourColor, final int contourWidth) { + setContourColor(contourColor); + setContourWidth(contourWidth); + invalidateSelf(); + } + + /** + * Set contour color for the {@link Icon}. You should call + * {@link #drawContour(boolean)} method to enable contour. + * + * @param contourColor + */ + public void setContourColor(final int contourColor) { + mContourPaint.setColor(contourColor); + invalidateSelf(); + } + + /** + * Set contour width for the {@link Icon}. You should call + * {@link #drawContour(boolean)} method to enable contour. + * + * @param contourWidth + */ + public void setContourWidth(final int contourWidth) { + mContourWidth = contourWidth; + mContourPaint.setStrokeWidth(mContourWidth); + invalidateSelf(); + } + + /** + * Loads and draws given {@link Icon}. + * + * @param icon + */ + public void setIcon(final Icon icon) { + updateIcon(icon); + invalidateSelf(); + } + + /** + * Set a color for the {@link Icon}. + * + * @param color + */ + public void setIconColor(final int color) { + mIconPaint.setColor(color); + invalidateSelf(); + } + + /** + * Set a padding for the {@link Icon}. + * + * @param iconPadding + */ + public void setIconPadding(final int iconPadding) { + mIconPadding = iconPadding; + if (mDrawContour) { + mIconPadding += mContourWidth; + } + + invalidateSelf(); + } + + /** + * Set intrinsic height, which is used by several controls. + * + * @param intrinsicHeight + */ + public void setIntrinsicHeight(final int intrinsicHeight) { + mIntrinsicHeight = intrinsicHeight; + } + + /** + * Set intrinsic width, which is used by several controls. + * + * @param intrinsicWidth + */ + public void setIntrinsicWidth(final int intrinsicWidth) { + mIntrinsicWidth = intrinsicWidth; + } + + private void offsetIcon(final Rect viewBounds) { + // final float startX = viewBounds.centerX() - mPathBounds.width() / 2; + // final float offsetX = startX - mPathBounds.left; + // final float startY = viewBounds.centerY() - mPathBounds.height() / 2; + // final float offsetY = startY - mPathBounds.top; + + final FontMetrics metrics = mIconPaint.getFontMetrics(); + final float textSize = mIconPaint.getTextSize(); + final float offsetX = viewBounds.centerX() - textSize / 2; + final float offsetY = viewBounds.centerY() - (metrics.ascent + metrics.descent) / 2 + + (metrics.bottom - metrics.descent); + + mPath.offset(offsetX, offsetY); + } + + private void updateIcon(final Icon icon) { + mIcon = icon; + mIconUtfChars = Character.toChars(icon.getIconUtfValue()); + mIconPaint.setTypeface(mIcon.getIconicTypeface().getTypeface(mContext)); + } + + private void updatePaddingBounds(final Rect viewBounds) { + if (mIconPadding >= 0 && !(mIconPadding * 2 > viewBounds.width()) && !(mIconPadding * 2 > viewBounds.height())) { + mPaddingBounds.set(viewBounds.left + mIconPadding, viewBounds.top + mIconPadding, viewBounds.right + - mIconPadding, viewBounds.bottom - mIconPadding); + } + } + + private void updateTextSize(final Rect viewBounds) { + final float textSize = viewBounds.height(); + mIconPaint.setTextSize(textSize); + mIconPaint.getTextPath(mIconUtfChars, 0, mIconUtfChars.length, 0, 0, mPath); + mPath.computeBounds(mPathBounds, true); + + // final float deltaWidth = mPaddingBounds.width() / + // mPathBounds.width(); + // final float deltaHeight = mPaddingBounds.height() / + // mPathBounds.height(); + // final float delta = deltaWidth < deltaHeight ? deltaWidth : + // deltaHeight; + // textSize *= delta; + // + // mIconPaint.setTextSize(textSize); + + // mIconPaint.getTextPath(mIconUtfChars, 0, mIconUtfChars.length, 0, + // viewBounds.height(), mPath); + // mPath.computeBounds(mPathBounds, true); + } +} diff --git a/twidere/src/main/java/com/atermenji/android/iconicdroid/icon/Icon.java b/twidere/src/main/java/com/atermenji/android/iconicdroid/icon/Icon.java new file mode 100644 index 000000000..dd6b5ef09 --- /dev/null +++ b/twidere/src/main/java/com/atermenji/android/iconicdroid/icon/Icon.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 Artur Termenji + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.atermenji.android.iconicdroid.icon; + +import android.graphics.Typeface; + +import com.atermenji.android.iconicdroid.util.TypefaceManager.IconicTypeface; + +import java.io.Serializable; + +/** + * An interface which every icon font wrapper should implement. + */ +public interface Icon extends Serializable { + + /** + * Gets a {@link Typeface} for an Icon. + * + * @return {@link IconicTypeface} + */ + public IconicTypeface getIconicTypeface(); + + /** + * Returns UTF value of an Icon. + * + * @return UTF value of an Icon + */ + public int getIconUtfValue(); + +} diff --git a/twidere/src/main/java/com/atermenji/android/iconicdroid/util/TypefaceManager.java b/twidere/src/main/java/com/atermenji/android/iconicdroid/util/TypefaceManager.java new file mode 100644 index 000000000..e653b2174 --- /dev/null +++ b/twidere/src/main/java/com/atermenji/android/iconicdroid/util/TypefaceManager.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 Artur Termenji + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.atermenji.android.iconicdroid.util; + +import android.content.Context; +import android.graphics.Typeface; + +/** + * Helper class that wraps icon fonts and manages {@link Typeface} loading. + */ +public class TypefaceManager { + + private TypefaceManager() { + } + + public static interface IconicTypeface { + + /** + * Loads a {@link Typeface} for the given icon font. {@link Typeface} is + * loaded only once to avoid memory consumption. + * + * @param context + * @return {@link Typeface} + */ + public Typeface getTypeface(final Context context); + } +} diff --git a/twidere/src/main/java/com/readystatesoftware/viewbadger/BadgeView.java b/twidere/src/main/java/com/readystatesoftware/viewbadger/BadgeView.java new file mode 100644 index 000000000..00b7b796d --- /dev/null +++ b/twidere/src/main/java/com/readystatesoftware/viewbadger/BadgeView.java @@ -0,0 +1,491 @@ +package com.readystatesoftware.viewbadger; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewParent; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.TabWidget; +import android.widget.TextView; + +import org.mariotaku.twidere.util.accessor.ViewAccessor; + +/** + * A simple text label view that can be applied as a "badge" to any given + * {@link android.view.View}. This class is intended to be instantiated at + * runtime rather than included in XML layouts. + * + * @author Jeff Gilfelt + */ +public class BadgeView extends TextView { + + public static final int POSITION_TOP_LEFT = 1; + public static final int POSITION_TOP_RIGHT = 2; + public static final int POSITION_BOTTOM_LEFT = 3; + public static final int POSITION_BOTTOM_RIGHT = 4; + public static final int POSITION_CENTER = 5; + + private static final int DEFAULT_MARGIN_DIP = 5; + private static final int DEFAULT_LR_PADDING_DIP = 5; + private static final int DEFAULT_CORNER_RADIUS_DIP = 8; + private static final int DEFAULT_POSITION = POSITION_TOP_RIGHT; + private static final int DEFAULT_BADGE_COLOR = Color.parseColor("#CCFF4444"); // holo_red_light; + private static final int DEFAULT_TEXT_COLOR = Color.WHITE; + + private static Animation fadeIn; + private static Animation fadeOut; + + private Context context; + private View target; + + private int badgePosition; + private int badgeMarginH; + private int badgeMarginV; + private int badgeColor; + + private boolean isShown; + + private ShapeDrawable badgeBg; + + private int targetTabIndex; + + public BadgeView(final Context context) { + this(context, (AttributeSet) null, android.R.attr.textViewStyle); + } + + public BadgeView(final Context context, final AttributeSet attrs) { + this(context, attrs, android.R.attr.textViewStyle); + } + + public BadgeView(final Context context, final AttributeSet attrs, final int defStyle) { + this(context, attrs, defStyle, null, 0); + } + + public BadgeView(final Context context, final AttributeSet attrs, final int defStyle, final View target, + final int tabIndex) { + super(context, attrs, defStyle); + init(context, target, tabIndex); + } + + /** + * Constructor - + * + * create a new BadgeView instance attached to a target + * {@link android.widget.TabWidget} tab at a given index. + * + * @param context context for this view. + * @param target the TabWidget to attach the badge to. + * @param index the position of the tab within the target. + */ + public BadgeView(final Context context, final TabWidget target, final int index) { + this(context, null, android.R.attr.textViewStyle, target, index); + } + + /** + * Constructor - + * + * create a new BadgeView instance attached to a target + * {@link android.view.View}. + * + * @param context context for this view. + * @param target the View to attach the badge to. + */ + public BadgeView(final Context context, final View target) { + this(context, null, android.R.attr.textViewStyle, target, 0); + } + + /** + * Decrement the numeric badge label. If the current badge label cannot be + * converted to an integer value, its label will be set to "0". + * + * @param offset the decrement offset. + */ + public int decrement(final int offset) { + return increment(-offset); + } + + /** + * Returns the color value of the badge background. + * + */ + public int getBadgeBackgroundColor() { + return badgeColor; + } + + /** + * Returns the positioning of this badge. + * + * one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, POSITION_BOTTOM_LEFT, + * POSITION_BOTTOM_RIGHT, POSTION_CENTER. + * + */ + public int getBadgePosition() { + return badgePosition; + } + + /** + * Returns the horizontal margin from the target View that is applied to + * this badge. + * + */ + public int getHorizontalBadgeMargin() { + return badgeMarginH; + } + + /** + * Returns the target View this badge has been attached to. + * + */ + public View getTarget() { + return target; + } + + /** + * Returns the vertical margin from the target View that is applied to this + * badge. + * + */ + public int getVerticalBadgeMargin() { + return badgeMarginV; + } + + /** + * Make the badge non-visible in the UI. + * + */ + public void hide() { + hide(false, null); + } + + /** + * Make the badge non-visible in the UI. + * + * @param anim Animation to apply to the view when made non-visible. + */ + public void hide(final Animation anim) { + hide(true, anim); + } + + /** + * Make the badge non-visible in the UI. + * + * @param animate flag to apply the default fade-out animation. + */ + public void hide(final boolean animate) { + hide(animate, fadeOut); + } + + /** + * Increment the numeric badge label. If the current badge label cannot be + * converted to an integer value, its label will be set to "0". + * + * @param offset the increment offset. + */ + public int increment(final int offset) { + final CharSequence txt = getText(); + int i; + if (txt != null) { + try { + i = Integer.parseInt(txt.toString()); + } catch (final NumberFormatException e) { + i = 0; + } + } else { + i = 0; + } + i = i + offset; + setText(String.valueOf(i)); + return i; + } + + /** + * Is this badge currently visible in the UI? + * + */ + @Override + public boolean isShown() { + return isShown; + } + + /** + * Set the color value of the badge background. + * + * @param badgeColor the badge background color. + */ + public void setBadgeBackgroundColor(final int badgeColor) { + this.badgeColor = badgeColor; + badgeBg = getDefaultBackground(); + } + + /** + * Set the horizontal/vertical margin from the target View that is applied + * to this badge. + * + * @param badgeMargin the margin in pixels. + */ + public void setBadgeMargin(final int badgeMargin) { + badgeMarginH = badgeMargin; + badgeMarginV = badgeMargin; + } + + /** + * Set the horizontal/vertical margin from the target View that is applied + * to this badge. + * + * @param horizontal margin in pixels. + * @param vertical margin in pixels. + */ + public void setBadgeMargin(final int horizontal, final int vertical) { + badgeMarginH = horizontal; + badgeMarginV = vertical; + } + + /** + * Set the positioning of this badge. + * + * @param layoutPosition one of POSITION_TOP_LEFT, POSITION_TOP_RIGHT, + * POSITION_BOTTOM_LEFT, POSITION_BOTTOM_RIGHT, POSTION_CENTER. + * + */ + public void setBadgePosition(final int layoutPosition) { + badgePosition = layoutPosition; + } + + public int setCount(final int count) { + setText(String.valueOf(count)); + return count; + } + + /** + * Make the badge visible in the UI. + * + */ + public void show() { + show(false, null); + } + + /** + * Make the badge visible in the UI. + * + * @param anim Animation to apply to the view when made visible. + */ + public void show(final Animation anim) { + show(true, anim); + } + + /** + * Make the badge visible in the UI. + * + * @param animate flag to apply the default fade-in animation. + */ + public void show(final boolean animate) { + show(animate, fadeIn); + } + + /** + * Toggle the badge visibility in the UI. + * + */ + public void toggle() { + toggle(false, null, null); + } + + /** + * Toggle the badge visibility in the UI. + * + * @param animIn Animation to apply to the view when made visible. + * @param animOut Animation to apply to the view when made non-visible. + */ + public void toggle(final Animation animIn, final Animation animOut) { + toggle(true, animIn, animOut); + } + + /** + * Toggle the badge visibility in the UI. + * + * @param animate flag to apply the default fade-in/out animation. + */ + public void toggle(final boolean animate) { + toggle(animate, fadeIn, fadeOut); + } + + @Override + protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { + setMinWidth(h); + super.onSizeChanged(w, h, oldw, oldh); + } + + private void applyLayoutParams() { + + final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + + switch (badgePosition) { + case POSITION_TOP_LEFT: + lp.gravity = Gravity.LEFT | Gravity.TOP; + lp.setMargins(badgeMarginH, badgeMarginV, 0, 0); + break; + case POSITION_TOP_RIGHT: + lp.gravity = Gravity.RIGHT | Gravity.TOP; + lp.setMargins(0, badgeMarginV, badgeMarginH, 0); + break; + case POSITION_BOTTOM_LEFT: + lp.gravity = Gravity.LEFT | Gravity.BOTTOM; + lp.setMargins(badgeMarginH, 0, 0, badgeMarginV); + break; + case POSITION_BOTTOM_RIGHT: + lp.gravity = Gravity.RIGHT | Gravity.BOTTOM; + lp.setMargins(0, 0, badgeMarginH, badgeMarginV); + break; + case POSITION_CENTER: + lp.gravity = Gravity.CENTER; + lp.setMargins(0, 0, 0, 0); + break; + default: + break; + } + + setLayoutParams(lp); + + } + + private void applyTo(View target) { + + final LayoutParams lp = target.getLayoutParams(); + final ViewParent parent = target.getParent(); + final FrameLayout container = new FrameLayout(context); + + if (target instanceof TabWidget) { + + // set target to the relevant tab child container + target = ((TabWidget) target).getChildTabViewAt(targetTabIndex); + this.target = target; + + ((ViewGroup) target).addView(container, new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + + setVisibility(View.GONE); + container.addView(this); + + } else { + + // TODO verify that parent is indeed a ViewGroup + final ViewGroup group = (ViewGroup) parent; + final int index = group.indexOfChild(target); + + group.removeView(target); + group.addView(container, index, lp); + + container.addView(target); + + setVisibility(View.GONE); + container.addView(this); + + group.invalidate(); + + } + + } + + private int dipToPixels(final int dip) { + final Resources r = getResources(); + final float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, r.getDisplayMetrics()); + return (int) px; + } + + private ShapeDrawable getDefaultBackground() { + + final int r = dipToPixels(DEFAULT_CORNER_RADIUS_DIP); + final float[] outerR = new float[] { r, r, r, r, r, r, r, r }; + + final RoundRectShape rr = new RoundRectShape(outerR, null, null); + final ShapeDrawable drawable = new ShapeDrawable(rr); + drawable.getPaint().setColor(badgeColor); + + return drawable; + + } + + private void hide(final boolean animate, final Animation anim) { + setVisibility(View.GONE); + if (animate) { + startAnimation(anim); + } + isShown = false; + } + + private void init(final Context context, final View target, final int tabIndex) { + + this.context = context; + this.target = target; + targetTabIndex = tabIndex; + + setGravity(Gravity.CENTER); + + // apply defaults + badgePosition = DEFAULT_POSITION; + badgeMarginH = dipToPixels(DEFAULT_MARGIN_DIP); + badgeMarginV = badgeMarginH; + badgeColor = DEFAULT_BADGE_COLOR; + + setTypeface(Typeface.DEFAULT_BOLD); + final int paddingPixels = dipToPixels(DEFAULT_LR_PADDING_DIP); + setPadding(paddingPixels, 0, paddingPixels, 0); + setTextColor(DEFAULT_TEXT_COLOR); + + fadeIn = new AlphaAnimation(0, 1); + fadeIn.setInterpolator(new DecelerateInterpolator()); + fadeIn.setDuration(200); + + fadeOut = new AlphaAnimation(1, 0); + fadeOut.setInterpolator(new AccelerateInterpolator()); + fadeOut.setDuration(200); + + isShown = false; + + if (this.target != null) { + applyTo(this.target); + } else { + show(); + } + + } + + private void show(final boolean animate, final Animation anim) { + if (getBackground() == null) { + if (badgeBg == null) { + badgeBg = getDefaultBackground(); + } + ViewAccessor.setBackground(this, badgeBg); + } + applyLayoutParams(); + + if (animate) { + startAnimation(anim); + } + setVisibility(View.VISIBLE); + isShown = true; + } + + private void toggle(final boolean animate, final Animation animIn, final Animation animOut) { + if (isShown) { + hide(animate && animOut != null, animOut); + } else { + show(animate && animIn != null, animIn); + } + } + +} diff --git a/twidere/src/main/java/com/scvngr/levelup/views/gallery/AbsSpinner.java b/twidere/src/main/java/com/scvngr/levelup/views/gallery/AbsSpinner.java new file mode 100644 index 000000000..45fb9f92b --- /dev/null +++ b/twidere/src/main/java/com/scvngr/levelup/views/gallery/AbsSpinner.java @@ -0,0 +1,517 @@ +package com.scvngr.levelup.views.gallery; + +/* + * Modified from the Android Source code. The example for how to do so was viewed here: + * http://www.inter-fuser.com/2010/01/android-coverflow-widget.html + * + * Copyright (C) 2006 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. + */ + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; +import android.widget.SpinnerAdapter; + +/** + * An abstract base class for spinner widgets. SDK users will probably not need + * to use this class. + * + * CHECKSTYLE:OFF Android Attr + * + * @attr ref android.R.styleable#AbsSpinner_entries CHECKSTYLE:ON + */ +public abstract class AbsSpinner extends AdapterView { + + SpinnerAdapter mAdapter; + + int mHeightMeasureSpec; + int mWidthMeasureSpec; + boolean mBlockLayoutRequests; + int mSelectionLeftPadding = 0; + int mSelectionTopPadding = 0; + int mSelectionRightPadding = 0; + int mSelectionBottomPadding = 0; + Rect mSpinnerPadding = new Rect(); + View mSelectedView = null; + Interpolator mInterpolator; + + RecycleBin mRecycler = new RecycleBin(); + private DataSetObserver mDataSetObserver; + + /* + * Temporary frame to hold a child View's frame rectangle. + */ + private Rect mTouchFrame; + + /** + * Constructor. + * + * @param context the context to inflate into + */ + public AbsSpinner(final Context context) { + super(context); + initAbsSpinner(); + } + + /** + * Constructor for xml inflation. + * + * @param context the context to inflate into + * @param attrs the xml attrs + */ + public AbsSpinner(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructor for xml inflation. + * + * @param context the context to inflate into + * @param attrs the xml attrs + * @param defStyle the default style resource + */ + public AbsSpinner(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + initAbsSpinner(); + } + + @Override + public final SpinnerAdapter getAdapter() { + return mAdapter; + } + + @Override + public final int getCount() { + return mItemCount; + } + + @Override + public final View getSelectedView() { + if (mItemCount > 0 && mSelectedPosition >= 0) + return getChildAt(mSelectedPosition - mFirstPosition); + else + return null; + } + + @Override + public void onRestoreInstanceState(final Parcelable state) { + final SavedState ss = (SavedState) state; + + super.onRestoreInstanceState(ss.getSuperState()); + + if (ss.selectedId >= 0) { + mDataChanged = true; + mNeedSync = true; + mSyncRowId = ss.selectedId; + mSyncPosition = ss.position; + mSyncMode = SYNC_SELECTED_POSITION; + requestLayout(); + } + } + + @Override + public Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + final SavedState ss = new SavedState(superState); + ss.selectedId = getSelectedItemId(); + if (ss.selectedId >= 0) { + ss.position = getSelectedItemPosition(); + } else { + ss.position = INVALID_POSITION; + } + return ss; + } + + /** + * Maps a point to a position in the list. + * + * @param x X in local coordinate + * @param y Y in local coordinate + * @return The position of the item which contains the specified point, or + * {@link #INVALID_POSITION} if the point does not intersect an + * item. + */ + public final int pointToPosition(final int x, final int y) { + Rect frame = mTouchFrame; + if (frame == null) { + mTouchFrame = new Rect(); + frame = mTouchFrame; + } + + final int count = getChildCount(); + for (int i = count - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (child.getVisibility() == View.VISIBLE) { + child.getHitRect(frame); + if (frame.contains(x, y)) return mFirstPosition + i; + } + } + return INVALID_POSITION; + } + + /** + * Override to prevent spamming ourselves with layout requests as we place + * views. + * + * @see android.view.View#requestLayout() + */ + @Override + public final void requestLayout() { + if (!mBlockLayoutRequests) { + super.requestLayout(); + } + } + + /** + * The Adapter is used to provide the data which backs this Spinner. It also + * provides methods to transform spinner items based on their position + * relative to the selected item. + * + * @param adapter The SpinnerAdapter to use for this Spinner + */ + // CHECKSTYLE:OFF unmodified + @Override + public void setAdapter(final SpinnerAdapter adapter) { + if (null != mAdapter) { + mAdapter.unregisterDataSetObserver(mDataSetObserver); + resetList(); + } + + mAdapter = adapter; + + mOldSelectedPosition = INVALID_POSITION; + mOldSelectedRowId = INVALID_ROW_ID; + + if (mAdapter != null) { + mOldItemCount = mItemCount; + mItemCount = mAdapter.getCount(); + checkFocus(); + + mDataSetObserver = new AdapterDataSetObserver(); + mAdapter.registerDataSetObserver(mDataSetObserver); + + final int position = mItemCount > 0 ? 0 : INVALID_POSITION; + + setSelectedPositionInt(position); + setNextSelectedPositionInt(position); + + if (mItemCount == 0) { + // Nothing selected + checkSelectionChanged(); + } + + } else { + checkFocus(); + resetList(); + // Nothing selected + checkSelectionChanged(); + } + + requestLayout(); + } + + // CHECKSTYLE:ON + + @Override + public final void setSelection(final int position) { + setNextSelectedPositionInt(position); + requestLayout(); + invalidate(); + } + + /** + * Jump directly to a specific item in the adapter data. + * + * @param position the position to select + * @param animate if true, animate the selection + */ + public final void setSelection(final int position, final boolean animate) { + // Animate only if requested position is already on screen somewhere + final boolean shouldAnimate = animate && mFirstPosition <= position + && position <= mFirstPosition + getChildCount() - 1; + setSelectionInt(position, shouldAnimate); + } + + // CHECKSTYLE:OFF overridden in gallery + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + // CHECKSTYLE:ON + return new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + /** + * @see android.view.View#measure(int, int) + * + * Figure out the dimensions of this Spinner. The width comes from the + * widthMeasureSpec as Spinnners can't have their width set to + * UNSPECIFIED. The height is based on the height of the selected item + * plus padding. + */ + // CHECKSTYLE:OFF unmodified + @Override + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int widthSize; + int heightSize; + + mSpinnerPadding.left = getPaddingLeft() > mSelectionLeftPadding ? getPaddingLeft() : mSelectionLeftPadding; + mSpinnerPadding.top = getPaddingTop() > mSelectionTopPadding ? getPaddingTop() : mSelectionTopPadding; + mSpinnerPadding.right = getPaddingRight() > mSelectionRightPadding ? getPaddingRight() : mSelectionRightPadding; + mSpinnerPadding.bottom = getPaddingBottom() > mSelectionBottomPadding ? getPaddingBottom() + : mSelectionBottomPadding; + + if (mDataChanged) { + handleDataChanged(); + } + + int preferredHeight = 0; + int preferredWidth = 0; + boolean needsMeasuring = true; + + final int selectedPosition = getSelectedItemPosition(); + if (selectedPosition >= 0 && mAdapter != null) { + // Try looking in the recycler. (Maybe we were measured once + // already) + View view = mRecycler.get(selectedPosition); + if (view == null) { + // Make a new one + view = mAdapter.getView(selectedPosition, null, this); + } + + if (view != null) { + // Put in recycler for re-measuring and/or layout + mRecycler.put(selectedPosition, view); + } + + if (view != null) { + if (view.getLayoutParams() == null) { + mBlockLayoutRequests = true; + view.setLayoutParams(generateDefaultLayoutParams()); + mBlockLayoutRequests = false; + } + measureChild(view, widthMeasureSpec, heightMeasureSpec); + + preferredHeight = getChildHeight(view) + mSpinnerPadding.top + mSpinnerPadding.bottom; + preferredWidth = getChildWidth(view) + mSpinnerPadding.left + mSpinnerPadding.right; + + needsMeasuring = false; + } + } + + if (needsMeasuring) { + // No views -- just use padding + preferredHeight = mSpinnerPadding.top + mSpinnerPadding.bottom; + if (widthMode == MeasureSpec.UNSPECIFIED) { + preferredWidth = mSpinnerPadding.left + mSpinnerPadding.right; + } + } + + preferredHeight = Math.max(preferredHeight, getSuggestedMinimumHeight()); + preferredWidth = Math.max(preferredWidth, getSuggestedMinimumWidth()); + + heightSize = resolveSize(preferredHeight, heightMeasureSpec); + widthSize = resolveSize(preferredWidth, widthMeasureSpec); + + setMeasuredDimension(widthSize, heightSize); + mHeightMeasureSpec = heightMeasureSpec; + mWidthMeasureSpec = widthMeasureSpec; + } + + // CHECKSTYLE:ON + + /** + * Common code for different constructor flavors. + */ + private void initAbsSpinner() { + setFocusable(true); + setWillNotDraw(false); + } + + /** + * Gets the height of the child view passed. + * + * @param child the child to get the height of + * @return the child's measured height + */ + final int getChildHeight(final View child) { + return child.getMeasuredHeight(); + } + + /** + * Gets the width of the child view passed. + * + * @param child the child to get the width of + * @return the child's measure width + */ + final int getChildWidth(final View child) { + return child.getMeasuredWidth(); + } + + @Override + final void handleDataChanged() { + /* + * FIXME -- this is called from both measure and layout. This is + * harmless right now, but we don't want to do redundant work if this + * gets more complicated + */ + super.handleDataChanged(); + } + + // CHECKSTYLE:OFF unmodified + abstract void layout(int delta, boolean animate); + + // CHECKSTYLE:ON + + /** + * Recycle all child views. + */ + final void recycleAllViews() { + final int childCount = getChildCount(); + final AbsSpinner.RecycleBin recycleBin = mRecycler; + + // All views go in recycler + for (int i = 0; i < childCount; i++) { + final View v = getChildAt(i); + final int index = mFirstPosition + i; + recycleBin.put(index, v); + } + } + + /** + * Clear out all children from the list. + */ + final void resetList() { + mDataChanged = false; + mNeedSync = false; + + removeAllViewsInLayout(); + mOldSelectedPosition = INVALID_POSITION; + mOldSelectedRowId = INVALID_ROW_ID; + + setSelectedPositionInt(INVALID_POSITION); + setNextSelectedPositionInt(INVALID_POSITION); + invalidate(); + } + + /** + * Makes the item at the supplied position selected. + * + * @param position Position to select + * @param animate Should the transition be animated + * + */ + final void setSelectionInt(final int position, final boolean animate) { + if (position != mOldSelectedPosition) { + mBlockLayoutRequests = true; + final int delta = position - mSelectedPosition; + setNextSelectedPositionInt(position); + layout(delta, animate); + mBlockLayoutRequests = false; + } + } + + class RecycleBin { + private final SparseArray mScrapHeap = new SparseArray(); + + public void put(final int position, final View v) { + mScrapHeap.put(position, v); + } + + void clear() { + final SparseArray scrapHeap = mScrapHeap; + final int count = scrapHeap.size(); + for (int i = 0; i < count; i++) { + final View view = scrapHeap.valueAt(i); + if (view != null) { + removeDetachedView(view, true); + } + } + scrapHeap.clear(); + } + + View get(final int position) { + // System.out.print("Looking for " + position); + final View result = mScrapHeap.get(position); + if (result != null) { + // System.out.println(" HIT"); + mScrapHeap.delete(position); + } else { + // System.out.println(" MISS"); + } + return result; + } + + View peek(final int position) { + // System.out.print("Looking for " + position); + return mScrapHeap.get(position); + } + } + + // CHECKSTYLE:ON + + // CHECKSTYLE:OFF unmodified + static class SavedState extends BaseSavedState { + long selectedId; + int position; + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public SavedState createFromParcel(final Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(final int size) { + return new SavedState[size]; + } + }; + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(final Parcel in) { + super(in); + selectedId = in.readLong(); + position = in.readInt(); + } + + /** + * Constructor called from {@link AbsSpinner#onSaveInstanceState()} + */ + SavedState(final Parcelable superState) { + super(superState); + } + + @Override + public String toString() { + return "AbsSpinner.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " selectedId=" + + selectedId + " position=" + position + "}"; + } + + @Override + public void writeToParcel(final Parcel out, final int flags) { + super.writeToParcel(out, flags); + out.writeLong(selectedId); + out.writeInt(position); + } + } +} diff --git a/twidere/src/main/java/com/scvngr/levelup/views/gallery/AdapterView.java b/twidere/src/main/java/com/scvngr/levelup/views/gallery/AdapterView.java new file mode 100644 index 000000000..c3fdb446e --- /dev/null +++ b/twidere/src/main/java/com/scvngr/levelup/views/gallery/AdapterView.java @@ -0,0 +1,1182 @@ +/* + * Modified from the Android Source code. The example for how to do so was viewed here: + * http://www.inter-fuser.com/2010/01/android-coverflow-widget.html + * + * Copyright (C) 2006 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 com.scvngr.levelup.views.gallery; + +// CHECKSTYLE:OFF +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.DataSetObserver; +import android.os.Handler; +import android.os.Parcelable; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.GridView; +import android.widget.ListView; +import android.widget.Spinner; + +// CHECKSTYLE:ON + +/** + * An AdapterView is a view whose children are determined by an {@link Adapter}. + * + *

+ * See {@link ListView}, {@link GridView}, {@link Spinner} and {@link Gallery} + * for commonly used subclasses of AdapterView. + */ +// CHECKSTYLE:OFF android comment +public abstract class AdapterView extends ViewGroup { + // CHECKSTYLE:ON + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the adapter does not want the item's view recycled. + */ + public static final int ITEM_VIEW_TYPE_IGNORE = -1; + + /** + * The item view type returned by {@link Adapter#getItemViewType(int)} when + * the item is a header or footer. + */ + public static final int ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2; + + /** + * The position of the first child displayed. + */ + @ViewDebug.ExportedProperty + int mFirstPosition = 0; + + /** + * The offset in pixels from the top of the AdapterView to the top of the + * view to select during the next layout. + */ + int mSpecificTop; + + /** + * Position from which to start looking for mSyncRowId. + */ + int mSyncPosition; + + /** + * Row id to look for when data has changed. + */ + long mSyncRowId = INVALID_ROW_ID; + + /** + * Height of the view when mSyncPosition and mSyncRowId where set. + */ + long mSyncHeight; + + /** + * True if we need to sync to mSyncRowId. + */ + boolean mNeedSync = false; + + /** + * Indicates whether to sync based on the selection or position. Possible + * values are {@link #SYNC_SELECTED_POSITION} or + * {@link #SYNC_FIRST_POSITION}. + */ + int mSyncMode; + + /** + * Our height after the last layout. + */ + private int mLayoutHeight; + + /** + * Sync based on the selected child. + */ + static final int SYNC_SELECTED_POSITION = 0; + + /** + * Sync based on the first child displayed. + */ + static final int SYNC_FIRST_POSITION = 1; + + /** + * Maximum amount of time to spend in {@link #findSyncPosition()}. + */ + static final int SYNC_MAX_DURATION_MILLIS = 100; + + /** + * Indicates that this view is currently being laid out. + */ + boolean mInLayout = false; + + /** + * The listener that receives notifications when an item is selected. + */ + OnItemSelectedListener mOnItemSelectedListener; + + /** + * The listener that receives notifications when an item is clicked. + */ + OnItemClickListener mOnItemClickListener; + + /** + * The listener that receives notifications when an item is long clicked. + */ + OnItemLongClickListener mOnItemLongClickListener; + + /** + * True if the data has changed since the last layout. + */ + boolean mDataChanged; + + /** + * The position within the adapter's data set of the item to select during + * the next layout. + */ + @ViewDebug.ExportedProperty + int mNextSelectedPosition = INVALID_POSITION; + + /** + * The item id of the item to select during the next layout. + */ + long mNextSelectedRowId = INVALID_ROW_ID; + + /** + * The position within the adapter's data set of the currently selected + * item. + */ + @ViewDebug.ExportedProperty + int mSelectedPosition = INVALID_POSITION; + + /** + * The item id of the currently selected item. + */ + long mSelectedRowId = INVALID_ROW_ID; + + /** + * View to show if there are no items to show. + */ + View mEmptyView; + + /** + * The number of items in the current adapter. + */ + @ViewDebug.ExportedProperty + int mItemCount; + + /** + * The number of items in the adapter before a data changed event occured. + */ + int mOldItemCount; + + /** + * Represents an invalid position. All valid positions are in the range 0 to + * 1 less than the number of items in the current adapter. + */ + public static final int INVALID_POSITION = -1; + + /** + * Represents an empty or invalid row id. + */ + public static final long INVALID_ROW_ID = Long.MIN_VALUE; + + /** + * The last selected position we used when notifying. + */ + int mOldSelectedPosition = INVALID_POSITION; + + /** + * The id of the last selected position we used when notifying. + */ + long mOldSelectedRowId = INVALID_ROW_ID; + + /** + * Indicates what focusable state is requested when calling setFocusable(). + * In addition to this, this view has other criteria for actually + * determining the focusable state (such as whether its empty or the text + * filter is shown). + * + * @see #setFocusable(boolean) + * @see #checkFocus() + */ + private boolean mDesiredFocusableState; + private boolean mDesiredFocusableInTouchModeState; + + private SelectionNotifier mSelectionNotifier; + /** + * When set to true, calls to requestLayout() will not propagate up the + * parent hierarchy. This is used to layout the children during a layout + * pass. + */ + boolean mBlockLayoutRequests = false; + + /** + * Constructor. + * + * @param context the context to inflate with + */ + public AdapterView(final Context context) { + super(context); + } + + /** + * Constructor for xml inflation. + * + * @param context the context to inflate with + * @param attrs the xml attrs + */ + public AdapterView(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + /** + * Constructor for xml inflation. + * + * @param context the contex to inflate with + * @param attrs the xml attrs + * @param defStyle the default style resource + */ + public AdapterView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @param child Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void addView(final View child) { + throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @param child Ignored. + * @param index Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void addView(final View child, final int index) { + throw new UnsupportedOperationException("addView(View, int) is " + "not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @param child Ignored. + * @param index Ignored. + * @param params Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void addView(final View child, final int index, final LayoutParams params) { + throw new UnsupportedOperationException("addView(View, int, LayoutParams) " + "is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @param child Ignored. + * @param params Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void addView(final View child, final LayoutParams params) { + throw new UnsupportedOperationException("addView(View, LayoutParams) " + "is not supported in AdapterView"); + } + + /** + * Returns the adapter currently associated with this widget. + * + * @return The adapter used to provide this view's content. + */ + public abstract T getAdapter(); + + /** + * @return The number of items owned by the Adapter associated with this + * AdapterView. (This is the number of data items, which may be + * larger than the number of visible view.) + */ + // CHECKSTYLE:OFF overridden in subclass + // @ViewDebug.CapturedViewProperty + public int getCount() { + return mItemCount; + } + + // CHECKSTYLE:ON + + /** + * When the current adapter is empty, the AdapterView can display a special + * view call the empty view. The empty view is used to provide feedback to + * the user that no data is available in this AdapterView. + * + * @return The view to show if the adapter is empty. + */ + public final View getEmptyView() { + return mEmptyView; + } + + /** + * Returns the position within the adapter's data set for the first item + * displayed on screen. + * + * @return The position within the adapter's data set + */ + public final int getFirstVisiblePosition() { + return mFirstPosition; + } + + /** + * Gets the data associated with the specified position in the list. + * + * @param position Which data to get + * @return The data associated with the specified position in the list + */ + // CHECKSTYLE:OFF unmodified + public Object getItemAtPosition(final int position) { + final T adapter = getAdapter(); + return adapter == null || position < 0 ? null : adapter.getItem(position); + } + + public long getItemIdAtPosition(final int position) { + final T adapter = getAdapter(); + return adapter == null || position < 0 ? INVALID_ROW_ID : adapter.getItemId(position); + } + + // CHECKSTYLE:ON + + /** + * Returns the position within the adapter's data set for the last item + * displayed on screen. + * + * @return The position within the adapter's data set + */ + public final int getLastVisiblePosition() { + return mFirstPosition + getChildCount() - 1; + } + + /** + * @return The callback to be invoked with an item in this AdapterView has + * been clicked, or null id no callback has been set. + */ + public final OnItemClickListener getOnItemClickListener() { + return mOnItemClickListener; + } + + /** + * @return The callback to be invoked with an item in this AdapterView has + * been clicked and held, or null id no callback as been set. + */ + public final OnItemLongClickListener getOnItemLongClickListener() { + return mOnItemLongClickListener; + } + + /** + * Gets the OnItemSelected listener. + * + * @return the {@link OnItemSelectedListener} + */ + public final OnItemSelectedListener getOnItemSelectedListener() { + return mOnItemSelectedListener; + } + + /** + * Get the position within the adapter's data set for the view, where view + * is a an adapter item or a descendant of an adapter item. + * + * @param view an adapter item, or a descendant of an adapter item. This + * must be visible in this AdapterView at the time of the call. + * @return the position within the adapter's data set of the view, or + * {@link #INVALID_POSITION} if the view does not correspond to a + * list item (or it is not currently visible). + */ + public final int getPositionForView(final View view) { + View listItem = view; + try { + View v; + // CHECKSTYLE:OFF unmodified + while (!(v = (View) listItem.getParent()).equals(this)) { + // CHECKSTYLE:ON + listItem = v; + } + } catch (final ClassCastException e) { + // We made it up to the window without find this list view + return INVALID_POSITION; + } + + // Search the children for the list item + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i).equals(listItem)) return mFirstPosition + i; + } + + // Child not found! + return INVALID_POSITION; + } + + /** + * @return The data corresponding to the currently selected item, or null if + * there is nothing selected. + */ + public final Object getSelectedItem() { + final T adapter = getAdapter(); + final int selection = getSelectedItemPosition(); + if (adapter != null && adapter.getCount() > 0 && selection >= 0) + return adapter.getItem(selection); + else + return null; + } + + /** + * @return The id corresponding to the currently selected item, or + * {@link #INVALID_ROW_ID} if nothing is selected. + */ + // @ViewDebug.CapturedViewProperty + public final long getSelectedItemId() { + return mNextSelectedRowId; + } + + /** + * Return the position of the currently selected item within the adapter's + * data set. + * + * @return int Position (starting at 0), or {@link #INVALID_POSITION} if + * there is nothing selected. + */ + // @ViewDebug.CapturedViewProperty + public final int getSelectedItemPosition() { + return mNextSelectedPosition; + } + + /** + * @return The view corresponding to the currently selected item, or null if + * nothing is selected + */ + public abstract View getSelectedView(); + + /** + * Call the OnItemClickListener, if it is defined. + * + * @param view The view within the AdapterView that was clicked. + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + * @return True if there was an assigned OnItemClickListener that was + * called, false otherwise is returned. + */ + public final boolean performItemClick(final View view, final int position, final long id) { + if (mOnItemClickListener != null) { + playSoundEffect(SoundEffectConstants.CLICK); + mOnItemClickListener.onItemClick(this, view, position, id); + return true; + } + + return false; + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void removeAllViews() { + throw new UnsupportedOperationException("removeAllViews() is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @param child Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void removeView(final View child) { + throw new UnsupportedOperationException("removeView(View) is not supported in AdapterView"); + } + + /** + * This method is not supported and throws an UnsupportedOperationException + * when called. + * + * @param index Ignored. + * + * @throws UnsupportedOperationException Every time this method is invoked. + */ + @Override + public final void removeViewAt(final int index) { + throw new UnsupportedOperationException("removeViewAt(int) is not supported in AdapterView"); + } + + /** + * Sets the adapter that provides the data and the views to represent the + * data in this widget. + * + * @param adapter The adapter to use to create this view's content. + */ + public abstract void setAdapter(T adapter); + + /** + * Sets the view to show if the adapter is empty. + * + * @param emptyView the view to show when the adapter is empty + */ + public final void setEmptyView(final View emptyView) { + mEmptyView = emptyView; + + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.isEmpty(); + updateEmptyStatus(empty); + } + + @Override + public final void setFocusable(final boolean focusable) { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + + mDesiredFocusableState = focusable; + if (!focusable) { + mDesiredFocusableInTouchModeState = false; + } + + super.setFocusable(focusable && (!empty || isInFilterMode())); + } + + @Override + public final void setFocusableInTouchMode(final boolean focusable) { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + + mDesiredFocusableInTouchModeState = focusable; + if (focusable) { + mDesiredFocusableState = true; + } + + super.setFocusableInTouchMode(focusable && (!empty || isInFilterMode())); + } + + @Override + public final void setOnClickListener(final OnClickListener l) { + throw new RuntimeException("Don't call setOnClickListener for an AdapterView. " + + "You probably want setOnItemClickListener instead"); + } + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been clicked. + * + * @param listener The callback that will be invoked. + */ + public final void setOnItemClickListener(final OnItemClickListener listener) { + mOnItemClickListener = listener; + } + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been clicked and held. + * + * @param listener The callback that will run + */ + public final void setOnItemLongClickListener(final OnItemLongClickListener listener) { + if (!isLongClickable()) { + setLongClickable(true); + } + mOnItemLongClickListener = listener; + } + + /** + * Register a callback to be invoked when an item in this AdapterView has + * been selected. + * + * @param listener The callback that will run + */ + public final void setOnItemSelectedListener(final OnItemSelectedListener listener) { + mOnItemSelectedListener = listener; + } + + /** + * Sets the currently selected item. + * + * @param position Index (starting at 0) of the data item to be selected. + */ + public abstract void setSelection(int position); + + @Override + protected final boolean canAnimate() { + return super.canAnimate() && mItemCount > 0; + } + + /** + * Override to prevent thawing of any views created by the adapter. + * + * @param container the container of parcelables to use to restore the state + */ + @Override + protected final void dispatchRestoreInstanceState(final SparseArray container) { + dispatchThawSelfOnly(container); + } + + /** + * Override to prevent freezing of any views created by the adapter. + * + * @param container the container of parcelables to use to save the state. + */ + @Override + protected final void dispatchSaveInstanceState(final SparseArray container) { + dispatchFreezeSelfOnly(container); + } + + // CHECKSTYLE:OFF + @Override + protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { + mLayoutHeight = getHeight(); + } + + // CHECKSTYLE:ON + + /** + * Fire the onselected listener for the chosen item. + */ + private void fireOnSelected() { + if (mOnItemSelectedListener == null) return; + + final int selection = this.getSelectedItemPosition(); + if (selection >= 0) { + final View v = getSelectedView(); + mOnItemSelectedListener.onItemSelected(this, v, selection, getAdapter().getItemId(selection)); + } else { + mOnItemSelectedListener.onNothingSelected(this); + } + } + + /** + * Update the status of the list based on the empty parameter. If empty is + * true and we have an empty view, display it. In all the other cases, make + * sure that the listview is VISIBLE and that the empty view is GONE (if + * it's not null). + * + */ + // CHECKSTYLE:OFF unmodifed + @SuppressLint("WrongCall") + private void updateEmptyStatus(boolean empty) { + if (isInFilterMode()) { + empty = false; + } + + if (empty) { + if (mEmptyView != null) { + mEmptyView.setVisibility(View.VISIBLE); + setVisibility(View.GONE); + } else { + // If the caller just removed our empty view, make sure the list + // view is visible + setVisibility(View.VISIBLE); + } + + // We are now GONE, so pending layouts will not be dispatched. + // Force one here to make sure that the state of the list matches + // the state of the adapter. + if (mDataChanged) { + this.onLayout(false, getLeft(), getTop(), getRight(), getBottom()); + } + } else { + if (mEmptyView != null) { + mEmptyView.setVisibility(View.GONE); + } + setVisibility(View.VISIBLE); + } + } + + // CHECKSTYLE:ON + + // CHECKSTYLE:OFF unmodified + void checkFocus() { + final T adapter = getAdapter(); + final boolean empty = adapter == null || adapter.getCount() == 0; + final boolean focusable = !empty || isInFilterMode(); + // The order in which we set focusable in touch mode/focusable may + // matter + // for the client, see View.setFocusableInTouchMode() comments for more + // details + super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState); + super.setFocusable(focusable && mDesiredFocusableState); + if (mEmptyView != null) { + updateEmptyStatus(adapter == null || adapter.isEmpty()); + } + } + + // CHECKSTYLE:ON + + void checkSelectionChanged() { + if (mSelectedPosition != mOldSelectedPosition || mSelectedRowId != mOldSelectedRowId) { + selectionChanged(); + mOldSelectedPosition = mSelectedPosition; + mOldSelectedRowId = mSelectedRowId; + } + } + + // CHECKSTYLE:ON + + /** + * Searches the adapter for a position matching mSyncRowId. The search + * starts at mSyncPosition and then alternates between moving up and moving + * down until 1) we find the right position, or 2) we run out of time, or 3) + * we have looked at every position + * + * @return Position of the row that matches mSyncRowId, or + * {@link #INVALID_POSITION} if it can't be found + */ + final int findSyncPosition() { + final int count = mItemCount; + + if (count == 0) return INVALID_POSITION; + + final long idToMatch = mSyncRowId; + int seed = mSyncPosition; + + // If there isn't a selection don't hunt for it + if (idToMatch == INVALID_ROW_ID) return INVALID_POSITION; + + // Pin seed to reasonable values + seed = Math.max(0, seed); + seed = Math.min(count - 1, seed); + + final long endTime = SystemClock.uptimeMillis() + SYNC_MAX_DURATION_MILLIS; + + long rowId; + + // First position scanned so far + int first = seed; + + // Last position scanned so far + int last = seed; + + // True if we should move down on the next iteration + boolean next = false; + + // True when we have looked at the first item in the data + boolean hitFirst; + + // True when we have looked at the last item in the data + boolean hitLast; + + // Get the item ID locally (instead of getItemIdAtPosition), so we need + // the adapter + final T adapter = getAdapter(); + if (adapter == null) return INVALID_POSITION; + + while (SystemClock.uptimeMillis() <= endTime) { + rowId = adapter.getItemId(seed); + if (rowId == idToMatch) // Found it! + return seed; + + hitLast = last == count - 1; + hitFirst = first == 0; + + if (hitLast && hitFirst) { + // Looked at everything + break; + } + + if (hitFirst || next && !hitLast) { + // Either we hit the top, or we are trying to move down + last++; + seed = last; + // Try going up next time + next = false; + } else if (hitLast || !next && !hitFirst) { + // Either we hit the bottom, or we are trying to move up + first--; + seed = first; + // Try going down next time + next = true; + } + + } + + return INVALID_POSITION; + } + + // CHECKSTYLE:OFF + void handleDataChanged() { + final int count = mItemCount; + boolean found = false; + + if (count > 0) { + + int newPos; + + // Find the row we are supposed to sync to + if (mNeedSync) { + // Update this first, since setNextSelectedPositionInt inspects + // it + mNeedSync = false; + + // See if we can find a position in the new data with the same + // id as the old selection + newPos = findSyncPosition(); + if (newPos >= 0) { + // Verify that new selection is selectable + final int selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos == newPos) { + // Same row id is selected + setNextSelectedPositionInt(newPos); + found = true; + } + } + } + if (!found) { + // Try to use the same position if we can't find matching data + newPos = getSelectedItemPosition(); + + // Pin position to the available range + if (newPos >= count) { + newPos = count - 1; + } + if (newPos < 0) { + newPos = 0; + } + + // Make sure we select something selectable -- first look down + int selectablePos = lookForSelectablePosition(newPos, true); + if (selectablePos < 0) { + // Looking down didn't work -- try looking up + selectablePos = lookForSelectablePosition(newPos, false); + } + if (selectablePos >= 0) { + setNextSelectedPositionInt(selectablePos); + checkSelectionChanged(); + found = true; + } + } + } + if (!found) { + // Nothing is selected + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; + mNextSelectedPosition = INVALID_POSITION; + mNextSelectedRowId = INVALID_ROW_ID; + mNeedSync = false; + checkSelectionChanged(); + } + } + + /** + * Indicates whether this view is in filter mode. Filter mode can for + * instance be enabled by a user when typing on the keyboard. + * + * @return True if the view is in filter mode, false otherwise. + */ + final boolean isInFilterMode() { + return false; + } + + /** + * Find a position that can be selected (i.e., is not a separator). + * + * @param position The starting position to look at. + * @param lookDown Whether to look down for other positions. + * @return The next selectable position starting at position and then + * searching either up or down. Returns {@link #INVALID_POSITION} if + * nothing can be found. + */ + final int lookForSelectablePosition(final int position, final boolean lookDown) { + return position; + } + + /** + * Remember enough information to restore the screen state when the data has + * changed. + * + */ + final void rememberSyncState() { + if (getChildCount() > 0) { + mNeedSync = true; + mSyncHeight = mLayoutHeight; + if (mSelectedPosition >= 0) { + // Sync the selection state + final View v = getChildAt(mSelectedPosition - mFirstPosition); + mSyncRowId = mNextSelectedRowId; + mSyncPosition = mNextSelectedPosition; + if (v != null) { + mSpecificTop = v.getTop(); + } + mSyncMode = SYNC_SELECTED_POSITION; + } else { + // Sync the based on the offset of the first view + final View v = getChildAt(0); + final T adapter = getAdapter(); + if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) { + mSyncRowId = adapter.getItemId(mFirstPosition); + } else { + mSyncRowId = NO_ID; + } + mSyncPosition = mFirstPosition; + if (v != null) { + mSpecificTop = v.getTop(); + } + mSyncMode = SYNC_FIRST_POSITION; + } + } + } + + void selectionChanged() { + if (mOnItemSelectedListener != null) { + if (mInLayout || mBlockLayoutRequests) { + // If we are in a layout traversal, defer notification + // by posting. This ensures that the view tree is + // in a consistent state and is able to accomodate + // new layout or invalidate requests. + if (mSelectionNotifier == null) { + mSelectionNotifier = new SelectionNotifier(); + } + mSelectionNotifier.post(mSelectionNotifier); + } else { + fireOnSelected(); + } + } + } + + // CHECKSTYLE:ON + + /** + * Utility to keep mNextSelectedPosition and mNextSelectedRowId in sync. + * + * @param position Intended value for mSelectedPosition the next time we go + * through layout + */ + // CHECKSTYLE:OFF overridden in subclass + void setNextSelectedPositionInt(final int position) { + mNextSelectedPosition = position; + mNextSelectedRowId = getItemIdAtPosition(position); + // If we are trying to sync to the selection, update that too + if (mNeedSync && mSyncMode == SYNC_SELECTED_POSITION && position >= 0) { + mSyncPosition = position; + mSyncRowId = mNextSelectedRowId; + } + } + + // CHECKSTYLE:ON + + /** + * Utility to keep mSelectedPosition and mSelectedRowId in sync. + * + * @param position Our current position + */ + // CHECKSTYLE:OFF overridden in gallery + void setSelectedPositionInt(final int position) { + mSelectedPosition = position; + mSelectedRowId = getItemIdAtPosition(position); + } + + // CHECKSTYLE:ON + + /** + * Extra menu information provided to the + * {@link android.view.View.OnCreateContextMenuListener#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) } + * callback when a context menu is brought up for this AdapterView. + * + */ + public static class AdapterContextMenuInfo implements ContextMenu.ContextMenuInfo { + + /** + * The child view for which the context menu is being displayed. This + * will be one of the children of this AdapterView. + */ + public View targetView; + + /** + * The position in the adapter for which the context menu is being + * displayed. + */ + public int position; + + /** + * The row id of the item for which the context menu is being displayed. + */ + public long id; + + // CHECKSTYLE:OFF not modified + public AdapterContextMenuInfo(final View targetView, final int position, final long id) { + this.targetView = targetView; + this.position = position; + this.id = id; + } + // CHECKSTYLE:ON + } + + /** + * Interface definition for a callback to be invoked when an item in this + * AdapterView has been clicked. + */ + public interface OnItemClickListener { + + /** + * Callback method to be invoked when an item in this AdapterView has + * been clicked. + *

+ * Implementers can call getItemAtPosition(position) if they need to + * access the data associated with the selected item. + * + * @param parent The AdapterView where the click happened. + * @param view The view within the AdapterView that was clicked (this + * will be a view provided by the adapter) + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + */ + void onItemClick(AdapterView parent, View view, int position, long id); + } + + /** + * Interface definition for a callback to be invoked when an item in this + * view has been clicked and held. + */ + public interface OnItemLongClickListener { + /** + * Callback method to be invoked when an item in this view has been + * clicked and held. + * + * Implementers can call getItemAtPosition(position) if they need to + * access the data associated with the selected item. + * + * @param parent The AbsListView where the click happened + * @param view The view within the AbsListView that was clicked + * @param position The position of the view in the list + * @param id The row id of the item that was clicked + * + * @return true if the callback consumed the long click, false otherwise + */ + boolean onItemLongClick(AdapterView parent, View view, int position, long id); + } + + /** + * Interface definition for a callback to be invoked when an item in this + * view has been selected. + */ + public interface OnItemSelectedListener { + /** + * Callback method to be invoked when an item in this view has been + * selected. + * + * Impelmenters can call getItemAtPosition(position) if they need to + * access the data associated with the selected item. + * + * @param parent The AdapterView where the selection happened + * @param view The view within the AdapterView that was clicked + * @param position The position of the view in the adapter + * @param id The row id of the item that is selected + */ + void onItemSelected(AdapterView parent, View view, int position, long id); + + /** + * Callback method to be invoked when the selection disappears from this + * view. The selection can disappear for instance when touch is + * activated or when the adapter becomes empty. + * + * @param parent The AdapterView that now contains no selected item. + */ + void onNothingSelected(AdapterView parent); + } + + // CHECKSTYLE:OFF unmodified + private class SelectionNotifier extends Handler implements Runnable { + @Override + public void run() { + if (mDataChanged) { + // Data has changed between when this SelectionNotifier + // was posted and now. We need to wait until the AdapterView + // has been synched to the new data. + post(this); + } else { + fireOnSelected(); + } + } + } + + // CHECKSTYLE:OFF unmodified + class AdapterDataSetObserver extends DataSetObserver { + + private Parcelable mInstanceState = null; + + public void clearSavedState() { + mInstanceState = null; + } + + @Override + public void onChanged() { + mDataChanged = true; + mOldItemCount = mItemCount; + mItemCount = getAdapter().getCount(); + + // Detect the case where a cursor that was previously invalidated + // has + // been repopulated with new data. + if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 + && mItemCount > 0) { + onRestoreInstanceState(mInstanceState); + mInstanceState = null; + } else { + rememberSyncState(); + } + checkFocus(); + requestLayout(); + } + + @Override + public void onInvalidated() { + mDataChanged = true; + + if (AdapterView.this.getAdapter().hasStableIds()) { + // Remember the current state for the case where our hosting + // activity is being + // stopped and later restarted + mInstanceState = onSaveInstanceState(); + } + + // Data is invalid so we should reset our state + mOldItemCount = mItemCount; + mItemCount = 0; + mSelectedPosition = INVALID_POSITION; + mSelectedRowId = INVALID_ROW_ID; + mNextSelectedPosition = INVALID_POSITION; + mNextSelectedRowId = INVALID_ROW_ID; + mNeedSync = false; + checkSelectionChanged(); + + checkFocus(); + requestLayout(); + } + } + // CHECKSTYLE:ON +} diff --git a/twidere/src/main/java/com/scvngr/levelup/views/gallery/Gallery.java b/twidere/src/main/java/com/scvngr/levelup/views/gallery/Gallery.java new file mode 100644 index 000000000..e9d1a9e58 --- /dev/null +++ b/twidere/src/main/java/com/scvngr/levelup/views/gallery/Gallery.java @@ -0,0 +1,1520 @@ +package com.scvngr.levelup.views.gallery; + +/* + * Based on the android GalleryView source. Modified to allow the view to snap to the + * left rather than the center. The biggest changes in this class are marked with comments + * with "Note:". + * + * Copyright (C) 2007 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. + */ + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.Transformation; +import android.widget.Scroller; + +/** + * A view that shows items in a center-locked, horizontally scrolling list. + *

+ * The default values for the Gallery assume you will be using + * {@link android.R.styleable#Theme_GalleryItemBackground} as the background for + * each View given to the Gallery from the Adapter. If you are not doing this, + * you may need to adjust some Gallery properties, such as the spacing. + *

+ * Views given to the Gallery should use {@link Gallery.LayoutParams} as their + * layout parameters type. + * + *

+ * See the Gallery tutorial. + *

+ * + * CHECKSTYLE:OFF Android comments + * + * @attr ref android.R.styleable#Gallery_animationDuration + * @attr ref android.R.styleable#Gallery_spacing + * @attr ref android.R.styleable#Gallery_gravity CHECKSTYLE:ON + */ +public final class Gallery extends AbsSpinner implements GestureDetector.OnGestureListener { + + /** + * Duration in milliseconds from the start of a scroll during which we're + * unsure whether the user is scrolling or flinging. + */ + private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250; + + /** + * Horizontal spacing between items. + */ + private int mSpacing = 0; + + /** + * How long the transition animation should run when a child view changes + * position, measured in milliseconds. + */ + // CHECKSTYLE:OFF not a magic number + private int mAnimationDuration = 400; + // CHECKSTYLE:ON + + /** + * The alpha of items that are not selected. + */ + private float mUnselectedAlpha; + + /** + * Left most edge of a child seen so far during layout. + */ + private int mLeftMost; + + /** + * Right most edge of a child seen so far during layout. + */ + private int mRightMost; + + private int mGravity; + + /** + * Helper for detecting touch gestures. + */ + private final GestureDetector mGestureDetector; + + /** + * The position of the item that received the user's down touch. + */ + private int mDownTouchPosition; + + /** + * The view of the item that received the user's down touch. + */ + private View mDownTouchView; + + /** + * Executes the delta scrolls from a fling or scroll movement. + */ + private final FlingRunnable mFlingRunnable = new FlingRunnable(); + + /** + * Sets mSuppressSelectionChanged = false. This is used to set it to false + * in the future. It will also trigger a selection changed. + */ + private final Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() { + @Override + public void run() { + mSuppressSelectionChanged = false; + selectionChanged(); + } + }; + + /** + * When fling runnable runs, it resets this to false. Any method along the + * path until the end of its run() can set this to true to abort any + * remaining fling. For example, if we've reached either the leftmost or + * rightmost item, we will set this to true. + */ + private boolean mShouldStopFling; + + /** + * The currently selected item's child. + */ + private View mSelectedChild; + + /** + * Whether to continuously callback on the item selected listener during a + * fling. + */ + private boolean mShouldCallbackDuringFling = true; + + /** + * Whether to callback when an item that is not selected is clicked. + */ + private boolean mShouldCallbackOnUnselectedItemClick = true; + + /** + * If true, do not callback to item selected listener. + */ + private boolean mSuppressSelectionChanged; + + /** + * If true, we have received the "invoke" (center or enter buttons) key + * down. This is checked before we action on the "invoke" key up, and is + * subsequently cleared. + */ + private boolean mReceivedInvokeKeyDown; + + private AdapterContextMenuInfo mContextMenuInfo; + + /** + * If true, this onScroll is the first for this user's drag (remember, a + * drag sends many onScrolls). + */ + private boolean mIsFirstScroll; + + /** + * If true, mFirstPosition is the position of the rightmost child, and the + * children are ordered right to left. + */ + private boolean mIsRtl = true; + + private boolean mScrollToChildAfterItemClickEnabled; + private boolean mRightSpacingEnabled; + + /** + * Constructor. + * + * @param context the context to inflate into + */ + public Gallery(final Context context) { + this(context, null); + } + + /** + * Constructor for XML inflation. + * + * @param context the context to inflate with + * @param attrs the xml attrs + */ + public Gallery(final Context context, final AttributeSet attrs) { + this(context, attrs, android.R.attr.galleryStyle); + } + + /** + * Constructor for XML inflation. + * + * @param context the context to inflate with + * @param attrs the XML attrs + * @param defStyle the default style resource id + */ + public Gallery(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + + mGestureDetector = new GestureDetector(context, this); + mGestureDetector.setIsLongpressEnabled(true); + setScrollAfterItemClickEnabled(true); + setScrollRightSpacingEnabled(true); + } + + @Override + public boolean dispatchKeyEvent(final KeyEvent event) { + // Gallery steals all key events + return event.dispatch(this, null, null); + } + + @Override + public void dispatchSetSelected(final boolean selected) { + /* + * We don't want to pass the selected state given from its parent to its + * children since this widget itself has a selected state to give to its + * children. + */ + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + @Override + public boolean onDown(final MotionEvent e) { + + // Kill any existing fling/scroll + mFlingRunnable.stop(false); + + // Get the item's view that was touched + mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY()); + + if (mDownTouchPosition >= 0) { + mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition); + mDownTouchView.setPressed(true); + } + + // Reset the multiple-scroll tracking state + mIsFirstScroll = true; + + // Must return true to get matching events for this down event. + return true; + } + + @Override + public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { + + if (!mShouldCallbackDuringFling) { + // We want to suppress selection changes + + // Remove any future code to set mSuppressSelectionChanged = false + removeCallbacks(mDisableSuppressSelectionChangedRunnable); + + // This will get reset once we scroll into slots + if (!mSuppressSelectionChanged) { + mSuppressSelectionChanged = true; + } + } + + // Fling the Gallery! + mFlingRunnable.startUsingVelocity((int) -velocityX); + + return true; + } + + /** + * Handles left, right, and clicking. + * + * @see android.view.View#onKeyDown + */ + // CHECKSTYLE:OFF unmodified + @Override + public boolean onKeyDown(final int keyCode, final KeyEvent event) { + switch (keyCode) { + + case KeyEvent.KEYCODE_DPAD_LEFT: + if (movePrevious()) { + playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT); + } + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (moveNext()) { + playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT); + } + return true; + + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + mReceivedInvokeKeyDown = true; + // fallthrough to default handling + } + + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(final int keyCode, final KeyEvent event) { + switch (keyCode) { + + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: { + + if (mReceivedInvokeKeyDown) { + if (mItemCount > 0) { + + dispatchPress(mSelectedChild); + postDelayed(new Runnable() { + @Override + public void run() { + dispatchUnpress(); + } + }, ViewConfiguration.getPressedStateDuration()); + + final int selectedIndex = mSelectedPosition - mFirstPosition; + performItemClick(getChildAt(selectedIndex), mSelectedPosition, + mAdapter.getItemId(mSelectedPosition)); + } + } + + // Clear the flag + mReceivedInvokeKeyDown = false; + + return true; + } + } + + return super.onKeyUp(keyCode, event); + } + + @Override + public void onLongPress(final MotionEvent e) { + + if (mDownTouchPosition < 0) return; + + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + final long id = getItemIdAtPosition(mDownTouchPosition); + dispatchLongPress(mDownTouchView, mDownTouchPosition, id); + } + + @Override + public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) { + + /* + * Now's a good time to tell our parent to stop intercepting our events! + * The user has moved more than the slop amount, since GestureDetector + * ensures this before calling this method. Also, if a parent is more + * interested in this touch's events than we are, it would have + * intercepted them by now (for example, we can assume when a Gallery is + * in the ListView, a vertical scroll would not end up in this method + * since a ListView would have intercepted it by now). + */ + getParent().requestDisallowInterceptTouchEvent(true); + + /* + * As the user scrolls, we want to callback selection changes so + * related- info on the screen is up-to-date with the Gallery's + * selection + */ + if (!mShouldCallbackDuringFling) { + if (mIsFirstScroll) { + /* + * We're not notifying the client of selection changes during + * the fling, and this scroll could possibly be a fling. Don't + * do selection changes until we're sure it is not a fling. + */ + if (!mSuppressSelectionChanged) { + mSuppressSelectionChanged = true; + } + + postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT); + } + } else { + if (mSuppressSelectionChanged) { + mSuppressSelectionChanged = false; + } + } + + // Track the motion + trackMotionScroll(-1 * (int) distanceX); + + mIsFirstScroll = false; + return true; + } + + @Override + public void onShowPress(final MotionEvent e) { + } + + @Override + public boolean onSingleTapUp(final MotionEvent e) { + + if (mDownTouchPosition >= 0) { + if (mScrollToChildAfterItemClickEnabled) { + // An item tap should make it selected, so scroll to this child. + scrollToChild(mDownTouchPosition - mFirstPosition); + } + + // Also pass the click so the client knows, if it wants to. + if (mShouldCallbackOnUnselectedItemClick || mDownTouchPosition == mSelectedPosition) { + performItemClick(mDownTouchView, mDownTouchPosition, mAdapter.getItemId(mDownTouchPosition)); + } + + return true; + } + + return false; + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + + if (!isEnabled()) return false; + + // Give everything to the gesture detector + final boolean retValue = mGestureDetector.onTouchEvent(event); + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_UP) { + // Helper method for lifted finger + onUp(); + } else if (action == MotionEvent.ACTION_CANCEL) { + onCancel(); + } + + return retValue; + } + + /** + * Sets how long the transition animation should run when a child view + * changes position. Only relevant if animation is turned on. + * + * @param animationDurationMillis The duration of the transition, in + * milliseconds. + * + * @attr ref android.R.styleable#Gallery_animationDuration + */ + public void setAnimationDuration(final int animationDurationMillis) { + mAnimationDuration = animationDurationMillis; + } + + /** + * Whether or not to callback on any {@link #getOnItemSelectedListener()} + * while the items are being flinged. If false, only the final selected item + * will cause the callback. If true, all items between the first and the + * final will cause callbacks. + * + * @param shouldCallback Whether or not to callback on the listener while + * the items are being flinged. + */ + public void setCallbackDuringFling(final boolean shouldCallback) { + mShouldCallbackDuringFling = shouldCallback; + } + + /** + * Whether or not to callback when an item that is not selected is clicked. + * If false, the item will become selected (and re-centered). If true, the + * {@link #getOnItemClickListener()} will get the callback. + * + * @param shouldCallback Whether or not to callback on the listener when a + * item that is not selected is clicked. + * @hide + */ + public void setCallbackOnUnselectedItemClick(final boolean shouldCallback) { + mShouldCallbackOnUnselectedItemClick = shouldCallback; + } + + /** + * Describes how the child views are aligned. + * + * @param gravity the gravity to set + * + * @attr ref android.R.styleable#Gallery_gravity + */ + public void setGravity(final int gravity) { + if (mGravity != gravity) { + mGravity = gravity; + requestLayout(); + } + } + + public void setScrollAfterItemClickEnabled(final boolean enabled) { + mScrollToChildAfterItemClickEnabled = enabled; + } + + public void setScrollRightSpacingEnabled(final boolean enabled) { + mRightSpacingEnabled = enabled; + } + + /** + * Sets the spacing between items in a Gallery. + * + * @param spacing The spacing in pixels between items in the Gallery + * + * @attr ref android.R.styleable#Gallery_spacing + */ + public void setSpacing(final int spacing) { + mSpacing = spacing; + } + + /** + * Sets the alpha of items that are not selected in the Gallery. + * + * @param unselectedAlpha the alpha for the items that are not selected. + * + * @attr ref android.R.styleable#Gallery_unselectedAlpha + */ + public void setUnselectedAlpha(final float unselectedAlpha) { + mUnselectedAlpha = unselectedAlpha; + } + + @Override + public boolean showContextMenu() { + + if (isPressed() && mSelectedPosition >= 0) { + final int index = mSelectedPosition - mFirstPosition; + final View v = getChildAt(index); + return dispatchLongPress(v, mSelectedPosition, mSelectedRowId); + } + + return false; + } + + @Override + public boolean showContextMenuForChild(final View originalView) { + + final int longPressPosition = getPositionForView(originalView); + if (longPressPosition < 0) return false; + + final long longPressId = mAdapter.getItemId(longPressPosition); + return dispatchLongPress(originalView, longPressPosition, longPressId); + } + + @Override + protected boolean checkLayoutParams(final ViewGroup.LayoutParams p) { + return p instanceof LayoutParams; + } + + @Override + protected int computeHorizontalScrollExtent() { + // Only 1 item is considered to be selected + return 1; + } + + @Override + protected int computeHorizontalScrollOffset() { + // Current scroll position is the same as the selected position + return mSelectedPosition; + } + + @Override + protected int computeHorizontalScrollRange() { + // Scroll range is the same as the item count + return mItemCount; + } + + @Override + protected void dispatchSetPressed(final boolean pressed) { + + // Show the pressed state on the selected child + if (mSelectedChild != null) { + mSelectedChild.setPressed(pressed); + } + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + /* + * Gallery expects Gallery.LayoutParams. + */ + return new Gallery.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + // CHECKSTYLE:ON + + @Override + protected int getChildDrawingOrder(final int childCount, final int i) { + final int selectedIndex = mSelectedPosition - mFirstPosition; + + // Just to be safe + if (selectedIndex < 0) return i; + + if (i == childCount - 1) + // Draw the selected child last + return selectedIndex; + else if (i >= selectedIndex) // Move the children after the selected + // child earlier one + return i + 1; + else + // Keep the children before the selected child the same + return i; + } + + // CHECKSTYLE:OFF unchanged method + @Override + protected boolean getChildStaticTransformation(final View child, final Transformation t) { + + t.clear(); + t.setAlpha(child == mSelectedChild ? 1.0f : mUnselectedAlpha); + + return true; + } + + @Override + protected ContextMenuInfo getContextMenuInfo() { + return mContextMenuInfo; + } + + @Override + protected void onFocusChanged(final boolean gainFocus, final int direction, final Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + + /* + * The Gallery shows focus by focusing the selected item. So, give focus + * to our selected item instead. We steal keys from our selected item + * elsewhere. + */ + if (gainFocus && mSelectedChild != null) { + mSelectedChild.requestFocus(direction); + mSelectedChild.setSelected(true); + } + + } + + @Override + protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) { + super.onLayout(changed, l, t, r, b); + + /* + * Remember that we are in layout to prevent more layout request from + * being generated. + */ + mInLayout = true; + layout(0, false); + mInLayout = false; + } + + /** + * Figure out vertical placement based on mGravity. + * + * @param child Child to place + * @param duringLayout if this is being called during layout + * @return Where the top of the child should be + */ + private int calculateTop(final View child, final boolean duringLayout) { + // CHECKSTYLE:OFF unmodified + final int myHeight = duringLayout ? getMeasuredHeight() : getHeight(); + final int childHeight = duringLayout ? child.getMeasuredHeight() : child.getHeight(); + + int childTop = 0; + + switch (mGravity) { + case Gravity.TOP: + childTop = mSpinnerPadding.top; + break; + case Gravity.CENTER_VERTICAL: + final int availableSpace = myHeight - mSpinnerPadding.bottom - mSpinnerPadding.top - childHeight; + childTop = mSpinnerPadding.top + availableSpace / 2; + break; + case Gravity.BOTTOM: + childTop = myHeight - mSpinnerPadding.bottom - childHeight; + break; + } + return childTop; + // CHECKSTYLE:ON + } + + // CHECKSTYLE:ON + + /** + * Detaches children that are off the screen (i.e.: Gallery bounds). + * + * @param toLeft Whether to detach children to the left of the Gallery, or + * to the right. + */ + private void detachOffScreenChildren(final boolean toLeft) { + final int numChildren = getChildCount(); + final int firstPosition = mFirstPosition; + int start = 0; + int count = 0; + + if (toLeft) { + final int galleryLeft = getPaddingLeft(); + for (int i = 0; i < numChildren; i++) { + // CHECKSTYLE:OFF unmodified + final int n = mIsRtl ? numChildren - 1 - i : i; + // CHECKSTYLE:ON + final View child = getChildAt(n); + if (child.getRight() >= galleryLeft) { + break; + } else { + start = n; + count++; + mRecycler.put(firstPosition + n, child); + } + } + if (!mIsRtl) { + start = 0; + } + } else { + final int galleryRight = getWidth() - getPaddingRight(); + for (int i = numChildren - 1; i >= 0; i--) { + // CHECKSTYLE:OFF unmodified + final int n = mIsRtl ? numChildren - 1 - i : i; + // CHECKSTYLE:ON + final View child = getChildAt(n); + if (child.getLeft() <= galleryRight) { + break; + } else { + start = n; + count++; + mRecycler.put(firstPosition + n, child); + } + } + if (mIsRtl) { + start = 0; + } + } + + detachViewsFromParent(start, count); + + if (toLeft != mIsRtl) { + mFirstPosition += count; + } + } + + // CHECKSTYLE:OFF unmodified + private boolean dispatchLongPress(final View view, final int position, final long id) { + boolean handled = false; + + if (mOnItemLongClickListener != null) { + handled = mOnItemLongClickListener.onItemLongClick(this, mDownTouchView, mDownTouchPosition, id); + } + + if (!handled) { + mContextMenuInfo = new AdapterContextMenuInfo(view, position, id); + handled = super.showContextMenuForChild(this); + } + + if (handled) { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + + return handled; + } + + // CHECKSTYLE:ON + + // CHECKSTYLE:OFF unmodified + private void dispatchPress(final View child) { + + if (child != null) { + child.setPressed(true); + } + + setPressed(true); + } + + private void dispatchUnpress() { + + for (int i = getChildCount() - 1; i >= 0; i--) { + getChildAt(i).setPressed(false); + } + + setPressed(false); + } + + // CHECKSTYLE:OFF unmodified + private void fillToGalleryLeft() { + if (mIsRtl) { + fillToGalleryLeftRtl(); + } else { + fillToGalleryLeftLtr(); + } + } + + private void fillToGalleryLeftLtr() { + final int itemSpacing = mSpacing; + final int galleryLeft = getPaddingLeft(); + + // Set state for initial iteration + View prevIterationView = getChildAt(0); + int curPosition; + int curRightEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition - 1; + curRightEdge = prevIterationView.getLeft() - itemSpacing; + } else { + // No children available! + curPosition = 0; + curRightEdge = getRight() - getLeft() - getPaddingRight(); + mShouldStopFling = true; + } + + while (curRightEdge > galleryLeft && curPosition >= 0) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, curRightEdge, false); + + // Remember some state + mFirstPosition = curPosition; + + // Set state for next iteration + curRightEdge = prevIterationView.getLeft() - itemSpacing; + curPosition--; + } + } + + private void fillToGalleryLeftRtl() { + final int itemSpacing = mSpacing; + final int galleryLeft = getPaddingLeft(); + final int numChildren = getChildCount(); + + // Set state for initial iteration + View prevIterationView = getChildAt(numChildren - 1); + int curPosition; + int curRightEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition + numChildren; + curRightEdge = prevIterationView.getLeft() - itemSpacing; + } else { + // No children available! + mFirstPosition = curPosition = mItemCount - 1; + curRightEdge = getRight() - getLeft() - getPaddingRight(); + mShouldStopFling = true; + } + + while (curRightEdge > galleryLeft && curPosition < mItemCount) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, curRightEdge, false); + + // Set state for next iteration + curRightEdge = prevIterationView.getLeft() - itemSpacing; + curPosition++; + } + } + + // CHECKSTYLE:ON + + // Unused methods from GestureDetector.OnGestureListener below + + private void fillToGalleryRight() { + if (mIsRtl) { + fillToGalleryRightRtl(); + } else { + fillToGalleryRightLtr(); + } + } + + // Unused methods from GestureDetector.OnGestureListener above + + private void fillToGalleryRightLtr() { + final int itemSpacing = mSpacing; + final int galleryRight = getRight() - getLeft() - getPaddingRight(); + final int numChildren = getChildCount(); + final int numItems = mItemCount; + + // Set state for initial iteration + View prevIterationView = getChildAt(numChildren - 1); + int curPosition; + int curLeftEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition + numChildren; + curLeftEdge = prevIterationView.getRight() + itemSpacing; + } else { + mFirstPosition = curPosition = mItemCount - 1; + curLeftEdge = getPaddingLeft(); + mShouldStopFling = true; + } + + while (curLeftEdge < galleryRight && curPosition < numItems) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, curLeftEdge, true); + + // Set state for next iteration + curLeftEdge = prevIterationView.getRight() + itemSpacing; + curPosition++; + } + } + + private void fillToGalleryRightRtl() { + final int itemSpacing = mSpacing; + final int GalleryRight = getRight() - getLeft() - getPaddingRight(); + + // Set state for initial iteration + View prevIterationView = getChildAt(0); + int curPosition; + int curLeftEdge; + + if (prevIterationView != null) { + curPosition = mFirstPosition - 1; + curLeftEdge = prevIterationView.getRight() + itemSpacing; + } else { + curPosition = 0; + curLeftEdge = getPaddingLeft(); + mShouldStopFling = true; + } + + while (curLeftEdge < GalleryRight && curPosition >= 0) { + prevIterationView = makeAndAddView(curPosition, curPosition - mSelectedPosition, curLeftEdge, true); + + // Remember some state + mFirstPosition = curPosition; + + // Set state for next iteration + curLeftEdge = prevIterationView.getRight() + itemSpacing; + curPosition--; + } + } + + /** + * Modified from getCenterOfGallery. This now gets the left of the view + * (plus padding). + * + * @return The left of this Gallery. + */ + private int getGalleryLockPoint() { + // Lock left of the view + return getPaddingLeft(); + } + + /** + * Obtain a view, either by pulling an existing view from the recycler or by + * getting a new one from the adapter. If we are animating, make sure there + * is enough information in the view's layout parameters to animate from the + * old to new positions. + * + * @param position Position in the Gallery for the view to obtain + * @param offset Offset from the selected position + * @param x X-coordinate indicating where this view should be placed. This + * will either be the left or right edge of the view, depending + * on the fromLeft parameter + * @param fromLeft Are we positioning views based on the left edge? (i.e., + * building from left to right)? + * @return A view that has been added to the Gallery + */ + private View makeAndAddView(final int position, final int offset, final int x, final boolean fromLeft) { + + View child; + if (!mDataChanged) { + child = mRecycler.get(position); + if (child != null) { + // Can reuse an existing view + final int childLeft = child.getLeft(); + + // Remember left and right edges of where views have been placed + mRightMost = Math.max(mRightMost, childLeft + child.getMeasuredWidth()); + mLeftMost = Math.min(mLeftMost, childLeft); + + // Position the view + setUpChild(child, offset, x, fromLeft); + + return child; + } + } + + // Nothing found in the recycler -- ask the adapter for a view + child = mAdapter.getView(position, null, this); + + // Position the view + setUpChild(child, offset, x, fromLeft); + + return child; + } + + // CHECKSTYLE:ON + + /** + * Offset the horizontal location of all children of this view by the + * specified number of pixels. + * + * @param offset the number of pixels to offset + */ + private void offsetChildrenLeftAndRight(final int offset) { + for (int i = getChildCount() - 1; i >= 0; i--) { + getChildAt(i).offsetLeftAndRight(offset); + } + } + + // CHECKSTYLE:OFF unmodified + private void onFinishedMovement() { + if (mSuppressSelectionChanged) { + mSuppressSelectionChanged = false; + + // We haven't been callbacking during the fling, so do it now + super.selectionChanged(); + } + invalidate(); + } + + /** + * Scrolls the items so that the selected item is in its 'slot' (its center + * is the Gallery's center). + * + * Note: modifed to snap the left of a view rather than the center + */ + private void scrollIntoSlots() { + if (getChildCount() == 0 || mSelectedChild == null) return; + + final int selectedCenter = getLeftOfView(mSelectedChild); + final int targetCenter = getGalleryLockPoint(); + + final int scrollAmount = targetCenter - selectedCenter; + if (scrollAmount != 0) { + mFlingRunnable.startUsingDistance(scrollAmount); + } else { + onFinishedMovement(); + } + } + + /** + * Scroll to the child at the position passed. + * + * @param childPosition the child's position + * @return if scrolling to the child is starting, false otherwise + */ + private boolean scrollToChild(final int childPosition) { + final View child = getChildAt(childPosition); + + if (child != null) { + final int distance = getGalleryLockPoint() - getLeftOfView(child); + mFlingRunnable.startUsingDistance(distance); + return true; + } + + return false; + } + + /** + * Looks for the child that is closest to the lock point of the gallery and + * sets it as the selected child. + * + * Note: modified from setSelectionToCenterChild to now select the the + * selected view to the child closest to the gallery's "lock point" (it's + * left). + */ + private void setSelectionToChildClosestToLockPoint() { + if (mSelectedChild == null) return; + + final int lockPoint = getGalleryLockPoint(); + + // TODO better search + int closestEdgeDistance = Integer.MAX_VALUE; + int newSelectedChildIndex = 0; + for (int i = getChildCount() - 1; i >= 0; i--) { + final View child = getChildAt(i); + + /* + * Note: Since we are locking on the left edge of the scroller, we + * want to put more emphasis on the closest edge calculations + * because the left edge being on the left of the screen puts + * unnecessary weight on the currently selected item. + */ + if (child.getLeft() == lockPoint && child.getRight() >= lockPoint) { + // This child is in the lock point + newSelectedChildIndex = i; + break; + } + + final int childClosestEdgeDistance = Math.min(Math.abs(child.getLeft() - lockPoint), + Math.abs(child.getRight() - lockPoint)); + if (childClosestEdgeDistance < closestEdgeDistance) { + closestEdgeDistance = childClosestEdgeDistance; + newSelectedChildIndex = i; + } + } + + final int newPos = mFirstPosition + newSelectedChildIndex; + + if (newPos != mSelectedPosition) { + setSelectedPositionInt(newPos); + setNextSelectedPositionInt(newPos); + checkSelectionChanged(); + } + } + + // CHECKSTYLE:ON + + /** + * Helper for makeAndAddView to set the position of a view and fill out its + * layout parameters. + * + * @param child The view to position + * @param offset Offset from the selected position + * @param x X-coordinate indicating where this view should be placed. This + * will either be the left or right edge of the view, depending + * on the fromLeft parameter + * @param fromLeft Are we positioning views based on the left edge? (i.e., + * building from left to right)? + */ + private void setUpChild(final View child, final int offset, final int x, final boolean fromLeft) { + + /* + * Respect layout params that are already in the view. Otherwise make + * some up... + */ + Gallery.LayoutParams lp = child.getLayoutParams(); + if (lp == null) { + lp = generateDefaultLayoutParams(); + } + + // CHECKSTYLE:OFF unmodified + addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp); + // CHECKSTYLE:ON + + child.setSelected(offset == 0); + + // Get measure specs + final int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, mSpinnerPadding.top + + mSpinnerPadding.bottom, lp.height); + final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec, mSpinnerPadding.left + + mSpinnerPadding.right, lp.width); + + // Measure child + child.measure(childWidthSpec, childHeightSpec); + + int childLeft; + int childRight; + + // Position vertically based on gravity setting + final int childTop = calculateTop(child, true); + final int childBottom = childTop + child.getMeasuredHeight(); + + final int width = child.getMeasuredWidth(); + if (fromLeft) { + childLeft = x; + childRight = childLeft + width; + } else { + childLeft = x - width; + childRight = x; + } + + child.layout(childLeft, childTop, childRight, childBottom); + } + + // CHECKSTYLE:OFF unmodified + private void updateSelectedItemMetadata() { + + final View oldSelectedChild = mSelectedChild; + + final View child = mSelectedChild = getChildAt(mSelectedPosition - mFirstPosition); + if (child == null) return; + + child.setSelected(true); + child.setFocusable(true); + + if (hasFocus()) { + child.requestFocus(); + } + + // We unfocus the old child down here so the above hasFocus check + // returns true + if (oldSelectedChild != null && oldSelectedChild != child) { + + // Make sure its drawable state doesn't contain 'selected' + oldSelectedChild.setSelected(false); + + /* + * Make sure it is not focusable anymore, since otherwise arrow keys + * can make this one be focused + */ + oldSelectedChild.setFocusable(false); + } + + } + + /** + * Gets the limit of the amount to scroll the view. Note: Modified from the + * android source to snap to the left of the view rather than the center. + * + * @param motionToLeft if the motion is to the left of the view + * @param deltaX the change in x + * @return the clamped value to scroll + */ + // TODO + int getLimitedMotionScrollAmount(final boolean motionToLeft, final int deltaX) { + // CHECKSTYLE:OFF unmodifed code + final int extremeItemPosition = motionToLeft != mIsRtl ? mItemCount - 1 : 0; + // CHECKSTYLE:ON + final View extremeChild = getChildAt(extremeItemPosition - mFirstPosition); + + if (extremeChild == null) return deltaX; + + final int extremeChildLeft = getLeftOfView(extremeChild); + final int galleryLockPoint = getGalleryLockPoint(); + + if (motionToLeft) { + if (!mRightSpacingEnabled && extremeChild.getRight() <= getRight()) return 0; + // The extreme child is past his boundary point! + if (extremeChildLeft <= galleryLockPoint) return 0; + } else { + // The extreme child is past his boundary point! + if (extremeChildLeft >= galleryLockPoint) return 0; + } + + final int centerDifference = galleryLockPoint - extremeChildLeft; + + // CHECKSTYLE:OFF unmodifed return + return motionToLeft ? Math.max(centerDifference, deltaX) : Math.min(centerDifference, deltaX); + // CHECKSTYLE:ON + } + + /** + * Creates and positions all views for this Gallery. + *

+ * We layout rarely, most of the time {@link #trackMotionScroll(int)} takes + * care of repositioning, adding, and removing children. + * + * Note: modified to offset to the gallery lock point rather than based on + * the # of children. + * + * @param delta Change in the selected position. +1 means the selection is + * moving to the right, so views are scrolling to the left. -1 + * means the selection is moving to the left. + * @param animate if true, animate the view + */ + @Override + void layout(final int delta, final boolean animate) { + + mIsRtl = false; + + if (mDataChanged) { + handleDataChanged(); + } + + // Handle an empty Gallery by removing all views. + if (mItemCount == 0) { + resetList(); + return; + } + + // Update to the new selected position. + if (mNextSelectedPosition >= 0) { + setSelectedPositionInt(mNextSelectedPosition); + } + + // All views go in recycler while we are in layout + recycleAllViews(); + + // Clear out old views + detachAllViewsFromParent(); + + /* + * These will be used to give initial positions to views entering the + * Gallery as we scroll + */ + mRightMost = 0; + mLeftMost = 0; + + // Make selected view and move it to the lock point + + /* + * mFirstPosition will be decreased as we add views to the left later + * on. The 0 for x will be offset in a couple lines down. + */ + mFirstPosition = mSelectedPosition; + final View sel = makeAndAddView(mSelectedPosition, 0, 0, true); + + // Put the selected child at the lockPoint + sel.offsetLeftAndRight(getGalleryLockPoint()); + + fillToGalleryRight(); + fillToGalleryLeft(); + + // Flush any cached views that did not get reused above + mRecycler.clear(); + + invalidate(); + checkSelectionChanged(); + + mDataChanged = false; + mNeedSync = false; + setNextSelectedPositionInt(mSelectedPosition); + + updateSelectedItemMetadata(); + } + + // CHECKSTYLE:ON + + boolean moveNext() { + if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) { + scrollToChild(mSelectedPosition - mFirstPosition + 1); + return true; + } else + return false; + } + + boolean movePrevious() { + if (mItemCount > 0 && mSelectedPosition > 0) { + /** + * used to be scrollToChild(mSelectedPosition - mFirstPosition - 1); + * Found that this did not work with trackball/keyboard navigation. + */ + setSelection(mSelectedPosition - 1); + return true; + } else + return false; + } + + /** + * Called when a touch event's action is MotionEvent.ACTION_CANCEL. + */ + void onCancel() { + onUp(); + } + + /** + * Called when a touch event's action is MotionEvent.ACTION_UP. + */ + void onUp() { + + if (mFlingRunnable.mScroller.isFinished()) { + scrollIntoSlots(); + } + + dispatchUnpress(); + } + + @Override + void selectionChanged() { + if (!mSuppressSelectionChanged) { + super.selectionChanged(); + } + } + + @Override + void setSelectedPositionInt(final int position) { + super.setSelectedPositionInt(position); + + // Updates any metadata we keep about the selected item. + updateSelectedItemMetadata(); + } + + /** + * Tracks a motion scroll. In reality, this is used to do just about any + * movement to items (touch scroll, arrow-key scroll, set an item as + * selected). + * + * @param deltaX Change in X from the previous event. + */ + void trackMotionScroll(final int deltaX) { + + if (getChildCount() == 0) return; + + final boolean toLeft = deltaX < 0; + + final int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX); + if (limitedDeltaX != deltaX) { + // The above call returned a limited amount, so stop any + // scrolls/flings + mFlingRunnable.endFling(false); + onFinishedMovement(); + } + + offsetChildrenLeftAndRight(limitedDeltaX); + + detachOffScreenChildren(toLeft); + + if (toLeft) { + // If moved left, there will be empty space on the right + fillToGalleryRight(); + } else { + // Similarly, empty space on the left + fillToGalleryLeft(); + } + + // Clear unused views + mRecycler.clear(); + + setSelectionToChildClosestToLockPoint(); + + onScrollChanged(0, 0, 0, 0); // Dummy values, View's implementation does + // not use these. + + invalidate(); + } + + /** + * Modified from getCenterOfView to get the left of a view instead. + * + * @param view the view to get the left of + * @return The center of the given view. + */ + private static int getLeftOfView(final View view) { + return view.getLeft(); + } + + /** + * Responsible for fling behavior. Use {@link #startUsingVelocity(int)} to + * initiate a fling. Each frame of the fling is handled in {@link #run()}. A + * FlingRunnable will keep re-posting itself until the fling is done. + */ + private class FlingRunnable implements Runnable { + /** + * Tracks the decay of a fling scroll. + */ + private final Scroller mScroller; + + /** + * X value reported by mScroller on the previous fling. + */ + private int mLastFlingX; + + /** + * Constructor. + */ + public FlingRunnable() { + mScroller = new Scroller(getContext()); + } + + @Override + public void run() { + + if (mItemCount == 0) { + endFling(true); + return; + } + + mShouldStopFling = false; + + final Scroller scroller = mScroller; + final boolean more = scroller.computeScrollOffset(); + final int x = scroller.getCurrX(); + + // Flip sign to convert finger direction to list items direction + // (e.g. finger moving down means list is moving towards the top) + int delta = mLastFlingX - x; + + // Pretend that each frame of a fling scroll is a touch scroll + if (delta > 0) { + // Moving towards the left. Use leftmost view as + // mDownTouchPosition + mDownTouchPosition = mIsRtl ? mFirstPosition + getChildCount() - 1 : mFirstPosition; + + // Don't fling more than 1 screen + delta = Math.min(getWidth() - getPaddingLeft() - getPaddingRight() - 1, delta); + } else { + // Moving towards the right. Use rightmost view as + // mDownTouchPosition + mDownTouchPosition = mIsRtl ? mFirstPosition : mFirstPosition + getChildCount() - 1; + + // Don't fling more than 1 screen + delta = Math.max(-(getWidth() - getPaddingRight() - getPaddingLeft() - 1), delta); + } + + trackMotionScroll(delta); + + if (more && !mShouldStopFling) { + mLastFlingX = x; + post(this); + } else { + endFling(true); + } + } + + public void startUsingDistance(final int distance) { + if (distance == 0) return; + + startCommon(); + + mLastFlingX = 0; + mScroller.startScroll(0, 0, -distance, 0, mAnimationDuration); + post(this); + } + + public void startUsingVelocity(final int initialVelocity) { + if (initialVelocity == 0) return; + + startCommon(); + + final int initialX = initialVelocity < 0 ? Integer.MAX_VALUE : 0; + mLastFlingX = initialX; + mScroller.fling(initialX, 0, initialVelocity, 0, 0, Integer.MAX_VALUE, 0, Integer.MAX_VALUE); + post(this); + } + + public void stop(final boolean scrollIntoSlots) { + removeCallbacks(this); + endFling(scrollIntoSlots); + } + + private void endFling(final boolean scrollIntoSlots) { + /* + * Force the scroller's status to finished (without setting its + * position to the end) + */ + mScroller.forceFinished(true); + + if (scrollIntoSlots) { + scrollIntoSlots(); + } + } + + // CHECKSTYLE:OFF unmodified + private void startCommon() { + // Remove any pending flings + removeCallbacks(this); + } + + } +} diff --git a/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/Crouton.java b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/Crouton.java new file mode 100644 index 000000000..784e96525 --- /dev/null +++ b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/Crouton.java @@ -0,0 +1,847 @@ +/* + * Copyright 2012 - 2013 Benjamin Weiss + * Copyright 2012 Neofonie Mobile GmbH + * + * 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 de.keyboardsurfer.android.widget.crouton; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.util.TypedValue; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.mariotaku.twidere.util.accessor.ViewAccessor; + +/* + * Based on an article by Cyril Mottier (http://android.cyrilmottier.com/?p=773)
+ */ + +/** + * Displays information in a non-invasive context related manner. Like + * {@link android.widget.Toast}, but better. + *

+ * Important: Call {@link Crouton#clearCroutonsForActivity(Activity)} + * within {@link android.app.Activity#onDestroy()} to avoid {@link Context} + * leaks. + */ +public final class Crouton { + private static final int IMAGE_ID = 0x100; + private static final int TEXT_ID = 0x101; + private final CharSequence text; + private final CroutonStyle style; + private CroutonConfiguration configuration = null; + private final View customView; + + private OnClickListener onClickListener; + + private Activity activity; + private ViewGroup viewGroup; + private FrameLayout croutonView; + private Animation inAnimation; + private Animation outAnimation; + private CroutonLifecycleCallback lifecycleCallback = null; + + /** + * Creates the {@link Crouton}. + * + * @param activity The {@link Activity} that the {@link Crouton} should be + * attached to. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + */ + private Crouton(final Activity activity, final CharSequence text, final CroutonStyle style) { + if (activity == null || text == null || style == null) + throw new IllegalArgumentException("Null parameters are not accepted"); + + this.activity = activity; + viewGroup = null; + this.text = text; + this.style = style; + customView = null; + } + + /** + * Creates the {@link Crouton}. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + */ + private Crouton(final Activity activity, final CharSequence text, final CroutonStyle style, + final ViewGroup viewGroup) { + if (activity == null || text == null || style == null) + throw new IllegalArgumentException("Null parameters are not accepted"); + + this.activity = activity; + this.text = text; + this.style = style; + this.viewGroup = viewGroup; + customView = null; + } + + /** + * Creates the {@link Crouton}. + * + * @param activity The {@link Activity} that the {@link Crouton} should be + * attached to. + * @param customView The custom {@link View} to display + */ + private Crouton(final Activity activity, final View customView) { + if (activity == null || customView == null) + throw new IllegalArgumentException("Null parameters are not accepted"); + + this.activity = activity; + viewGroup = null; + this.customView = customView; + style = new CroutonStyle.Builder().build(); + text = null; + } + + /** + * Creates the {@link Crouton}. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + */ + private Crouton(final Activity activity, final View customView, final ViewGroup viewGroup) { + this(activity, customView, viewGroup, CroutonConfiguration.DEFAULT); + } + + /** + * Creates the {@link Crouton}. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + * @param configuration The {@link CroutonConfiguration} for this + * {@link Crouton}. + */ + private Crouton(final Activity activity, final View customView, final ViewGroup viewGroup, + final CroutonConfiguration configuration) { + if (activity == null || customView == null) + throw new IllegalArgumentException("Null parameters are not accepted"); + + this.activity = activity; + this.customView = customView; + this.viewGroup = viewGroup; + style = new CroutonStyle.Builder().build(); + text = null; + this.configuration = configuration; + } + + /** Cancels a {@link Crouton} immediately. */ + public void cancel() { + final CroutonManager manager = CroutonManager.getInstance(); + manager.removeCroutonImmediately(this); + } + + public Animation getInAnimation() { + if (null == inAnimation && null != activity) { + if (getConfiguration().inAnimationResId > 0) { + inAnimation = AnimationUtils.loadAnimation(getActivity(), getConfiguration().inAnimationResId); + } else { + measureCroutonView(); + inAnimation = DefaultAnimationsBuilder.buildDefaultSlideInDownAnimation(getView()); + } + } + + return inAnimation; + } + + public Animation getOutAnimation() { + if (null == outAnimation && null != activity) { + if (getConfiguration().outAnimationResId > 0) { + outAnimation = AnimationUtils.loadAnimation(getActivity(), getConfiguration().outAnimationResId); + } else { + outAnimation = DefaultAnimationsBuilder.buildDefaultSlideOutUpAnimation(getView()); + } + } + + return outAnimation; + } + + /** + * Set the {@link CroutonConfiguration} on this {@link Crouton}, prior to + * showing it. + * + * @param configuration a {@link CroutonConfiguration} built using the + * {@link CroutonConfiguration.Builder}. + * @return this {@link Crouton}. + */ + public Crouton setConfiguration(final CroutonConfiguration configuration) { + this.configuration = configuration; + return this; + } + + /** + * @param lifecycleCallback Callback object for notable events in the life + * of a Crouton. + */ + public void setLifecycleCallback(final CroutonLifecycleCallback lifecycleCallback) { + this.lifecycleCallback = lifecycleCallback; + } + + /** + * Allows setting of an {@link OnClickListener} directly to a + * {@link Crouton} without having to use a custom view. + * + * @param onClickListener The {@link OnClickListener} to set. + * @return this {@link Crouton}. + */ + public Crouton setOnClickListener(final OnClickListener onClickListener) { + this.onClickListener = onClickListener; + return this; + } + + /** + * Displays the {@link Crouton}. If there's another {@link Crouton} visible + * at the time, this {@link Crouton} will be displayed afterwards. + */ + public void show() { + CroutonManager.getInstance().add(this); + } + + @Override + public String toString() { + return "Crouton{" + "text=" + text + ", style=" + style + ", configuration=" + configuration + ", customView=" + + customView + ", onClickListener=" + onClickListener + ", activity=" + activity + ", viewGroup=" + + viewGroup + ", croutonView=" + croutonView + ", inAnimation=" + inAnimation + ", outAnimation=" + + outAnimation + ", lifecycleCallback=" + lifecycleCallback + '}'; + } + + private RelativeLayout initializeContentView(final Resources resources) { + final RelativeLayout contentView = new RelativeLayout(activity); + contentView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.WRAP_CONTENT)); + + // set padding + int padding = style.paddingInPixels; + + // if a padding dimension has been set, this will overwrite any padding + // in pixels + if (style.paddingDimensionResId > 0) { + padding = resources.getDimensionPixelSize(style.paddingDimensionResId); + } + contentView.setPadding(padding, padding, padding, padding); + + // only setup image if one is requested + ImageView image = null; + if (null != style.imageDrawable || 0 != style.imageResId) { + image = initializeImageView(); + contentView.addView(image, image.getLayoutParams()); + } + + final TextView text = initializeTextView(resources); + + final RelativeLayout.LayoutParams textParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + if (null != image) { + textParams.addRule(RelativeLayout.RIGHT_OF, image.getId()); + } + contentView.addView(text, textParams); + return contentView; + } + + private void initializeCroutonView() { + final Resources resources = activity.getResources(); + + croutonView = initializeCroutonViewGroup(resources); + + // create content view + final RelativeLayout contentView = initializeContentView(resources); + croutonView.addView(contentView); + } + + private FrameLayout initializeCroutonViewGroup(final Resources resources) { + final FrameLayout croutonView = new FrameLayout(activity); + + if (null != onClickListener) { + croutonView.setOnClickListener(onClickListener); + } + + final int height; + if (style.heightDimensionResId > 0) { + height = resources.getDimensionPixelSize(style.heightDimensionResId); + } else { + height = style.heightInPixels; + } + + final int width; + if (style.widthDimensionResId > 0) { + width = resources.getDimensionPixelSize(style.widthDimensionResId); + } else { + width = style.widthInPixels; + } + + croutonView.setLayoutParams(new FrameLayout.LayoutParams(width != 0 ? width + : FrameLayout.LayoutParams.MATCH_PARENT, height)); + + // set background + if (style.backgroundColorValue != -1) { + croutonView.setBackgroundColor(style.backgroundColorValue); + } else { + croutonView.setBackgroundColor(resources.getColor(style.backgroundColorResourceId)); + } + + // set the background drawable if set. This will override the background + // color. + if (style.backgroundDrawableResourceId != 0) { + final Bitmap background = BitmapFactory.decodeResource(resources, style.backgroundDrawableResourceId); + final BitmapDrawable drawable = new BitmapDrawable(resources, background); + if (style.isTileEnabled) { + drawable.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + } + ViewAccessor.setBackground(croutonView, drawable); + } + return croutonView; + } + + private ImageView initializeImageView() { + ImageView image; + image = new ImageView(activity); + image.setId(IMAGE_ID); + image.setAdjustViewBounds(true); + image.setScaleType(style.imageScaleType); + + // set the image drawable if not null + if (null != style.imageDrawable) { + image.setImageDrawable(style.imageDrawable); + } + + // set the image resource if not 0. This will overwrite the drawable + // if both are set + if (style.imageResId != 0) { + image.setImageResource(style.imageResId); + } + + final RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + imageParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE); + imageParams.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE); + + image.setLayoutParams(imageParams); + + return image; + } + + private TextView initializeTextView(final Resources resources) { + final TextView text = new TextView(activity); + text.setId(TEXT_ID); + text.setText(this.text); + text.setContentDescription(this.text); + text.setTypeface(Typeface.DEFAULT_BOLD); + text.setGravity(style.gravity); + + // set the text color if set + if (style.textColorResourceId != 0) { + text.setTextColor(resources.getColor(style.textColorResourceId)); + } + + // Set the text size. If the user has set a text size and text + // appearance, the text size in the text appearance + // will override this. + if (style.textSize != 0) { + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, style.textSize); + } + + // Setup the shadow if requested + if (style.textShadowColorResId != 0) { + initializeTextViewShadow(resources, text); + } + + // Set the text appearance + if (style.textAppearanceResId != 0) { + text.setTextAppearance(activity, style.textAppearanceResId); + } + return text; + } + + private void initializeTextViewShadow(final Resources resources, final TextView text) { + final int textShadowColor = resources.getColor(style.textShadowColorResId); + final float textShadowRadius = style.textShadowRadius; + final float textShadowDx = style.textShadowDx; + final float textShadowDy = style.textShadowDy; + text.setShadowLayer(textShadowRadius, textShadowDx, textShadowDy, textShadowColor); + } + + private boolean isCroutonViewNotNull() { + return null != croutonView && null != croutonView.getParent(); + } + + private boolean isCustomViewNotNull() { + return null != customView && null != customView.getParent(); + } + + private void measureCroutonView() { + final View view = getView(); + int widthSpec; + if (viewGroup != null) { + widthSpec = View.MeasureSpec.makeMeasureSpec(viewGroup.getMeasuredWidth(), View.MeasureSpec.AT_MOST); + } else { + widthSpec = View.MeasureSpec.makeMeasureSpec(activity.getWindow().getDecorView().getMeasuredWidth(), + View.MeasureSpec.AT_MOST); + } + view.measure(widthSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + } + + /** Removes the activity reference this {@link Crouton} is holding */ + void detachActivity() { + activity = null; + } + + /** Removes the lifecycleCallback reference this {@link Crouton} is holding */ + void detachLifecycleCallback() { + lifecycleCallback = null; + } + + /** Removes the viewGroup reference this {@link Crouton} is holding */ + void detachViewGroup() { + viewGroup = null; + } + + /** @return the activity */ + Activity getActivity() { + return activity; + } + + /** @return this croutons configuration */ + CroutonConfiguration getConfiguration() { + if (null == configuration) { + configuration = getStyle().configuration; + } + return configuration; + } + + /** @return the lifecycleCallback */ + CroutonLifecycleCallback getLifecycleCallback() { + return lifecycleCallback; + } + + /** @return the style */ + CroutonStyle getStyle() { + return style; + } + + /** @return the text */ + CharSequence getText() { + return text; + } + + /** @return the view */ + View getView() { + // return the custom view if one exists + if (null != customView) return customView; + + // if already setup return the view + if (null == croutonView) { + initializeCroutonView(); + } + + return croutonView; + } + + /** @return the viewGroup */ + ViewGroup getViewGroup() { + return viewGroup; + } + + /** + * @return true if the {@link Crouton} is being displayed, else + * false. + */ + boolean isShowing() { + return null != activity && (isCroutonViewNotNull() || isCustomViewNotNull()); + } + + /** + * Cancels all queued {@link Crouton}s. If there is a {@link Crouton} + * displayed currently, it will be the last one displayed. + */ + public static void cancelAllCroutons() { + CroutonManager.getInstance().clearCroutonQueue(); + } + + /** + * Clears (and removes from {@link Activity}'s content view, if necessary) + * all croutons for the provided activity + * + * @param activity - The {@link Activity} to clear the croutons for. + */ + public static void clearCroutonsForActivity(final Activity activity) { + CroutonManager.getInstance().clearCroutonsForActivity(activity); + } + + /** + * Convenience method to get the license text for embedding within your + * application. + * + * @return The license text. + */ + public static String getLicenseText() { + return "This application uses the Crouton library.\n\n" + "Copyright 2012 - 2013 Benjamin Weiss \n" + + "Copyright 2012 Neofonie Mobile GmbH\n" + "\n" + + "Licensed under the Apache License, Version 2.0 (the \"License\");\n" + + "you may not use this file except in compliance with the License.\n" + + "You may obtain a copy of the License at\n" + "\n" + + " http://www.apache.org/licenses/LICENSE-2.0\n" + "\n" + + "Unless required by applicable law or agreed to in writing, software\n" + + "distributed under the License is distributed on an \"AS IS\" BASIS,\n" + + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + + "See the License for the specific language governing permissions and\n" + + "limitations under the License."; + } + + /** + * Allows hiding of a previously displayed {@link Crouton}. + * + * @param crouton The {@link Crouton} you want to hide. + */ + public static void hide(final Crouton crouton) { + CroutonManager.getInstance().removeCrouton(crouton); + } + + // //////////////////////////////////////////////////////////////////////////////////// + // You have reached the internal API of Crouton. + // If you do not plan to develop for Crouton there is nothing of interest + // below here. + // //////////////////////////////////////////////////////////////////////////////////// + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that the {@link Crouton} should be + * attached to. + * @param customView The custom {@link View} to display + * @return The created {@link Crouton}. + */ + public static Crouton make(final Activity activity, final View customView) { + return new Crouton(activity, customView); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + * @return The created {@link Crouton}. + */ + public static Crouton make(final Activity activity, final View customView, final int viewGroupResId) { + return new Crouton(activity, customView, (ViewGroup) activity.findViewById(viewGroupResId)); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + * @param configuration The configuration for this crouton. + * @return The created {@link Crouton}. + */ + public static Crouton make(final Activity activity, final View customView, final int viewGroupResId, + final CroutonConfiguration configuration) { + return new Crouton(activity, customView, (ViewGroup) activity.findViewById(viewGroupResId), configuration); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + * @return The created {@link Crouton}. + */ + public static Crouton make(final Activity activity, final View customView, final ViewGroup viewGroup) { + return new Crouton(activity, customView, viewGroup); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity. + * + * @param activity The {@link Activity} that the {@link Crouton} should be + * attached to. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @return The created {@link Crouton}. + */ + public static Crouton makeText(final Activity activity, final CharSequence text, final CroutonStyle style) { + return new Crouton(activity, text, style); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + * @return The created {@link Crouton}. + */ + public static Crouton makeText(final Activity activity, final CharSequence text, final CroutonStyle style, + final int viewGroupResId) { + return new Crouton(activity, text, style, (ViewGroup) activity.findViewById(viewGroupResId)); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + * @return The created {@link Crouton}. + */ + public static Crouton makeText(final Activity activity, final CharSequence text, final CroutonStyle style, + final ViewGroup viewGroup) { + return new Crouton(activity, text, style, viewGroup); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that the {@link Crouton} should be + * attached to. + * @param textResourceId The resource id of the text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @return The created {@link Crouton}. + */ + public static Crouton makeText(final Activity activity, final int textResourceId, final CroutonStyle style) { + return makeText(activity, activity.getString(textResourceId), style); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param textResourceId The resource id of the text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + * @return The created {@link Crouton}. + */ + public static Crouton makeText(final Activity activity, final int textResourceId, final CroutonStyle style, + final int viewGroupResId) { + return makeText(activity, activity.getString(textResourceId), style, + (ViewGroup) activity.findViewById(viewGroupResId)); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param textResourceId The resource id of the text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + * @return The created {@link Crouton}. + */ + public static Crouton makeText(final Activity activity, final int textResourceId, final CroutonStyle style, + final ViewGroup viewGroup) { + return makeText(activity, activity.getString(textResourceId), style, viewGroup); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link android.app.Activity} that the {@link Crouton} + * should be attached to. + * @param customView The custom {@link View} to display + */ + public static void show(final Activity activity, final View customView) { + make(activity, customView).show(); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + */ + public static void show(final Activity activity, final View customView, final int viewGroupResId) { + make(activity, customView, viewGroupResId).show(); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param customView The custom {@link View} to display + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + */ + public static void show(final Activity activity, final View customView, final ViewGroup viewGroup) { + make(activity, customView, viewGroup).show(); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link android.app.Activity} that the {@link Crouton} + * should be attached to. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + */ + public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style) { + makeText(activity, text, style).show(); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + */ + public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style, + final int viewGroupResId) { + makeText(activity, text, style, (ViewGroup) activity.findViewById(viewGroupResId)).show(); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + * @param configuration The configuration for this Crouton. + */ + public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style, + final int viewGroupResId, final CroutonConfiguration configuration) { + makeText(activity, text, style, (ViewGroup) activity.findViewById(viewGroupResId)).setConfiguration( + configuration).show(); + } + + /** + * Creates a {@link Crouton} with provided text and style for a given + * activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param text The text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + */ + public static void showText(final Activity activity, final CharSequence text, final CroutonStyle style, + final ViewGroup viewGroup) { + makeText(activity, text, style, viewGroup).show(); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity and displays it directly. + * + * @param activity The {@link Activity} that the {@link Crouton} should be + * attached to. + * @param textResourceId The resource id of the text you want to display. + * @param style The style that this {@link Crouton} should be created with. + */ + public static void showText(final Activity activity, final int textResourceId, final CroutonStyle style) { + showText(activity, activity.getString(textResourceId), style); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param textResourceId The resource id of the text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroupResId The resource id of the {@link ViewGroup} that this + * {@link Crouton} should be added to. + */ + public static void showText(final Activity activity, final int textResourceId, final CroutonStyle style, + final int viewGroupResId) { + showText(activity, activity.getString(textResourceId), style, viewGroupResId); + } + + /** + * Creates a {@link Crouton} with provided text-resource and style for a + * given activity and displays it directly. + * + * @param activity The {@link Activity} that represents the context in which + * the Crouton should exist. + * @param textResourceId The resource id of the text you want to display. + * @param style The style that this {@link Crouton} should be created with. + * @param viewGroup The {@link ViewGroup} that this {@link Crouton} should + * be added to. + */ + public static void showText(final Activity activity, final int textResourceId, final CroutonStyle style, + final ViewGroup viewGroup) { + showText(activity, activity.getString(textResourceId), style, viewGroup); + } +} diff --git a/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonConfiguration.java b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonConfiguration.java new file mode 100644 index 000000000..c421dff00 --- /dev/null +++ b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonConfiguration.java @@ -0,0 +1,127 @@ +/* + * Copyright 2013 Benjamin Weiss + * + * 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 de.keyboardsurfer.android.widget.crouton; + +/** + * Allows configuring a {@link Crouton}s behavior aside from the actual view, + * which is defined via {@link CroutonStyle}. + *

+ * This allows to re-use a {@link CroutonStyle} while modifying parameters that + * only have to be applied when the {@link Crouton} is being displayed. + * + * @author chris + * @since 1.8 + */ +public class CroutonConfiguration { + + /** + * Display a {@link Crouton} for an infinite amount of time or until + * {@link de.keyboardsurfer.android.widget.crouton.Crouton#cancel()} has + * been called. + */ + public static final int DURATION_INFINITE = -1; + /** The default short display duration of a {@link Crouton}. */ + public static final int DURATION_SHORT = 3000; + /** The default long display duration of a {@link Crouton}. */ + public static final int DURATION_LONG = 5000; + + /** The default {@link CroutonConfiguration} of a {@link Crouton}. */ + public static final CroutonConfiguration DEFAULT; + + static { + DEFAULT = new Builder().setDuration(DURATION_SHORT).build(); + } + + /** + * The durationInMilliseconds the {@link Crouton} will be displayed in + * milliseconds. + */ + final int durationInMilliseconds; + /** The resource id for the in animation. */ + final int inAnimationResId; + /** The resource id for the out animation. */ + final int outAnimationResId; + + private CroutonConfiguration(final Builder builder) { + durationInMilliseconds = builder.durationInMilliseconds; + inAnimationResId = builder.inAnimationResId; + outAnimationResId = builder.outAnimationResId; + } + + @Override + public String toString() { + return "Configuration{" + "durationInMilliseconds=" + durationInMilliseconds + ", inAnimationResId=" + + inAnimationResId + ", outAnimationResId=" + outAnimationResId + '}'; + } + + /** Creates a {@link Builder} to build a {@link CroutonConfiguration} upon. */ + public static class Builder { + private int durationInMilliseconds = DURATION_SHORT; + private int inAnimationResId = 0; + private int outAnimationResId = 0; + + /** + * Builds the {@link CroutonConfiguration}. + * + * @return The built {@link CroutonConfiguration}. + */ + public CroutonConfiguration build() { + return new CroutonConfiguration(this); + } + + /** + * Set the durationInMilliseconds option of the {@link Crouton}. + * + * @param duration The durationInMilliseconds the crouton will be + * displayed {@link Crouton} in milliseconds. + * @return the {@link Builder}. + */ + public Builder setDuration(final int duration) { + durationInMilliseconds = duration; + + return this; + } + + /** + * The resource id for the in animation. + * + * @param inAnimationResId The resource identifier for the animation + * that's being shown when the {@link Crouton} is going to be + * displayed. + * @return the {@link Builder}. + */ + public Builder setInAnimation(final int inAnimationResId) { + this.inAnimationResId = inAnimationResId; + + return this; + } + + /** + * The resource id for the out animation + * + * @param outAnimationResId The resource identifier for the animation + * that's being shown when the {@link Crouton} is going to be + * removed. + * @return the {@link Builder}. + */ + public Builder setOutAnimation(final int outAnimationResId) { + this.outAnimationResId = outAnimationResId; + + return this; + } + } +} diff --git a/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonLifecycleCallback.java b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonLifecycleCallback.java new file mode 100644 index 000000000..ed3b16b7f --- /dev/null +++ b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonLifecycleCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012 - 2013 Benjamin Weiss + * + * 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 de.keyboardsurfer.android.widget.crouton; + +/** Provides callback methods on major lifecycle events of a {@link Crouton}. */ +public interface CroutonLifecycleCallback { + /** Will be called when your Crouton has been displayed. */ + public void onDisplayed(); + + /** Will be called when your {@link Crouton} has been removed. */ + public void onRemoved(); + + // public void onCeasarDressing(); +} diff --git a/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonManager.java b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonManager.java new file mode 100644 index 000000000..3ad80ab8d --- /dev/null +++ b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonManager.java @@ -0,0 +1,366 @@ +/* + * Copyright 2012 - 2013 Benjamin Weiss + * Copyright 2012 Neofonie Mobile GmbH + * + * 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 de.keyboardsurfer.android.widget.crouton; + +import static org.mariotaku.twidere.util.Utils.announceForAccessibilityCompat; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.FrameLayout; + +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; + +/** Manages the lifecycle of {@link Crouton}s. */ +final class CroutonManager extends Handler { + private static CroutonManager INSTANCE; + + private final Queue croutonQueue; + + private CroutonManager() { + croutonQueue = new LinkedBlockingQueue(); + } + + /* + * (non-Javadoc) + * + * @see android.os.Handler#handleMessage(android.os.Message) + */ + @Override + public void handleMessage(final Message message) { + final Crouton crouton = (Crouton) message.obj; + + switch (message.what) { + case Messages.DISPLAY_CROUTON: { + displayCrouton(); + break; + } + case Messages.ADD_CROUTON_TO_VIEW: { + addCroutonToView(crouton); + break; + } + case Messages.REMOVE_CROUTON: { + removeCrouton(crouton); + if (null != crouton.getLifecycleCallback()) { + crouton.getLifecycleCallback().onRemoved(); + } + break; + } + default: { + super.handleMessage(message); + break; + } + } + } + + @Override + public String toString() { + return "CroutonManager{croutonQueue=" + croutonQueue + '}'; + } + + /** + * Removes the {@link Crouton}'s view after it's display + * durationInMilliseconds. + * + * @param crouton The {@link Crouton} added to a {@link ViewGroup} and + * should be removed. + */ + protected void removeCrouton(final Crouton crouton) { + final View croutonView = crouton.getView(); + final ViewGroup croutonParentView = (ViewGroup) croutonView.getParent(); + + if (croutonParentView == null) return; + croutonView.startAnimation(crouton.getOutAnimation()); + + // Remove the Crouton from the queue. + final Crouton removed = croutonQueue.poll(); + + // Remove the crouton from the view's parent. + croutonParentView.removeView(croutonView); + if (null != removed) { + removed.detachActivity(); + removed.detachViewGroup(); + if (null != removed.getLifecycleCallback()) { + removed.getLifecycleCallback().onRemoved(); + } + removed.detachLifecycleCallback(); + } + + // Send a message to display the next crouton but delay it by the out + // animation duration to make sure it finishes + sendMessageDelayed(crouton, Messages.DISPLAY_CROUTON, crouton.getOutAnimation().getDuration()); + } + + /** + * Adds a {@link Crouton} to the {@link ViewParent} of it's {@link Activity} + * . + * + * @param crouton The {@link Crouton} that should be added. + */ + private void addCroutonToView(final Crouton crouton) { + // don't add if it is already showing + if (crouton.isShowing()) return; + + final View croutonView = crouton.getView(); + if (null == croutonView.getParent()) { + ViewGroup.LayoutParams params = croutonView.getLayoutParams(); + if (null == params) { + params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + // display Crouton in ViewGroup is it has been supplied + if (null != crouton.getViewGroup()) { + // TODO implement add to last position feature (need to align + // with how this will be requested for activity) + if (crouton.getViewGroup() instanceof FrameLayout) { + crouton.getViewGroup().addView(croutonView, params); + } else { + crouton.getViewGroup().addView(croutonView, 0, params); + } + } else { + final Activity activity = crouton.getActivity(); + if (null == activity || activity.isFinishing()) return; + activity.addContentView(croutonView, params); + } + } + + croutonView.requestLayout(); // This is needed so the animation can use + // the measured with/height + croutonView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + ViewTreeObserverAccessor.removeOnGlobalLayoutListener(croutonView.getViewTreeObserver(), this); + + croutonView.startAnimation(crouton.getInAnimation()); + announceForAccessibilityCompat(crouton.getActivity(), crouton.getView(), crouton.getText(), getClass()); + if (CroutonConfiguration.DURATION_INFINITE != crouton.getConfiguration().durationInMilliseconds) { + sendMessageDelayed(crouton, Messages.REMOVE_CROUTON, + crouton.getConfiguration().durationInMilliseconds + crouton.getInAnimation().getDuration()); + } + } + }); + } + + private long calculateCroutonDuration(final Crouton crouton) { + long croutonDuration = crouton.getConfiguration().durationInMilliseconds; + croutonDuration += crouton.getInAnimation().getDuration(); + croutonDuration += crouton.getOutAnimation().getDuration(); + return croutonDuration; + } + + /** Displays the next {@link Crouton} within the queue. */ + private void displayCrouton() { + if (croutonQueue.isEmpty()) return; + + // First peek whether the Crouton has an activity. + final Crouton currentCrouton = croutonQueue.peek(); + + // If the activity is null we poll the Crouton off the queue. + if (null == currentCrouton.getActivity()) { + croutonQueue.poll(); + } + + if (!currentCrouton.isShowing()) { + // Display the Crouton + sendMessage(currentCrouton, Messages.ADD_CROUTON_TO_VIEW); + if (null != currentCrouton.getLifecycleCallback()) { + currentCrouton.getLifecycleCallback().onDisplayed(); + } + } else { + sendMessageDelayed(currentCrouton, Messages.DISPLAY_CROUTON, calculateCroutonDuration(currentCrouton)); + } + } + + private void removeAllMessages() { + removeMessages(Messages.ADD_CROUTON_TO_VIEW); + removeMessages(Messages.DISPLAY_CROUTON); + removeMessages(Messages.REMOVE_CROUTON); + } + + private void removeAllMessagesForCrouton(final Crouton crouton) { + removeMessages(Messages.ADD_CROUTON_TO_VIEW, crouton); + removeMessages(Messages.DISPLAY_CROUTON, crouton); + removeMessages(Messages.REMOVE_CROUTON, crouton); + } + + /** + * Sends a {@link Crouton} within a {@link Message}. + * + * @param crouton The {@link Crouton} that should be sent. + * @param messageId The {@link Message} id. + */ + private void sendMessage(final Crouton crouton, final int messageId) { + final Message message = obtainMessage(messageId); + message.obj = crouton; + sendMessage(message); + } + + /** + * Sends a {@link Crouton} within a delayed {@link Message}. + * + * @param crouton The {@link Crouton} that should be sent. + * @param messageId The {@link Message} id. + * @param delay The delay in milliseconds. + */ + private void sendMessageDelayed(final Crouton crouton, final int messageId, final long delay) { + final Message message = obtainMessage(messageId); + message.obj = crouton; + sendMessageDelayed(message, delay); + } + + /** + * Inserts a {@link Crouton} to be displayed. + * + * @param crouton The {@link Crouton} to be displayed. + */ + void add(final Crouton crouton) { + croutonQueue.add(crouton); + displayCrouton(); + } + + /** Removes all {@link Crouton}s from the queue. */ + void clearCroutonQueue() { + removeAllMessages(); + if (croutonQueue == null) return; + // remove any views that may already have been added to the activity's + // content view + for (final Crouton crouton : croutonQueue) { + if (crouton.isShowing()) { + ((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView()); + } + } + croutonQueue.clear(); + } + + /** + * Removes all {@link Crouton}s for the provided activity. This will remove + * crouton from {@link Activity}s content view immediately. + */ + void clearCroutonsForActivity(final Activity activity) { + if (croutonQueue == null) return; + final Iterator croutonIterator = croutonQueue.iterator(); + while (croutonIterator.hasNext()) { + final Crouton crouton = croutonIterator.next(); + if (null != crouton.getActivity() && crouton.getActivity().equals(activity)) { + // remove the crouton from the content view + if (crouton.isShowing()) { + ((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView()); + } + removeAllMessagesForCrouton(crouton); + // remove the crouton from the queue + croutonIterator.remove(); + } + } + } + + /** + * Removes a {@link Crouton} immediately, even when it's currently being + * displayed. + * + * @param crouton The {@link Crouton} that should be removed. + */ + void removeCroutonImmediately(final Crouton crouton) { + // if Crouton has already been displayed then it may not be in the queue + // (because it was popped). + // This ensures the displayed Crouton is removed from its parent + // immediately, whether another instance + // of it exists in the queue or not. + // Note: crouton.isShowing() is false here even if it really is showing, + // as croutonView object in + // Crouton seems to be out of sync with reality! + if (null != crouton.getActivity() && null != crouton.getView() && null != crouton.getView().getParent()) { + ((ViewGroup) crouton.getView().getParent()).removeView(crouton.getView()); + + // remove any messages pending for the crouton + removeAllMessagesForCrouton(crouton); + } + // remove any matching croutons from queue + if (croutonQueue == null) return; + final Iterator croutonIterator = croutonQueue.iterator(); + while (croutonIterator.hasNext()) { + final Crouton c = croutonIterator.next(); + if (c.equals(crouton) && null != c.getActivity()) { + // remove the crouton from the content view + if (crouton.isShowing()) { + ((ViewGroup) c.getView().getParent()).removeView(c.getView()); + } + + // remove any messages pending for the crouton + removeAllMessagesForCrouton(c); + + // remove the crouton from the queue + croutonIterator.remove(); + + // we have found our crouton so just break + break; + } + } + } + + /** @return The currently used instance of the {@link Manager}. */ + static synchronized CroutonManager getInstance() { + if (null == INSTANCE) { + INSTANCE = new CroutonManager(); + } + + return INSTANCE; + } + + private static final class Messages { + public static final int DISPLAY_CROUTON = 0xc2007; + + public static final int ADD_CROUTON_TO_VIEW = 0xc20074dd; + public static final int REMOVE_CROUTON = 0xc2007de1; + + private Messages() { /* no-op */ + } + } + + private static class ViewTreeObserverAccessor { + + @SuppressWarnings("deprecation") + private static void removeOnGlobalLayoutListener(final ViewTreeObserver observer, + final OnGlobalLayoutListener listener) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + observer.removeGlobalOnLayoutListener(listener); + } else { + ViewTreeObserverAccessorJB.removeOnGlobalLayoutListener(observer, listener); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private static class ViewTreeObserverAccessorJB { + + private static void removeOnGlobalLayoutListener(final ViewTreeObserver observer, + final OnGlobalLayoutListener listener) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return; + observer.removeOnGlobalLayoutListener(listener); + } + + } + } +} diff --git a/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonStyle.java b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonStyle.java new file mode 100644 index 000000000..90efa3e1f --- /dev/null +++ b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/CroutonStyle.java @@ -0,0 +1,479 @@ +/* + * Copyright 2012 - 2013 Benjamin Weiss + * Copyright 2012 Neofonie Mobile GmbH + * + * 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 de.keyboardsurfer.android.widget.crouton; + +import android.graphics.drawable.Drawable; +import android.view.Gravity; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; + +/** The style for a {@link Crouton}. */ +public class CroutonStyle { + + public static final int holoRedLight = 0xffff4444; + public static final int holoGreenLight = 0xff99cc00; + public static final int holoBlueLight = 0xff33b5e5; + public static final int holoOrangeLight = 0xffffbb33; + + /** Default style for alerting the user. */ + public static final CroutonStyle ALERT; + /** Default style for warn the user. */ + public static final CroutonStyle WARN; + /** Default style for confirming an action. */ + public static final CroutonStyle CONFIRM; + /** Default style for general information. */ + public static final CroutonStyle INFO; + + static { + ALERT = new Builder().setBackgroundColorValue(holoRedLight).build(); + WARN = new Builder().setBackgroundColorValue(holoOrangeLight).build(); + CONFIRM = new Builder().setBackgroundColorValue(holoGreenLight).build(); + INFO = new Builder().setBackgroundColorValue(holoBlueLight).build(); + } + + /** + * The {@link CroutonConfiguration} for this {@link CroutonStyle}. It can be + * overridden via {@link Crouton#setConfiguration(CroutonConfiguration)}. + */ + final CroutonConfiguration configuration; + + /** + * The resource id of the backgroundResourceId. + *

+ * 0 for no backgroundResourceId. + */ + final int backgroundColorResourceId; + + /** + * The resource id of the backgroundDrawableResourceId. + *

+ * 0 for no backgroundDrawableResourceId. + */ + final int backgroundDrawableResourceId; + + /** + * The backgroundColorResourceValue's e.g. 0xffff4444; + *

+ * -1 for no value. + */ + final int backgroundColorValue; + + /** Whether we should isTileEnabled the backgroundResourceId or not. */ + final boolean isTileEnabled; + + /** + * The text colorResourceId's resource id. + *

+ * 0 sets the text colorResourceId to the system theme default. + */ + final int textColorResourceId; + + /** The height of the {@link Crouton} in pixels. */ + final int heightInPixels; + + /** Resource ID for the height of the {@link Crouton}. */ + final int heightDimensionResId; + + /** The width of the {@link Crouton} in pixels. */ + final int widthInPixels; + + /** Resource ID for the width of the {@link Crouton}. */ + final int widthDimensionResId; + + /** The text's gravity as provided by {@link Gravity}. */ + final int gravity; + + /** An additional image to display in the {@link Crouton}. */ + final Drawable imageDrawable; + + /** An additional image to display in the {@link Crouton}. */ + final int imageResId; + + /** + * The {@link ImageView.ScaleType} for the image to display in the + * {@link Crouton}. + */ + final ImageView.ScaleType imageScaleType; + + /** + * The text size in sp + *

+ * 0 sets the text size to the system theme default + */ + final int textSize; + + /** The text shadow color's resource id */ + final int textShadowColorResId; + + /** The text shadow radius */ + final float textShadowRadius; + + /** The text shadow vertical offset */ + final float textShadowDy; + + /** The text shadow horizontal offset */ + final float textShadowDx; + + /** The text appearance resource id for the text. */ + final int textAppearanceResId; + + /** The padding for the crouton view content in pixels */ + final int paddingInPixels; + + /** The resource id for the padding for the view content */ + final int paddingDimensionResId; + + private CroutonStyle(final Builder builder) { + configuration = builder.configuration; + backgroundColorResourceId = builder.backgroundColorResourceId; + backgroundDrawableResourceId = builder.backgroundDrawableResourceId; + isTileEnabled = builder.isTileEnabled; + textColorResourceId = builder.textColorResourceId; + heightInPixels = builder.heightInPixels; + heightDimensionResId = builder.heightDimensionResId; + widthInPixels = builder.widthInPixels; + widthDimensionResId = builder.widthDimensionResId; + gravity = builder.gravity; + imageDrawable = builder.imageDrawable; + textSize = builder.textSize; + textShadowColorResId = builder.textShadowColorResId; + textShadowRadius = builder.textShadowRadius; + textShadowDx = builder.textShadowDx; + textShadowDy = builder.textShadowDy; + textAppearanceResId = builder.textAppearanceResId; + imageResId = builder.imageResId; + imageScaleType = builder.imageScaleType; + paddingInPixels = builder.paddingInPixels; + paddingDimensionResId = builder.paddingDimensionResId; + backgroundColorValue = builder.backgroundColorValue; + } + + @Override + public String toString() { + return "Style{" + "configuration=" + configuration + ", backgroundColorResourceId=" + backgroundColorResourceId + + ", backgroundDrawableResourceId=" + backgroundDrawableResourceId + ", backgroundColorValue=" + + backgroundColorValue + ", isTileEnabled=" + isTileEnabled + ", textColorResourceId=" + + textColorResourceId + ", heightInPixels=" + heightInPixels + ", heightDimensionResId=" + + heightDimensionResId + ", widthInPixels=" + widthInPixels + ", widthDimensionResId=" + + widthDimensionResId + ", gravity=" + gravity + ", imageDrawable=" + imageDrawable + ", imageResId=" + + imageResId + ", imageScaleType=" + imageScaleType + ", textSize=" + textSize + + ", textShadowColorResId=" + textShadowColorResId + ", textShadowRadius=" + textShadowRadius + + ", textShadowDy=" + textShadowDy + ", textShadowDx=" + textShadowDx + ", textAppearanceResId=" + + textAppearanceResId + ", paddingInPixels=" + paddingInPixels + ", paddingDimensionResId=" + + paddingDimensionResId + '}'; + } + + /** Builder for the {@link CroutonStyle} object. */ + public static class Builder { + private CroutonConfiguration configuration; + private int backgroundColorValue; + private int backgroundColorResourceId; + private int backgroundDrawableResourceId; + private boolean isTileEnabled; + private int textColorResourceId; + private int heightInPixels; + private int heightDimensionResId; + private int widthInPixels; + private int widthDimensionResId; + private int gravity; + private Drawable imageDrawable; + private int textSize; + private int textShadowColorResId; + private float textShadowRadius; + private float textShadowDx; + private float textShadowDy; + private int textAppearanceResId; + private int imageResId; + private ImageView.ScaleType imageScaleType; + private int paddingInPixels; + private int paddingDimensionResId; + + /** Creates a {@link Builder} to build a {@link CroutonStyle} upon. */ + public Builder() { + configuration = CroutonConfiguration.DEFAULT; + paddingInPixels = 10; + backgroundColorResourceId = android.R.color.holo_blue_light; + backgroundDrawableResourceId = 0; + backgroundColorValue = -1; + isTileEnabled = false; + textColorResourceId = android.R.color.white; + heightInPixels = LayoutParams.WRAP_CONTENT; + widthInPixels = LayoutParams.MATCH_PARENT; + gravity = Gravity.CENTER; + imageDrawable = null; + imageResId = 0; + imageScaleType = ImageView.ScaleType.FIT_XY; + } + + /** + * Creates a {@link Builder} to build a {@link CroutonStyle} upon. + * + * @param baseStyle The base {@link CroutonStyle} to use for this + * {@link CroutonStyle} . + */ + public Builder(final CroutonStyle baseStyle) { + configuration = baseStyle.configuration; + backgroundColorValue = baseStyle.backgroundColorValue; + backgroundColorResourceId = baseStyle.backgroundColorResourceId; + backgroundDrawableResourceId = baseStyle.backgroundDrawableResourceId; + isTileEnabled = baseStyle.isTileEnabled; + textColorResourceId = baseStyle.textColorResourceId; + heightInPixels = baseStyle.heightInPixels; + heightDimensionResId = baseStyle.heightDimensionResId; + widthInPixels = baseStyle.widthInPixels; + widthDimensionResId = baseStyle.widthDimensionResId; + gravity = baseStyle.gravity; + imageDrawable = baseStyle.imageDrawable; + textSize = baseStyle.textSize; + textShadowColorResId = baseStyle.textShadowColorResId; + textShadowRadius = baseStyle.textShadowRadius; + textShadowDx = baseStyle.textShadowDx; + textShadowDy = baseStyle.textShadowDy; + textAppearanceResId = baseStyle.textAppearanceResId; + imageResId = baseStyle.imageResId; + imageScaleType = baseStyle.imageScaleType; + paddingInPixels = baseStyle.paddingInPixels; + paddingDimensionResId = baseStyle.paddingDimensionResId; + } + + /** @return a configured {@link CroutonStyle} object. */ + public CroutonStyle build() { + return new CroutonStyle(this); + } + + /** + * Set the backgroundColorResourceId option of the {@link Crouton}. + * + * @param backgroundColorResourceId The backgroundColorResourceId's + * resource id. + * @return the {@link Builder}. + */ + public Builder setBackgroundColor(final int backgroundColorResourceId) { + this.backgroundColorResourceId = backgroundColorResourceId; + + return this; + } + + /** + * Set the backgroundColorResourceValue option of the {@link Crouton}. + * + * @param backgroundColorValue The backgroundColorResourceValue's e.g. + * 0xffff4444; + * @return the {@link Builder}. + */ + public Builder setBackgroundColorValue(final int backgroundColorValue) { + this.backgroundColorValue = backgroundColorValue; + return this; + } + + /** + * Set the backgroundDrawableResourceId option for the {@link Crouton}. + * + * @param backgroundDrawableResourceId Resource ID of a + * backgroundDrawableResourceId image drawable. + * @return the {@link Builder}. + */ + public Builder setBackgroundDrawable(final int backgroundDrawableResourceId) { + this.backgroundDrawableResourceId = backgroundDrawableResourceId; + + return this; + } + + /** + * Set the {@link CroutonConfiguration} option of the {@link Crouton}. + * + * @param configuration The {@link CroutonConfiguration}. + * @return the {@link Builder}. + */ + public Builder setConfiguration(final CroutonConfiguration configuration) { + this.configuration = configuration; + return this; + } + + /** + * Set the gravity option for the {@link Crouton}. + * + * @param gravity The text's gravity as provided by {@link Gravity}. + * @return the {@link Builder}. + */ + public Builder setGravity(final int gravity) { + this.gravity = gravity; + + return this; + } + + /** + * Set the heightInPixels option for the {@link Crouton}. + * + * @param height The height of the {@link Crouton} in pixel. Can also be + * {@link LayoutParams#MATCH_PARENT} or + * {@link LayoutParams#WRAP_CONTENT}. + * @return the {@link Builder}. + */ + public Builder setHeight(final int height) { + heightInPixels = height; + + return this; + } + + /** + * Set the resource id for the height option for the {@link Crouton}. + * + * @param heightDimensionResId Resource ID of a dimension for the height + * of the {@link Crouton}. + * @return the {@link Builder}. + */ + public Builder setHeightDimensionResId(final int heightDimensionResId) { + this.heightDimensionResId = heightDimensionResId; + + return this; + } + + /** + * Set the image option for the {@link Crouton}. + * + * @param imageDrawable An additional image to display in the + * {@link Crouton}. + * @return the {@link Builder}. + */ + public Builder setImageDrawable(final Drawable imageDrawable) { + this.imageDrawable = imageDrawable; + + return this; + } + + /** + * Set the image resource option for the {@link Crouton}. + * + * @param imageResId An additional image to display in the + * {@link Crouton}. + * @return the {@link Builder}. + */ + public Builder setImageResource(final int imageResId) { + this.imageResId = imageResId; + + return this; + } + + /** The {@link android.widget.ImageView.ScaleType} for the image. */ + public Builder setImageScaleType(final ImageView.ScaleType imageScaleType) { + this.imageScaleType = imageScaleType; + return this; + } + + /** The resource id for the padding for the crouton view's content. */ + public Builder setPaddingDimensionResId(final int paddingResId) { + paddingDimensionResId = paddingResId; + return this; + } + + /** The padding for the crouton view's content in pixels. */ + public Builder setPaddingInPixels(final int padding) { + paddingInPixels = padding; + return this; + } + + /** The text appearance resource id for the text. */ + public Builder setTextAppearance(final int textAppearanceResId) { + this.textAppearanceResId = textAppearanceResId; + return this; + } + + /** + * Set the textColorResourceId option for the {@link Crouton}. + * + * @param textColor The resource id of the text colorResourceId. + * @return the {@link Builder}. + */ + public Builder setTextColor(final int textColor) { + textColorResourceId = textColor; + + return this; + } + + /** The text shadow color resource id. */ + public Builder setTextShadowColor(final int textShadowColorResId) { + this.textShadowColorResId = textShadowColorResId; + return this; + } + + /** The text shadow horizontal offset. */ + public Builder setTextShadowDx(final float textShadowDx) { + this.textShadowDx = textShadowDx; + return this; + } + + /** The text shadow vertical offset. */ + public Builder setTextShadowDy(final float textShadowDy) { + this.textShadowDy = textShadowDy; + return this; + } + + /** The text shadow radius. */ + public Builder setTextShadowRadius(final float textShadowRadius) { + this.textShadowRadius = textShadowRadius; + return this; + } + + /** The text size in sp. */ + public Builder setTextSize(final int textSize) { + this.textSize = textSize; + return this; + } + + /** + * Set the isTileEnabled option for the {@link Crouton}. + * + * @param isTileEnabled true if you want the + * backgroundResourceId to be tiled, else false. + * @return the {@link Builder}. + */ + public Builder setTileEnabled(final boolean isTileEnabled) { + this.isTileEnabled = isTileEnabled; + + return this; + } + + /** + * Set the widthInPixels option for the {@link Crouton}. + * + * @param width The width of the {@link Crouton} in pixel. Can also be + * {@link LayoutParams#MATCH_PARENT} or + * {@link LayoutParams#WRAP_CONTENT}. + * @return the {@link Builder}. + */ + public Builder setWidth(final int width) { + widthInPixels = width; + + return this; + } + + /** + * Set the resource id for the width option for the {@link Crouton}. + * + * @param widthDimensionResId Resource ID of a dimension for the width + * of the {@link Crouton}. + * @return the {@link Builder}. + */ + public Builder setWidthDimensionResId(final int widthDimensionResId) { + this.widthDimensionResId = widthDimensionResId; + + return this; + } + } +} diff --git a/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/DefaultAnimationsBuilder.java b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/DefaultAnimationsBuilder.java new file mode 100644 index 000000000..cc4082634 --- /dev/null +++ b/twidere/src/main/java/de/keyboardsurfer/android/widget/crouton/DefaultAnimationsBuilder.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012 - 2013 Benjamin Weiss + * Copyright 2012 Neofonie Mobile GmbH + * + * 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 de.keyboardsurfer.android.widget.crouton; + +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.TranslateAnimation; + +/** Builds the default animations for showing and hiding a {@link Crouton}. */ +final class DefaultAnimationsBuilder { + private static final long DURATION = 400; + private static Animation slideInDownAnimation, slideOutUpAnimation; + private static int lastInAnimationHeight, lastOutAnimationHeight; + + private DefaultAnimationsBuilder() { + /* no-op */ + } + + private static boolean areLastMeasuredAnimationHeightAndCurrentEqual(final int lastHeight, final View croutonView) { + return lastHeight == croutonView.getMeasuredHeight(); + } + + private static boolean areLastMeasuredInAnimationHeightAndCurrentEqual(final View croutonView) { + return areLastMeasuredAnimationHeightAndCurrentEqual(lastInAnimationHeight, croutonView); + } + + private static boolean areLastMeasuredOutAnimationHeightAndCurrentEqual(final View croutonView) { + return areLastMeasuredAnimationHeightAndCurrentEqual(lastOutAnimationHeight, croutonView); + } + + private static void setLastInAnimationHeight(final int lastInAnimationHeight) { + DefaultAnimationsBuilder.lastInAnimationHeight = lastInAnimationHeight; + } + + private static void setLastOutAnimationHeight(final int lastOutAnimationHeight) { + DefaultAnimationsBuilder.lastOutAnimationHeight = lastOutAnimationHeight; + } + + /** + * @param croutonView The croutonView which gets animated. + * @return The default Animation for a showing {@link Crouton}. + */ + static Animation buildDefaultSlideInDownAnimation(final View croutonView) { + if (!areLastMeasuredInAnimationHeightAndCurrentEqual(croutonView) || null == slideInDownAnimation) { + slideInDownAnimation = new TranslateAnimation(0, 0, // X: from, to + -croutonView.getMeasuredHeight(), 0 // Y: from, to + ); + slideInDownAnimation.setDuration(DURATION); + setLastInAnimationHeight(croutonView.getMeasuredHeight()); + } + return slideInDownAnimation; + } + + /** + * @param croutonView The croutonView which gets animated. + * @return The default Animation for a hiding {@link Crouton}. + */ + static Animation buildDefaultSlideOutUpAnimation(final View croutonView) { + if (!areLastMeasuredOutAnimationHeightAndCurrentEqual(croutonView) || null == slideOutUpAnimation) { + slideOutUpAnimation = new TranslateAnimation(0, 0, // X: from, to + 0, -croutonView.getMeasuredHeight() // Y: from, to + ); + slideOutUpAnimation.setDuration(DURATION); + setLastOutAnimationHeight(croutonView.getMeasuredHeight()); + } + return slideOutUpAnimation; + } +} diff --git a/twidere/src/main/java/edu/ucdavis/earlybird/CSVFileFilter.java b/twidere/src/main/java/edu/ucdavis/earlybird/CSVFileFilter.java new file mode 100644 index 000000000..a7800c1f4 --- /dev/null +++ b/twidere/src/main/java/edu/ucdavis/earlybird/CSVFileFilter.java @@ -0,0 +1,19 @@ +package edu.ucdavis.earlybird; + +import java.io.File; +import java.io.FileFilter; + +public final class CSVFileFilter implements FileFilter { + + @Override + public boolean accept(final File file) { + return file.isFile() && "csv".equalsIgnoreCase(getExtension(file)); + } + + static String getExtension(final File file) { + final String name = file.getName(); + final int pos = name.lastIndexOf('.'); + if (pos == -1) return null; + return name.substring(pos + 1); + } +} diff --git a/twidere/src/main/java/edu/ucdavis/earlybird/ProfilingUtil.java b/twidere/src/main/java/edu/ucdavis/earlybird/ProfilingUtil.java new file mode 100644 index 000000000..a3014326a --- /dev/null +++ b/twidere/src/main/java/edu/ucdavis/earlybird/ProfilingUtil.java @@ -0,0 +1,70 @@ +package edu.ucdavis.earlybird; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.BatteryManager; +import android.util.Log; + +import org.mariotaku.twidere.Constants; +import org.mariotaku.twidere.util.Utils; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; + +public class ProfilingUtil { + + public static final String FILE_NAME_PROFILE = "Profile"; + public static final String FILE_NAME_LOCATION = "Location"; + public static final String FILE_NAME_APP = "App"; + public static final String FILE_NAME_WIFI = "Wifi"; + + public static boolean isCharging(final Context context) { + if (context == null) return false; + final Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + if (intent == null) return false; + final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB; + } + + public static boolean log(final Context context, final String msg) { + if (Utils.isDebuggable(context)) { + final StackTraceElement ste = new Throwable().fillInStackTrace().getStackTrace()[1]; + final String fullname = ste.getClassName(); + final String name = fullname.substring(fullname.lastIndexOf('.')); + final String tag = name + "." + ste.getMethodName(); + Log.d(tag, msg); + return true; + } else + return false; + } + + public static void profile(final Context context, final long accountID, final String text) { + profile(context, accountID + "_" + FILE_NAME_PROFILE, text); + } + + public static void profile(final Context context, final String name, final String text) { + if (context == null) return; + final SharedPreferences prefs = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, + Context.MODE_PRIVATE); + if (!prefs.getBoolean(Constants.KEY_UCD_DATA_PROFILING, false)) return; + final String filename = name + ".csv"; + new Thread() { + @Override + public void run() { + try { + final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_APPEND); + if (fos == null) return; + final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos)); + bw.write("[" + System.currentTimeMillis() + "], " + text + "\n"); + bw.flush(); + fos.close(); + } catch (final Exception e) { + e.printStackTrace(); + } + }; + }.start(); + } +} diff --git a/twidere/src/main/java/edu/ucdavis/earlybird/UCDService.java b/twidere/src/main/java/edu/ucdavis/earlybird/UCDService.java new file mode 100644 index 000000000..478003dde --- /dev/null +++ b/twidere/src/main/java/edu/ucdavis/earlybird/UCDService.java @@ -0,0 +1,84 @@ +package edu.ucdavis.earlybird; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.location.Location; +import android.location.LocationManager; +import android.os.IBinder; + +/** + * Request location ONCE per WAKE_PERIOD_IN_MILLI. + */ +public class UCDService extends Service { + + public static final long LOCATION_PERIOD_IN_MILLI = 15 * 60 * 1000; + public static final String ACTION_GET_LOCATION = "edu.ucdavis.earlybird.GET_LOCATION"; + private LocationManager mLocationManager; + private AlarmManager mAlarmManager; + private LocationUpdateReceiver mAlarmReceiver; + private PendingIntent locationIntent; + private PendingIntent uploadIntent; + + @Override + public IBinder onBind(final Intent intent) { + throw new IllegalStateException("Not implemented."); + } + + @Override + public void onCreate() { + super.onCreate(); + + ProfilingUtil.log(this, "onCreate"); + mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + mAlarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE); + + mAlarmReceiver = new LocationUpdateReceiver(); + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_GET_LOCATION); + registerReceiver(mAlarmReceiver, filter); + + final Intent intent = new Intent(ACTION_GET_LOCATION); + locationIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), LOCATION_PERIOD_IN_MILLI, + locationIntent); + + // Upload Service + final Intent i = new Intent(UploadReceiver.ACTION_UPLOAD_PROFILE); + uploadIntent = PendingIntent.getBroadcast(this, 0, i, 0); + mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 12 * 60 * 60 * 1000, + uploadIntent); + } + + @Override + public void onDestroy() { + mAlarmManager.cancel(locationIntent); + unregisterReceiver(mAlarmReceiver); + super.onDestroy(); + } + + private final class LocationUpdateReceiver extends BroadcastReceiver { + + @Override + public void onReceive(final Context context, final Intent intent) { + if (mLocationManager == null) return; + ProfilingUtil.log(context, "AlarmReceiver"); + final String provider = LocationManager.NETWORK_PROVIDER; + if (mLocationManager.isProviderEnabled(provider)) { + final Location location = mLocationManager.getLastKnownLocation(provider); + if (location != null) { + ProfilingUtil.profile(UCDService.this, ProfilingUtil.FILE_NAME_LOCATION, location.getTime() + "," + + location.getLatitude() + "," + location.getLongitude() + "," + location.getProvider()); + ProfilingUtil.log(context, + location.getTime() + "," + location.getLatitude() + "," + location.getLongitude() + "," + + location.getProvider()); + } + } + } + } + +} diff --git a/twidere/src/main/java/edu/ucdavis/earlybird/UploadReceiver.java b/twidere/src/main/java/edu/ucdavis/earlybird/UploadReceiver.java new file mode 100644 index 000000000..be3077cd6 --- /dev/null +++ b/twidere/src/main/java/edu/ucdavis/earlybird/UploadReceiver.java @@ -0,0 +1,27 @@ +package edu.ucdavis.earlybird; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.wifi.WifiManager; + +import org.mariotaku.twidere.util.Utils; + +public class UploadReceiver extends BroadcastReceiver { + + public static final String ACTION_UPLOAD_PROFILE = "edu.ucdavis.earlybird.UPLOAD_PROFILE"; + + @Override + public void onReceive(final Context context, final Intent intent) { + final String action = intent.getAction(); + final boolean isWifi = Utils.isOnWifi(context.getApplicationContext()); + final boolean isCharging = ProfilingUtil.isCharging(context.getApplicationContext()); + if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) { + final boolean wifi = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false); + ProfilingUtil.profile(context, ProfilingUtil.FILE_NAME_WIFI, wifi ? "connected" : "disconnected"); + } + if (isWifi && isCharging) { + new UploadTask(context).execute(); + } + } +} diff --git a/twidere/src/main/java/edu/ucdavis/earlybird/UploadTask.java b/twidere/src/main/java/edu/ucdavis/earlybird/UploadTask.java new file mode 100644 index 000000000..4aca398f2 --- /dev/null +++ b/twidere/src/main/java/edu/ucdavis/earlybird/UploadTask.java @@ -0,0 +1,136 @@ +package edu.ucdavis.earlybird; + +import static org.mariotaku.twidere.util.Utils.copyStream; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.provider.Settings.Secure; + +import twitter4j.TwitterException; +import twitter4j.http.HttpClientWrapper; +import twitter4j.http.HttpParameter; +import twitter4j.http.HttpResponse; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class UploadTask extends AsyncTask { + + private static final String LAST_UPLOAD_DATE = "last_upload_time"; + private static final double MILLSECS_HALF_DAY = 1000 * 60 * 60 * 12; + + private final String device_id; + private final Context context; + + private final HttpClientWrapper client = new HttpClientWrapper(); + + private static final String PROFILE_SERVER_URL = "http://weik.metaisle.com/profiles"; + + // private static final String PROFILE_SERVER_URL = + // "http://192.168.0.105:3000/profiles"; + + public UploadTask(final Context context) { + this.context = context; + device_id = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); + } + + public void uploadMultipart(final String url, final File file) { + final String app_root = file.getParent(); + final File tmp_dir = new File(app_root + "/tmp"); + if (!tmp_dir.exists()) { + if (!tmp_dir.mkdirs()) { + ProfilingUtil.log(context, "cannot create tmp, do nothing."); + return; + } + } + final File tmp = new File(tmp_dir, file.getName()); + file.renameTo(tmp); + + try { + final HttpParameter param = new HttpParameter("upload", tmp); + final HttpResponse resp = client.post(url, null, new HttpParameter[] { param }); + + // Responses from the server (code and message) + final int serverResponseCode = resp.getStatusCode(); + + ProfilingUtil.log(context, "server response code " + serverResponseCode); + + if (serverResponseCode / 100 == 2) { + tmp.delete(); + } else { + putBackProfile(context, tmp, file); + } + + } catch (final TwitterException e) { + e.printStackTrace(); + putBackProfile(context, tmp, file); + } + } + + @Override + protected Void doInBackground(final Void... params) { + + final SharedPreferences prefs = context.getSharedPreferences("ucd_data_profiling", Context.MODE_PRIVATE); + + if (prefs.contains(LAST_UPLOAD_DATE)) { + final long lastUpload = prefs.getLong(LAST_UPLOAD_DATE, System.currentTimeMillis()); + final double deltaDays = (System.currentTimeMillis() - lastUpload) / (MILLSECS_HALF_DAY * 2); + if (deltaDays < 1) { + ProfilingUtil.log(context, "Uploaded less than 1 day ago."); + return null; + } + } + + final File root = context.getFilesDir(); + final File[] files = root.listFiles(new CSVFileFilter()); + + uploadToNode(files); + prefs.edit().putLong(LAST_UPLOAD_DATE, System.currentTimeMillis()).commit(); + return null; + } + + private boolean uploadToNode(final File... files) { + for (final File file : files) { + if (file.isDirectory()) { + continue; + } + final String url = PROFILE_SERVER_URL + "/" + device_id + "/" + + file.getName().replaceFirst("[.][^.]+$", ""); + ProfilingUtil.log(context, url); + uploadMultipart(url, file); + } + return false; + } + + public static void putBackProfile(final Context context, final File tmp, final File profile) { + boolean success; + if (profile.exists()) { + try { + final FileOutputStream os = new FileOutputStream(tmp, true); + final FileInputStream is = new FileInputStream(profile); + copyStream(is, os); + is.close(); + os.close(); + } catch (final IOException e) { + e.printStackTrace(); + success = false; + } + success = true; + + if (success && tmp.renameTo(profile) && tmp.delete()) { + ProfilingUtil.log(context, "put profile back success"); + } else { + ProfilingUtil.log(context, "put profile back failed"); + } + } else { + if (tmp.renameTo(profile)) { + ProfilingUtil.log(context, "put profile back success"); + } else { + ProfilingUtil.log(context, "put profile back failed"); + } + } + } +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java new file mode 100644 index 000000000..7a6387047 --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouch.java @@ -0,0 +1,249 @@ +package it.sephiroth.android.library.imagezoom; + +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import it.sephiroth.android.library.imagezoom.ScaleGestureDetector.OnScaleGestureListener; + +import org.mariotaku.twidere.BuildConfig; +import org.mariotaku.twidere.Constants; + +public class ImageViewTouch extends ImageViewTouchBase implements Constants { + + private static final float SCROLL_DELTA_THRESHOLD = 1.0f; + static final float MIN_ZOOM = 0.9f; + protected int mTouchSlop; + protected float mCurrentScaleFactor; + protected float mScaleFactor; + protected int mDoubleTapDirection; + protected GestureDetector mGestureDetector; + protected OnGestureListener mGestureListener; + protected OnScaleGestureListener mScaleListener; + protected ScaleGestureDetector mScaleDetector; + protected boolean mDoubleTapToZoomEnabled = true; + protected boolean mScaleEnabled = true; + protected boolean mScrollEnabled = true; + + private OnImageViewTouchDoubleTapListener doubleTapListener; + + public ImageViewTouch(final Context context, final AttributeSet attrs) { + super(context, attrs); + } + + /** + * Determines whether this ImageViewTouch can be scrolled. + * + * @param direction - positive direction value means scroll from right to + * left, negative value means scroll from left to right + * @return true if there is some more place to scroll, false - otherwise. + */ + public boolean canScroll(final int direction) { + final RectF bitmapRect = getBitmapRect(); + updateRect(bitmapRect, mScrollRect); + final Rect imageViewRect = new Rect(); + getGlobalVisibleRect(imageViewRect); + + if (bitmapRect.right >= imageViewRect.right) { + if (direction < 0) return Math.abs(bitmapRect.right - imageViewRect.right) > SCROLL_DELTA_THRESHOLD; + } + + final double bitmapScrollRectDelta = Math.abs(bitmapRect.left - mScrollRect.left); + return bitmapScrollRectDelta > SCROLL_DELTA_THRESHOLD; + } + + public boolean getDoubleTapEnabled() { + return mDoubleTapToZoomEnabled; + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + if (mScaleDetector != null) { + mScaleDetector.onTouchEvent(event); + } + if (mScaleDetector != null && !mScaleDetector.isInProgress()) { + mGestureDetector.onTouchEvent(event); + } + final int action = event.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_UP: + if (getScale() < 1f) { + zoomTo(1f, 50); + } + break; + } + return true; + } + + public void setDoubleTapListener(final OnImageViewTouchDoubleTapListener doubleTapListener) { + this.doubleTapListener = doubleTapListener; + } + + public void setDoubleTapToZoomEnabled(final boolean value) { + mDoubleTapToZoomEnabled = value; + } + + public void setScaleEnabled(final boolean value) { + mScaleEnabled = value; + } + + public void setScrollEnabled(final boolean value) { + mScrollEnabled = value; + } + + @Override + protected void _setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix, + final float maxZoom) { + super._setImageDrawable(drawable, reset, initial_matrix, maxZoom); + mScaleFactor = getMaxZoom() / 3; + } + + protected OnGestureListener getGestureListener() { + return new GestureListener(); + } + + protected OnScaleGestureListener getScaleListener() { + return new ScaleListener(); + } + + @Override + protected void init() { + super.init(); + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mGestureListener = getGestureListener(); + mGestureDetector = new GestureDetector(getContext(), mGestureListener); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR) { + mScaleListener = getScaleListener(); + mScaleDetector = new ScaleGestureDetector(getContext(), mScaleListener); + } + mCurrentScaleFactor = 1f; + mDoubleTapDirection = 1; + } + + @Override + protected void onBitmapChanged(final Drawable drawable) { + super.onBitmapChanged(drawable); + + final float v[] = new float[9]; + mSuppMatrix.getValues(v); + mCurrentScaleFactor = v[Matrix.MSCALE_X]; + } + + protected float onDoubleTapPost(final float scale, final float maxZoom) { + if (mDoubleTapDirection == 1) { + if (scale + mScaleFactor * 2 <= maxZoom) + return scale + mScaleFactor; + else { + mDoubleTapDirection = -1; + return maxZoom; + } + } else { + mDoubleTapDirection = 1; + return 1f; + } + } + + @Override + protected void onZoom(final float scale) { + super.onZoom(scale); + if (mScaleDetector != null && !mScaleDetector.isInProgress()) { + mCurrentScaleFactor = scale; + } + } + + public class GestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onDoubleTap(final MotionEvent e) { + if (BuildConfig.DEBUG) { + Log.i(LOG_TAG, "onDoubleTap. double tap enabled? " + mDoubleTapToZoomEnabled); + } + if (mDoubleTapToZoomEnabled) { + final float scale = getScale(); + float targetScale = scale; + targetScale = onDoubleTapPost(scale, getMaxZoom()); + targetScale = Math.min(getMaxZoom(), Math.max(targetScale, MIN_ZOOM)); + mCurrentScaleFactor = targetScale; + zoomTo(targetScale, e.getX(), e.getY(), 200); + invalidate(); + } + + if (null != doubleTapListener) { + doubleTapListener.onDoubleTap(); + } + + return super.onDoubleTap(e); + } + + @Override + public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { + if (!mScrollEnabled) return false; + + // if (e1.getPointerCount() > 1 || e2.getPointerCount() > 1) return + // false; + if (mScaleDetector != null && mScaleDetector.isInProgress()) return false; + + final float diffX = e2.getX() - e1.getX(); + final float diffY = e2.getY() - e1.getY(); + + if (Math.abs(velocityX) > 800 || Math.abs(velocityY) > 800) { + scrollBy(diffX / 2, diffY / 2, 300); + invalidate(); + } + return super.onFling(e1, e2, velocityX, velocityY); + } + + @Override + public void onLongPress(final MotionEvent e) { + if (isLongClickable()) { + if (mScaleDetector != null && !mScaleDetector.isInProgress()) { + setPressed(true); + performLongClick(); + } + } + } + + @Override + public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) { + if (!mScrollEnabled) return false; + if (e1 == null || e2 == null) return false; + // if (e1.getPointerCount() > 1 || e2.getPointerCount() > 1) return + // false; + if (mScaleDetector != null && mScaleDetector.isInProgress()) return false; + if (getScale() == 1f) return false; + scrollBy(-distanceX, -distanceY); + invalidate(); + return super.onScroll(e1, e2, distanceX, distanceY); + } + } + + public interface OnImageViewTouchDoubleTapListener { + void onDoubleTap(); + } + + public class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { + + @Override + public boolean onScale(final ScaleGestureDetector detector) { + float targetScale = mCurrentScaleFactor * detector.getScaleFactor(); + if (mScaleEnabled) { + targetScale = Math.min(getMaxZoom(), Math.max(targetScale, MIN_ZOOM)); + zoomTo(targetScale, detector.getFocusX(), detector.getFocusY()); + mCurrentScaleFactor = Math.min(getMaxZoom(), Math.max(targetScale, MIN_ZOOM)); + mDoubleTapDirection = 1; + invalidate(); + return true; + } + return false; + } + } +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java new file mode 100644 index 000000000..d705c9b3c --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ImageViewTouchBase.java @@ -0,0 +1,504 @@ +package it.sephiroth.android.library.imagezoom; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.ImageView; + +import it.sephiroth.android.library.imagezoom.easing.Cubic; +import it.sephiroth.android.library.imagezoom.easing.Easing; +import it.sephiroth.android.library.imagezoom.graphics.FastBitmapDrawable; +import it.sephiroth.android.library.imagezoom.utils.IDisposable; + +/** + * Base View to manage image zoom/scrool/pinch operations + * + * @author alessandro + */ +public class ImageViewTouchBase extends ImageView implements IDisposable { + + public static final String LOG_TAG = "image"; + + protected Easing mEasing = new Cubic(); + + protected Matrix mBaseMatrix = new Matrix(); + protected Matrix mSuppMatrix = new Matrix(); + protected Handler mHandler = new Handler(); + protected Runnable mOnLayoutRunnable = null; + protected float mMaxZoom; + protected final Matrix mDisplayMatrix = new Matrix(); + protected final float[] mMatrixValues = new float[9]; + protected int mThisWidth = -1, mThisHeight = -1; + protected boolean mFitToScreen = false; + final protected float MAX_ZOOM = 2.0f; + protected RectF mBitmapRect = new RectF(); + + protected RectF mCenterRect = new RectF(); + protected RectF mScrollRect = new RectF(); + private OnBitmapChangedListener mListener; + + public ImageViewTouchBase(final Context context) { + super(context); + init(); + } + + public ImageViewTouchBase(final Context context, final AttributeSet attrs) { + super(context, attrs); + init(); + } + + public void clear() { + setImageBitmap(null, true); + } + + @Override + public void dispose() { + clear(); + } + + /** + * Returns the current image display matrix. This matrix can be used in the + * next call to the {@link #setImageBitmap(Bitmap, boolean, Matrix)} to + * restore the same view state of the previous {@link Bitmap} + * + * @return + */ + public Matrix getDisplayMatrix() { + return new Matrix(mSuppMatrix); + } + + public Matrix getImageViewMatrix() { + mDisplayMatrix.set(mBaseMatrix); + mDisplayMatrix.postConcat(mSuppMatrix); + return mDisplayMatrix; + } + + public float getMaxZoom() { + return mMaxZoom; + } + + @Override + public float getRotation() { + return 0; + } + + public float getScale() { + return getScale(mSuppMatrix); + } + + public void scrollBy(final float x, final float y) { + panBy(x, y); + } + + public void setFitToScreen(final boolean value) { + if (value != mFitToScreen) { + mFitToScreen = value; + requestLayout(); + } + } + + @Override + public void setImageBitmap(final Bitmap bm) { + setImageBitmap(bm, true); + } + + /** + * Set the new image to display and reset the internal matrix. + * + * @param bitmap - the {@link Bitmap} to display + * @param reset - if true the image bounds will be recreated, otherwise the + * old {@link Matrix} is used to display the new bitmap + * @see #setImageBitmap(Bitmap) + */ + public void setImageBitmap(final Bitmap bitmap, final boolean reset) { + setImageBitmap(bitmap, reset, null); + } + + /** + * Similar to {@link #setImageBitmap(Bitmap, boolean)} but an optional view + * {@link Matrix} can be passed to determine the new bitmap view matrix.
+ * This method is useful if you need to restore a Bitmap with the same + * zoom/pan values from a previous state + * + * @param bitmap - the {@link Bitmap} to display + * @param reset + * @param matrix - the {@link Matrix} to be used to display the new bitmap + * @see #setImageBitmap(Bitmap, boolean) + * @see #setImageBitmap(Bitmap) + * @see #getImageViewMatrix() + * @see #getDisplayMatrix() + */ + public void setImageBitmap(final Bitmap bitmap, final boolean reset, final Matrix matrix) { + setImageBitmap(bitmap, reset, matrix, -1); + } + + /** + * @param bitmap + * @param reset + * @param matrix + * @param maxZoom - maximum zoom allowd during zoom gestures + * @see #setImageBitmap(Bitmap, boolean, Matrix) + */ + public void setImageBitmap(final Bitmap bitmap, final boolean reset, final Matrix matrix, final float maxZoom) { + + Log.i(LOG_TAG, "setImageBitmap: " + bitmap); + + if (bitmap != null) { + setImageDrawable(new FastBitmapDrawable(bitmap), reset, matrix, maxZoom); + } else { + setImageDrawable(null, reset, matrix, maxZoom); + } + } + + @Override + public void setImageDrawable(final Drawable drawable) { + setImageDrawable(drawable, true, null, -1); + } + + public void setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix, + final float maxZoom) { + + final int viewWidth = getWidth(); + + if (viewWidth <= 0) { + mOnLayoutRunnable = new Runnable() { + + @Override + public void run() { + setImageDrawable(drawable, reset, initial_matrix, maxZoom); + } + }; + return; + } + + _setImageDrawable(drawable, reset, initial_matrix, maxZoom); + } + + @Override + public void setImageResource(final int resId) { + setImageDrawable(getContext().getResources().getDrawable(resId)); + } + + public void setOnBitmapChangedListener(final OnBitmapChangedListener listener) { + mListener = listener; + } + + public void zoomTo(final float scale, final float durationMs) { + final float cx = getWidth() / 2F; + final float cy = getHeight() / 2F; + zoomTo(scale, cx, cy, durationMs); + } + + protected void _setImageDrawable(final Drawable drawable, final boolean reset, final Matrix initial_matrix, + final float maxZoom) { + + if (drawable != null) { + if (mFitToScreen) { + getProperBaseMatrix2(drawable, mBaseMatrix); + } else { + getProperBaseMatrix(drawable, mBaseMatrix); + } + super.setImageDrawable(drawable); + } else { + mBaseMatrix.reset(); + super.setImageDrawable(null); + } + + if (reset) { + mSuppMatrix.reset(); + if (initial_matrix != null) { + mSuppMatrix = new Matrix(initial_matrix); + } + } + + setImageMatrix(getImageViewMatrix()); + + if (maxZoom < 1) { + mMaxZoom = maxZoom(); + } else { + mMaxZoom = maxZoom; + } + + onBitmapChanged(drawable); + } + + protected void center(final boolean horizontal, final boolean vertical) { + // Log.i(LOG_TAG, "center"); + final Drawable drawable = getDrawable(); + + if (drawable == null) return; + final RectF rect = getCenter(horizontal, vertical); + if (rect.left != 0 || rect.top != 0) { + postTranslate(rect.left, rect.top); + } + } + + protected RectF getBitmapRect() { + final Drawable drawable = getDrawable(); + + if (drawable == null) return null; + final Matrix m = getImageViewMatrix(); + mBitmapRect.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + m.mapRect(mBitmapRect); + return mBitmapRect; + } + + protected RectF getCenter(final boolean horizontal, final boolean vertical) { + final Drawable drawable = getDrawable(); + + if (drawable == null) return new RectF(0, 0, 0, 0); + + final RectF rect = getBitmapRect(); + final float height = rect.height(); + final float width = rect.width(); + float deltaX = 0, deltaY = 0; + if (vertical) { + final int viewHeight = getHeight(); + if (height < viewHeight) { + deltaY = (viewHeight - height) / 2 - rect.top; + } else if (rect.top > 0) { + deltaY = -rect.top; + } else if (rect.bottom < viewHeight) { + deltaY = getHeight() - rect.bottom; + } + } + if (horizontal) { + final int viewWidth = getWidth(); + if (width < viewWidth) { + deltaX = (viewWidth - width) / 2 - rect.left; + } else if (rect.left > 0) { + deltaX = -rect.left; + } else if (rect.right < viewWidth) { + deltaX = viewWidth - rect.right; + } + } + mCenterRect.set(deltaX, deltaY, 0, 0); + return mCenterRect; + } + + /** + * Setup the base matrix so that the image is centered and scaled properly. + * + * @param bitmap + * @param matrix + */ + protected void getProperBaseMatrix(final Drawable drawable, final Matrix matrix) { + final float viewWidth = getWidth(); + final float viewHeight = getHeight(); + final float w = drawable.getIntrinsicWidth(); + final float h = drawable.getIntrinsicHeight(); + matrix.reset(); + + if (w > viewWidth || h > viewHeight) { + final float widthScale = Math.min(viewWidth / w, 2.0f); + final float heightScale = Math.min(viewHeight / h, 2.0f); + final float scale = Math.min(widthScale, heightScale); + matrix.postScale(scale, scale); + final float tw = (viewWidth - w * scale) / 2.0f; + final float th = (viewHeight - h * scale) / 2.0f; + matrix.postTranslate(tw, th); + } else { + final float tw = (viewWidth - w) / 2.0f; + final float th = (viewHeight - h) / 2.0f; + matrix.postTranslate(tw, th); + } + } + + /** + * Setup the base matrix so that the image is centered and scaled properly. + * + * @param bitmap + * @param matrix + */ + protected void getProperBaseMatrix2(final Drawable bitmap, final Matrix matrix) { + final float viewWidth = getWidth(); + final float viewHeight = getHeight(); + final float w = bitmap.getIntrinsicWidth(); + final float h = bitmap.getIntrinsicHeight(); + matrix.reset(); + final float widthScale = Math.min(viewWidth / w, MAX_ZOOM); + final float heightScale = Math.min(viewHeight / h, MAX_ZOOM); + final float scale = Math.min(widthScale, heightScale); + matrix.postScale(scale, scale); + matrix.postTranslate((viewWidth - w * scale) / MAX_ZOOM, (viewHeight - h * scale) / MAX_ZOOM); + } + + protected float getScale(final Matrix matrix) { + return getValue(matrix, Matrix.MSCALE_X); + } + + protected float getValue(final Matrix matrix, final int whichValue) { + matrix.getValues(mMatrixValues); + return mMatrixValues[whichValue]; + } + + protected void init() { + setScaleType(ImageView.ScaleType.MATRIX); + } + + protected float maxZoom() { + final Drawable drawable = getDrawable(); + + if (drawable == null) return 1F; + + final float fw = (float) drawable.getIntrinsicWidth() / (float) mThisWidth; + final float fh = (float) drawable.getIntrinsicHeight() / (float) mThisHeight; + final float max = Math.max(fw, fh) * 4; + return max; + } + + protected void onBitmapChanged(final Drawable bitmap) { + if (mListener != null) { + mListener.onBitmapChanged(bitmap); + } + } + + @Override + protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { + super.onLayout(changed, left, top, right, bottom); + mThisWidth = right - left; + mThisHeight = bottom - top; + final Runnable r = mOnLayoutRunnable; + if (r != null) { + mOnLayoutRunnable = null; + r.run(); + } + if (getDrawable() != null) { + if (mFitToScreen) { + getProperBaseMatrix2(getDrawable(), mBaseMatrix); + } else { + getProperBaseMatrix(getDrawable(), mBaseMatrix); + } + setImageMatrix(getImageViewMatrix()); + } + } + + protected void onZoom(final float scale) { + } + + protected void panBy(final double dx, final double dy) { + final RectF rect = getBitmapRect(); + mScrollRect.set((float) dx, (float) dy, 0, 0); + updateRect(rect, mScrollRect); + postTranslate(mScrollRect.left, mScrollRect.top); + center(true, true); + } + + protected void postScale(final float scale, final float centerX, final float centerY) { + mSuppMatrix.postScale(scale, scale, centerX, centerY); + setImageMatrix(getImageViewMatrix()); + } + + protected void postTranslate(final float deltaX, final float deltaY) { + mSuppMatrix.postTranslate(deltaX, deltaY); + setImageMatrix(getImageViewMatrix()); + } + + protected void scrollBy(final float distanceX, final float distanceY, final double durationMs) { + final double dx = distanceX; + final double dy = distanceY; + final long startTime = System.currentTimeMillis(); + mHandler.post(new Runnable() { + + double old_x = 0; + double old_y = 0; + + @Override + public void run() { + final long now = System.currentTimeMillis(); + final double currentMs = Math.min(durationMs, now - startTime); + final double x = mEasing.easeOut(currentMs, 0, dx, durationMs); + final double y = mEasing.easeOut(currentMs, 0, dy, durationMs); + panBy(x - old_x, y - old_y); + old_x = x; + old_y = y; + if (currentMs < durationMs) { + mHandler.post(this); + } else { + final RectF centerRect = getCenter(true, true); + if (centerRect.left != 0 || centerRect.top != 0) { + scrollBy(centerRect.left, centerRect.top); + } + } + } + }); + } + + protected void updateRect(final RectF bitmapRect, final RectF scrollRect) { + if (bitmapRect == null || scrollRect == null) return; + final float width = getWidth(); + final float height = getHeight(); + + if (bitmapRect.top >= 0 && bitmapRect.bottom <= height) { + scrollRect.top = 0; + } + if (bitmapRect.left >= 0 && bitmapRect.right <= width) { + scrollRect.left = 0; + } + if (bitmapRect.top + scrollRect.top >= 0 && bitmapRect.bottom > height) { + scrollRect.top = (int) (0 - bitmapRect.top); + } + if (bitmapRect.bottom + scrollRect.top <= height - 0 && bitmapRect.top < 0) { + scrollRect.top = (int) (height - 0 - bitmapRect.bottom); + } + if (bitmapRect.left + scrollRect.left >= 0) { + scrollRect.left = (int) (0 - bitmapRect.left); + } + if (bitmapRect.right + scrollRect.left <= width - 0) { + scrollRect.left = (int) (width - 0 - bitmapRect.right); + // Log.d( LOG_TAG, "scrollRect(2): " + scrollRect.toString() ); + } + } + + protected void zoomTo(final float scale) { + final float cx = getWidth() / 2F; + final float cy = getHeight() / 2F; + zoomTo(scale, cx, cy); + } + + protected void zoomTo(float scale, final float centerX, final float centerY) { + // Log.i(LOG_TAG, "zoomTo"); + + if (scale > mMaxZoom) { + scale = mMaxZoom; + } + final float oldScale = getScale(); + final float deltaScale = scale / oldScale; + postScale(deltaScale, centerX, centerY); + onZoom(getScale()); + center(true, true); + } + + protected void zoomTo(final float scale, final float centerX, final float centerY, final float durationMs) { + // Log.i( LOG_TAG, "zoomTo: " + scale + ", " + centerX + ": " + centerY + // ); + final long startTime = System.currentTimeMillis(); + final float incrementPerMs = (scale - getScale()) / durationMs; + final float oldScale = getScale(); + mHandler.post(new Runnable() { + + @Override + public void run() { + final long now = System.currentTimeMillis(); + final float currentMs = Math.min(durationMs, now - startTime); + final float target = oldScale + incrementPerMs * currentMs; + zoomTo(target, centerX, centerY); + if (currentMs < durationMs) { + mHandler.post(this); + } else { + // if ( getScale() < 1f ) {} + } + } + }); + } + + public interface OnBitmapChangedListener { + + void onBitmapChanged(Drawable drawable); + } +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ScaleGestureDetector.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ScaleGestureDetector.java new file mode 100644 index 000000000..817266f2b --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/ScaleGestureDetector.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2010 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 it.sephiroth.android.library.imagezoom; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v4.view.MotionEventCompat; +import android.util.DisplayMetrics; +import android.util.FloatMath; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +/** + * Detects transformation gestures involving more than one pointer + * ("multitouch") using the supplied {@link MotionEvent}s. The + * {@link OnScaleGestureListener} callback will notify users when a particular + * gesture event has occurred. This class should only be used with + * {@link MotionEvent}s reported via touch. To use this class: + *

    + *
  • Create an instance of the {@code ScaleGestureDetector} for your + * {@link View} + *
  • In the {@link View#onTouchEvent(MotionEvent)} method ensure you call + * {@link #onTouchEvent(MotionEvent)}. The methods defined in your callback will + * be executed when the events occur. + *
+ */ +@TargetApi(Build.VERSION_CODES.ECLAIR) +public class ScaleGestureDetector { + /** + * This value is the threshold ratio between our previous combined pressure + * and the current combined pressure. We will only fire an onScale event if + * the computed ratio between the current and previous event pressures is + * greater than this value. When pressure decreases rapidly between events + * the position values can often be imprecise, as it usually indicates that + * the user is in the process of lifting a pointer off of the device. Its + * value was tuned experimentally. + */ + private static final float PRESSURE_THRESHOLD = 0.67f; + + private final Context mContext; + + private final OnScaleGestureListener mListener; + + private boolean mGestureInProgress; + private MotionEvent mPrevEvent; + private MotionEvent mCurrEvent; + + private float mFocusX; + private float mFocusY; + + private float mPrevFingerDiffX; + private float mPrevFingerDiffY; + private float mCurrFingerDiffX; + private float mCurrFingerDiffY; + private float mCurrLen; + private float mPrevLen; + private float mScaleFactor; + private float mCurrPressure; + private float mPrevPressure; + private long mTimeDelta; + private final float mEdgeSlop; + private float mRightSlopEdge; + + private float mBottomSlopEdge; + private boolean mSloppyGesture; + + public ScaleGestureDetector(final Context context, final OnScaleGestureListener listener) { + final ViewConfiguration config = ViewConfiguration.get(context); + mContext = context; + mListener = listener; + mEdgeSlop = config.getScaledEdgeSlop(); + } + + /** + * Return the current distance between the two pointers forming the gesture + * in progress. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpan() { + if (mCurrLen == -1) { + final float cvx = mCurrFingerDiffX; + final float cvy = mCurrFingerDiffY; + mCurrLen = FloatMath.sqrt(cvx * cvx + cvy * cvy); + } + return mCurrLen; + } + + /** + * Return the event time of the current event being processed. + * + * @return Current event time in milliseconds. + */ + public long getEventTime() { + return mCurrEvent.getEventTime(); + } + + /** + * Get the X coordinate of the current gesture's focal point. If a gesture + * is in progress, the focal point is directly between the two pointers + * forming the gesture. If a gesture is ending, the focal point is the + * location of the remaining pointer on the screen. If + * {@link #isInProgress()} would return false, the result of this function + * is undefined. + * + * @return X coordinate of the focal point in pixels. + */ + public float getFocusX() { + return mFocusX; + } + + /** + * Get the Y coordinate of the current gesture's focal point. If a gesture + * is in progress, the focal point is directly between the two pointers + * forming the gesture. If a gesture is ending, the focal point is the + * location of the remaining pointer on the screen. If + * {@link #isInProgress()} would return false, the result of this function + * is undefined. + * + * @return Y coordinate of the focal point in pixels. + */ + public float getFocusY() { + return mFocusY; + } + + /** + * Return the previous distance between the two pointers forming the gesture + * in progress. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpan() { + if (mPrevLen == -1) { + final float pvx = mPrevFingerDiffX; + final float pvy = mPrevFingerDiffY; + mPrevLen = FloatMath.sqrt(pvx * pvx + pvy * pvy); + } + return mPrevLen; + } + + /** + * Return the scaling factor from the previous scale event to the current + * event. This value is defined as ({@link #getCurrentSpan()} / + * {@link #getPreviousSpan()}). + * + * @return The current scaling factor. + */ + public float getScaleFactor() { + if (mScaleFactor == -1) { + mScaleFactor = getCurrentSpan() / getPreviousSpan(); + } + return mScaleFactor; + } + + /** + * Return the time difference in milliseconds between the previous accepted + * scaling event and the current scaling event. + * + * @return Time difference since the last scaling event in milliseconds. + */ + public long getTimeDelta() { + return mTimeDelta; + } + + /** + * Returns {@code true} if a two-finger scale gesture is in progress. + * + * @return {@code true} if a scale gesture is in progress, {@code false} + * otherwise. + */ + public boolean isInProgress() { + return mGestureInProgress; + } + + public boolean onTouchEvent(final MotionEvent event) { + final int action = event.getAction(); + final boolean handled = true; + + if (!mGestureInProgress) { + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_POINTER_DOWN: { + // We have a new multi-finger gesture + + // as orientation can change, query the metrics in touch + // down + final DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); + mRightSlopEdge = metrics.widthPixels - mEdgeSlop; + mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; + + // Be paranoid in case we missed an event + reset(); + + mPrevEvent = MotionEvent.obtain(event); + mTimeDelta = 0; + + setContext(event); + + // Check if we have a sloppy gesture. If so, delay + // the beginning of the gesture until we're sure that's + // what the user wanted. Sloppy gestures can happen if the + // edge of the user's hand is touching the screen, for + // example. + final float edgeSlop = mEdgeSlop; + final float rightSlop = mRightSlopEdge; + final float bottomSlop = mBottomSlopEdge; + final float x0 = event.getRawX(); + final float y0 = event.getRawY(); + final float x1 = getRawX(event, 1); + final float y1 = getRawY(event, 1); + + final boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > rightSlop || y0 > bottomSlop; + final boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlop || y1 > bottomSlop; + + if (p0sloppy && p1sloppy) { + mFocusX = -1; + mFocusY = -1; + mSloppyGesture = true; + } else if (p0sloppy) { + mFocusX = MotionEventCompat.getX(event, 1); + mFocusY = MotionEventCompat.getY(event, 1); + mSloppyGesture = true; + } else if (p1sloppy) { + mFocusX = MotionEventCompat.getX(event, 0); + mFocusY = MotionEventCompat.getY(event, 0); + mSloppyGesture = true; + } else { + mGestureInProgress = mListener.onScaleBegin(this); + } + } + break; + + case MotionEvent.ACTION_MOVE: + if (mSloppyGesture) { + // Initiate sloppy gestures if we've moved outside of + // the slop area. + final float edgeSlop = mEdgeSlop; + final float rightSlop = mRightSlopEdge; + final float bottomSlop = mBottomSlopEdge; + final float x0 = event.getRawX(); + final float y0 = event.getRawY(); + final float x1 = getRawX(event, 1); + final float y1 = getRawY(event, 1); + + final boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > rightSlop || y0 > bottomSlop; + final boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlop || y1 > bottomSlop; + + if (p0sloppy && p1sloppy) { + mFocusX = -1; + mFocusY = -1; + } else if (p0sloppy) { + mFocusX = MotionEventCompat.getX(event, 1); + mFocusY = MotionEventCompat.getY(event, 1); + } else if (p1sloppy) { + mFocusX = MotionEventCompat.getX(event, 0); + mFocusY = MotionEventCompat.getY(event, 0); + } else { + mSloppyGesture = false; + mGestureInProgress = mListener.onScaleBegin(this); + } + } + break; + + case MotionEvent.ACTION_POINTER_UP: + if (mSloppyGesture) { + // Set focus point to the remaining finger + final int id = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT == 0 ? 1 + : 0; + mFocusX = event.getX(id); + mFocusY = event.getY(id); + } + break; + } + } else { + // Transform gesture in progress - attempt to handle it + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_POINTER_UP: + // Gesture ended + setContext(event); + + // Set focus point to the remaining finger + final int id = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT == 0 ? 1 + : 0; + mFocusX = event.getX(id); + mFocusY = event.getY(id); + + if (!mSloppyGesture) { + mListener.onScaleEnd(this); + } + + reset(); + break; + + case MotionEvent.ACTION_CANCEL: + if (!mSloppyGesture) { + mListener.onScaleEnd(this); + } + + reset(); + break; + + case MotionEvent.ACTION_MOVE: + setContext(event); + + // Only accept the event if our relative pressure is within + // a certain limit - this can help filter shaky data as a + // finger is lifted. + if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { + final boolean updatePrevious = mListener.onScale(this); + + if (updatePrevious) { + mPrevEvent.recycle(); + mPrevEvent = MotionEvent.obtain(event); + } + } + break; + } + } + return handled; + } + + private void reset() { + if (mPrevEvent != null) { + mPrevEvent.recycle(); + mPrevEvent = null; + } + if (mCurrEvent != null) { + mCurrEvent.recycle(); + mCurrEvent = null; + } + mSloppyGesture = false; + mGestureInProgress = false; + } + + private void setContext(final MotionEvent curr) { + if (mCurrEvent != null) { + mCurrEvent.recycle(); + } + mCurrEvent = MotionEvent.obtain(curr); + + mCurrLen = -1; + mPrevLen = -1; + mScaleFactor = -1; + + final MotionEvent prev = mPrevEvent; + + final float px0 = prev.getX(0); + final float py0 = prev.getY(0); + final float px1 = prev.getX(1); + final float py1 = prev.getY(1); + final float cx0 = curr.getX(0); + final float cy0 = curr.getY(0); + final float cx1 = curr.getX(1); + final float cy1 = curr.getY(1); + + final float pvx = px1 - px0; + final float pvy = py1 - py0; + final float cvx = cx1 - cx0; + final float cvy = cy1 - cy0; + mPrevFingerDiffX = pvx; + mPrevFingerDiffY = pvy; + mCurrFingerDiffX = cvx; + mCurrFingerDiffY = cvy; + + mFocusX = cx0 + cvx * 0.5f; + mFocusY = cy0 + cvy * 0.5f; + mTimeDelta = curr.getEventTime() - prev.getEventTime(); + mCurrPressure = curr.getPressure(0) + curr.getPressure(1); + mPrevPressure = prev.getPressure(0) + prev.getPressure(1); + } + + /** + * MotionEvent has no getRawX(int) method; simulate it pending future API + * approval. + */ + private static float getRawX(final MotionEvent event, final int pointerIndex) { + final float offset = event.getX() - event.getRawX(); + return event.getX(pointerIndex) + offset; + } + + /** + * MotionEvent has no getRawY(int) method; simulate it pending future API + * approval. + */ + private static float getRawY(final MotionEvent event, final int pointerIndex) { + final float offset = event.getY() - event.getRawY(); + return event.getY(pointerIndex) + offset; + } + + /** + * The listener for receiving notifications when gestures occur. If you want + * to listen for all the different gestures then implement this interface. + * If you only want to listen for a subset it might be easier to extend + * {@link SimpleOnScaleGestureListener}. An application will receive events + * in the following order: + *
    + *
  • One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} + *
  • Zero or more + * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} + *
  • One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)} + *
+ */ + public static interface OnScaleGestureListener { + /** + * Responds to scaling events for a gesture in progress. Reported by + * pointer motion. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should consider this event as + * handled. If an event was not handled, the detector will + * continue to accumulate movement until an event is handled. + * This can be useful if an application, for example, only wants + * to update scaling factors if the change is greater than 0.01. + */ + public boolean onScale(ScaleGestureDetector detector); + + /** + * Responds to the beginning of a scaling gesture. Reported by new + * pointers going down. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should continue recognizing this + * gesture. For example, if a gesture is beginning with a focal + * point outside of a region where it makes sense, + * onScaleBegin() may return false to ignore the rest of the + * gesture. + */ + public boolean onScaleBegin(ScaleGestureDetector detector); + + /** + * Responds to the end of a scale gesture. Reported by existing pointers + * going up. Once a scale has ended, + * {@link ScaleGestureDetector#getFocusX()} and + * {@link ScaleGestureDetector#getFocusY()} will return the location of + * the pointer remaining on the screen. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + */ + public void onScaleEnd(ScaleGestureDetector detector); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of scaling-related events. This implements all methods in + * {@link OnScaleGestureListener} but does nothing. + * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns + * {@code false} so that a subclass can retrieve the accumulated scale + * factor in an overridden onScaleEnd. + * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns + * {@code true}. + */ + public static class SimpleOnScaleGestureListener implements OnScaleGestureListener { + + @Override + public boolean onScale(final ScaleGestureDetector detector) { + return false; + } + + @Override + public boolean onScaleBegin(final ScaleGestureDetector detector) { + return true; + } + + @Override + public void onScaleEnd(final ScaleGestureDetector detector) { + // Intentionally empty + } + } +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java new file mode 100644 index 000000000..934bb810c --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Cubic.java @@ -0,0 +1,20 @@ +package it.sephiroth.android.library.imagezoom.easing; + +public class Cubic implements Easing { + + @Override + public double easeIn(double time, final double start, final double end, final double duration) { + return end * (time /= duration) * time * time + start; + } + + @Override + public double easeInOut(double time, final double start, final double end, final double duration) { + if ((time /= duration / 2.0) < 1.0) return end / 2.0 * time * time * time + start; + return end / 2.0 * ((time -= 2.0) * time * time + 2.0) + start; + } + + @Override + public double easeOut(double time, final double start, final double end, final double duration) { + return end * ((time = time / duration - 1.0) * time * time + 1.0) + start; + } +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java new file mode 100644 index 000000000..364a92f1c --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/easing/Easing.java @@ -0,0 +1,10 @@ +package it.sephiroth.android.library.imagezoom.easing; + +public interface Easing { + + double easeIn(double time, double start, double end, double duration); + + double easeInOut(double time, double start, double end, double duration); + + double easeOut(double time, double start, double end, double duration); +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java new file mode 100644 index 000000000..eb99039ee --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/FastBitmapDrawable.java @@ -0,0 +1,85 @@ +package it.sephiroth.android.library.imagezoom.graphics; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; + +import java.io.InputStream; + +/** + * Fast bitmap drawable. Does not support states. it only support alpha and + * colormatrix + * + * @author alessandro + */ +public class FastBitmapDrawable extends Drawable implements IBitmapDrawable { + + protected Bitmap mBitmap; + protected Paint mPaint; + + public FastBitmapDrawable(final Bitmap b) { + mBitmap = b; + mPaint = new Paint(); + mPaint.setDither(true); + mPaint.setFilterBitmap(true); + } + + public FastBitmapDrawable(final Resources res, final InputStream is) { + this(BitmapFactory.decodeStream(is)); + } + + @Override + public void draw(final Canvas canvas) { + canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint); + } + + @Override + public Bitmap getBitmap() { + return mBitmap; + } + + @Override + public int getIntrinsicHeight() { + return mBitmap.getHeight(); + } + + @Override + public int getIntrinsicWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getMinimumHeight() { + return mBitmap.getHeight(); + } + + @Override + public int getMinimumWidth() { + return mBitmap.getWidth(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public void setAlpha(final int alpha) { + mPaint.setAlpha(alpha); + } + + public void setAntiAlias(final boolean value) { + mPaint.setAntiAlias(value); + invalidateSelf(); + } + + @Override + public void setColorFilter(final ColorFilter cf) { + mPaint.setColorFilter(cf); + } +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java new file mode 100644 index 000000000..c1532aa98 --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/graphics/IBitmapDrawable.java @@ -0,0 +1,15 @@ +package it.sephiroth.android.library.imagezoom.graphics; + +import android.graphics.Bitmap; + +import it.sephiroth.android.library.imagezoom.ImageViewTouchBase; + +/** + * Base interface used in the {@link ImageViewTouchBase} view + * + * @author alessandro + */ +public interface IBitmapDrawable { + + Bitmap getBitmap(); +} diff --git a/twidere/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java new file mode 100644 index 000000000..da991a799 --- /dev/null +++ b/twidere/src/main/java/it/sephiroth/android/library/imagezoom/utils/IDisposable.java @@ -0,0 +1,6 @@ +package it.sephiroth.android.library.imagezoom.utils; + +public interface IDisposable { + + void dispose(); +} diff --git a/twidere/src/main/java/me/imid/swipebacklayout/lib/SwipeBackLayout.java b/twidere/src/main/java/me/imid/swipebacklayout/lib/SwipeBackLayout.java new file mode 100644 index 000000000..a0f23e30c --- /dev/null +++ b/twidere/src/main/java/me/imid/swipebacklayout/lib/SwipeBackLayout.java @@ -0,0 +1,621 @@ +package me.imid.swipebacklayout.lib; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ImageView.ScaleType; + +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.util.MathUtils; +import org.mariotaku.twidere.util.ThemeUtils; +import org.mariotaku.twidere.util.accessor.ViewAccessor; + +public class SwipeBackLayout extends FrameLayout { + /** + * Minimum velocity that will be detected as a fling + */ + private static final int MIN_FLING_VELOCITY = 400; // dips per second + + private static final int DEFAULT_SCRIM_COLOR = 0x99000000; + + /** + * Edge flag indicating that the left edge should be affected. + */ + public static final int EDGE_LEFT = ViewDragHelper.EDGE_LEFT; + + /** + * Edge flag indicating that the right edge should be affected. + */ + public static final int EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT; + + /** + * Edge flag indicating that the bottom edge should be affected. + */ + public static final int EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM; + + /** + * Edge flag set indicating all edges should be affected. + */ + public static final int EDGE_ALL = EDGE_LEFT | EDGE_RIGHT | EDGE_BOTTOM; + + /** + * A view is not currently being dragged or animating as a result of a + * fling/snap. + */ + public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; + + /** + * A view is currently being dragged. The position is currently changing as + * a result of user input or simulated user input. + */ + public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; + + /** + * A view is currently settling into place as a result of a fling or + * predefined non-interactive motion. + */ + public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; + + /** + * Default threshold of scroll + */ + private static final float DEFAULT_SCROLL_THRESHOLD = 0.3f; + + private static final int OVERSCROLL_DISTANCE = 10; + + private static final int[] EDGE_FLAGS = { EDGE_LEFT, EDGE_RIGHT, EDGE_BOTTOM, EDGE_ALL }; + + private int mEdgeFlags; + + /** + * Threshold of scroll, we will close the activity, when scrollPercent over + * this value; + */ + private float mScrollThreshold = DEFAULT_SCROLL_THRESHOLD; + + private Activity mActivity; + + private boolean mEnable = true; + + private View mContentView; + + private ImageView mBackgroundView; + private final ViewDragHelper mDragHelper; + + private float mScrollPercent; + + private int mContentLeft; + + private int mContentTop; + + private SwipeListener mSwipeListener; + + private Drawable mShadowLeft; + + private Drawable mShadowRight; + + private Drawable mShadowBottom; + + private float mScrimOpacity; + + private int mScrimColor; + + private float mScrimAlpha; + + private boolean mInLayout; + + private final Rect mTmpRect = new Rect(); + + /** + * Edge being dragged + */ + private int mTrackingEdge; + + private float mScalePercent; + + private OnSwipeBackScrollListener mOnSwipeBackScrollListener; + + public SwipeBackLayout(final Context context) { + this(context, null); + } + + public SwipeBackLayout(final Context context, final AttributeSet attrs) { + this(context, attrs, R.attr.SwipeBackLayoutStyle); + } + + public SwipeBackLayout(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs); + setFitsSystemWindows(true); + mDragHelper = ViewDragHelper.create(this, new ViewDragCallback()); + + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeBackLayout, defStyle, + R.style.SwipeBackLayout); + + final int edgeSize = a.getDimensionPixelSize(R.styleable.SwipeBackLayout_edgeSize, -1); + if (edgeSize > 0) { + setEdgeSize(edgeSize); + } + final int mode = EDGE_FLAGS[a.getInt(R.styleable.SwipeBackLayout_edgeFlag, 0)]; + setEdgeTrackingEnabled(mode); + + final int shadowLeft = a.getResourceId(R.styleable.SwipeBackLayout_shadowLeft, R.drawable.shadow_left); + final int shadowRight = a.getResourceId(R.styleable.SwipeBackLayout_shadowRight, R.drawable.shadow_right); + final int shadowBottom = a.getResourceId(R.styleable.SwipeBackLayout_shadowBottom, R.drawable.shadow_bottom); + final int scrimColor = a.getColor(R.styleable.SwipeBackLayout_scrimColor, DEFAULT_SCRIM_COLOR); + final float scrimAlpha = a.getFloat(R.styleable.SwipeBackLayout_scrimAlpha, Color.alpha(scrimColor) / 255.0f); + final float scalePercent = a.getFraction(R.styleable.SwipeBackLayout_scalePercent, 1, 1, 1); + setShadow(shadowLeft, EDGE_LEFT); + setShadow(shadowRight, EDGE_RIGHT); + setShadow(shadowBottom, EDGE_BOTTOM); + setScalePercent(scalePercent); + setScrimColor(scrimColor); + setScrimAlpha(scrimAlpha); + a.recycle(); + final float density = getResources().getDisplayMetrics().density; + final float minVel = MIN_FLING_VELOCITY * density; + mDragHelper.setMinVelocity(minVel); + + } + + public void attachToActivity(final Activity activity) { + mActivity = activity; + final Drawable background = ThemeUtils.getWindowBackground(activity); + + final ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView(); + final ViewGroup decorChild = (ViewGroup) decor.getChildAt(0); + final ImageView backgroundChild = new ImageView(activity); + backgroundChild.setScaleType(ScaleType.CENTER_CROP); + ViewAccessor.setBackground(decorChild, background); + decor.removeView(decorChild); + setBackgroundView(backgroundChild); + setContentView(decorChild); + addView(decorChild, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + final FrameLayout frame = new FrameLayout(activity); + frame.setFitsSystemWindows(true); + frame.addView(backgroundChild, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + frame.addView(this); + decor.addView(frame); + } + + @Override + public void computeScroll() { + mScrimOpacity = 1 - mScrollPercent; + updateWindowBackground(); + if (mDragHelper.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + if (mOnSwipeBackScrollListener != null) { + mOnSwipeBackScrollListener.onSwipeBackScroll(mScrollPercent); + } + } + + public int getEdgeFlags() { + return mEdgeFlags; + } + + public Drawable getShadow(final int edgeFlag) { + if ((edgeFlag & EDGE_LEFT) != 0) + return mShadowLeft; + else if ((edgeFlag & EDGE_RIGHT) != 0) + return mShadowRight; + else if ((edgeFlag & EDGE_BOTTOM) != 0) return mShadowBottom; + return null; + } + + public int getTrackingEdge() { + return mTrackingEdge; + } + + public boolean isSwiping() { + return mDragHelper != null && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE; + } + + @Override + public boolean onInterceptTouchEvent(final MotionEvent event) { + if (!mEnable) return false; + try { + return mDragHelper.shouldInterceptTouchEvent(event); + } catch (final ArrayIndexOutOfBoundsException e) { + // FIXME: handle exception + // issues #9 + return false; + } + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + if (!mEnable) return false; + mDragHelper.processTouchEvent(event); + return true; + } + + @Override + public void requestLayout() { + if (!mInLayout) { + super.requestLayout(); + } + } + + /** + * Scroll out contentView and finish the activity + */ + public void scrollToFinishActivity() { + final int childWidth = mContentView.getWidth(); + final int childHeight = mContentView.getHeight(); + + int left = 0, top = 0; + if ((mEdgeFlags & EDGE_LEFT) != 0) { + left = childWidth + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE; + mTrackingEdge = EDGE_LEFT; + } else if ((mEdgeFlags & EDGE_RIGHT) != 0) { + left = -childWidth - mShadowRight.getIntrinsicWidth() - OVERSCROLL_DISTANCE; + mTrackingEdge = EDGE_RIGHT; + } else if ((mEdgeFlags & EDGE_BOTTOM) != 0) { + top = -childHeight - mShadowBottom.getIntrinsicHeight() - OVERSCROLL_DISTANCE; + mTrackingEdge = EDGE_BOTTOM; + } + + mDragHelper.smoothSlideViewTo(mContentView, left, top); + invalidate(); + } + + /** + * Set the size of an edge. This is the range in pixels along the edges of + * this view that will actively detect edge touches or drags if edge + * tracking is enabled. + * + * @param size The size of an edge in pixels + */ + public void setEdgeSize(final int size) { + mDragHelper.setEdgeSize(size); + } + + /** + * Enable edge tracking for the selected edges of the parent view. The + * callback's + * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)} + * and + * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)} + * methods will only be invoked for edges for which edge tracking has been + * enabled. + * + * @param edgeFlags Combination of edge flags describing the edges to watch + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void setEdgeTrackingEnabled(final int edgeFlags) { + mEdgeFlags = edgeFlags; + mDragHelper.setEdgeTrackingEnabled(mEdgeFlags); + } + + public void setEnableGesture(final boolean enable) { + mEnable = enable; + } + + public void setOnSwipeBackScrollListener(final OnSwipeBackScrollListener listener) { + mOnSwipeBackScrollListener = listener; + } + + /** + * Set a color to use for the scrim that obscures primary content while a + * drawer is open. + * + * @param color Color to use in 0xAARRGGBB format. + */ + public void setScrimColor(final int color) { + mScrimColor = color; + invalidate(); + } + + /** + * Set scroll threshold, we will close the activity, when scrollPercent over + * this value + * + * @param threshold + */ + public void setScrollThresHold(final float threshold) { + if (threshold >= 1.0f || threshold <= 0) + throw new IllegalArgumentException("Threshold value should be between 0 and 1.0"); + mScrollThreshold = threshold; + } + + /** + * Set a drawable used for edge shadow. + * + * @param shadow Drawable to use + * @param edgeFlags Combination of edge flags describing the edge to set + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void setShadow(final Drawable shadow, final int edgeFlag) { + if ((edgeFlag & EDGE_LEFT) != 0) { + mShadowLeft = shadow; + } else if ((edgeFlag & EDGE_RIGHT) != 0) { + mShadowRight = shadow; + } else if ((edgeFlag & EDGE_BOTTOM) != 0) { + mShadowBottom = shadow; + } + invalidate(); + } + + /** + * Set a drawable used for edge shadow. + * + * @param resId Resource of drawable to use + * @param edgeFlags Combination of edge flags describing the edge to set + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void setShadow(final int resId, final int edgeFlag) { + setShadow(getResources().getDrawable(resId), edgeFlag); + } + + /** + * Register a callback to be invoked when a swipe event is sent to this + * view. + * + * @param listener the swipe listener to attach to this view + */ + public void setSwipeListener(final SwipeListener listener) { + mSwipeListener = listener; + } + + public void setWindowBackgroundDrawable(final Drawable d) { + if (mBackgroundView == null) return; + mBackgroundView.setImageDrawable(d); + } + + @Override + protected boolean drawChild(final Canvas canvas, final View child, final long drawingTime) { + final boolean drawContent = child == mContentView; + drawShadow(canvas, child); + + final boolean ret = super.drawChild(canvas, child, drawingTime); + if (mScrimOpacity > 0 && drawContent && mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE) { + drawScrim(canvas, child); + } + return ret; + } + + @Override + protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { + mInLayout = true; + if (mContentView != null) { + mContentView.layout(mContentLeft, mContentTop, mContentLeft + mContentView.getMeasuredWidth(), mContentTop + + mContentView.getMeasuredHeight()); + } + mInLayout = false; + } + + private void drawScrim(final Canvas canvas, final View child) { + final int alpha = (int) (mScrimAlpha * 255 * mScrimOpacity); + final int color = alpha << 24 | mScrimColor & 0xffffff; + + if ((mTrackingEdge & EDGE_LEFT) != 0) { + canvas.clipRect(0, 0, child.getLeft(), getHeight()); + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + canvas.clipRect(child.getRight(), 0, getRight(), getHeight()); + } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + canvas.clipRect(child.getLeft(), child.getBottom(), getRight(), getHeight()); + } + canvas.drawColor(color); + } + + private void drawShadow(final Canvas canvas, final View child) { + final Rect childRect = mTmpRect; + child.getHitRect(childRect); + + if ((mEdgeFlags & EDGE_LEFT) != 0) { + mShadowLeft.setBounds(childRect.left - mShadowLeft.getIntrinsicWidth(), childRect.top, childRect.left, + childRect.bottom); + mShadowLeft.draw(canvas); + } + + if ((mEdgeFlags & EDGE_RIGHT) != 0) { + mShadowRight.setBounds(childRect.right, childRect.top, childRect.right + mShadowRight.getIntrinsicWidth(), + childRect.bottom); + mShadowRight.draw(canvas); + } + + if ((mEdgeFlags & EDGE_BOTTOM) != 0) { + mShadowBottom.setBounds(childRect.left, childRect.bottom, childRect.right, + childRect.bottom + mShadowBottom.getIntrinsicHeight()); + mShadowBottom.draw(canvas); + } + } + + private void setBackgroundView(final ImageView view) { + mBackgroundView = view; + } + + /** + * Set up contentView which will be moved by user gesture + * + * @param view + */ + private void setContentView(final View view) { + mContentView = view; + } + + private void setScalePercent(final float scalePercent) { + mScalePercent = scalePercent; + } + + private void setScrimAlpha(final float scrimAlpha) { + mScrimAlpha = scrimAlpha; + invalidate(); + } + + private void updateWindowBackground() { + if (mBackgroundView == null) return; + final float scrollPercentAbs = Math.abs(mScrollPercent); + final float percent = MathUtils.clamp(1 - (1 - scrollPercentAbs) * (1 - mScalePercent), 1, 0); + mBackgroundView.setScaleType(ScaleType.CENTER_CROP); + mBackgroundView.setScaleX(percent); + mBackgroundView.setScaleY(percent); + mBackgroundView.setVisibility(mScrollPercent <= 0 ? View.INVISIBLE : View.VISIBLE); + mBackgroundView.setAlpha(scrollPercentAbs); + // mBackgroundView.setScrollPercent(mScrollPercent / mScalePercent); + } + + public interface OnSwipeBackScrollListener { + void onSwipeBackScroll(float percent); + } + + public static interface SwipeListener { + /** + * Invoke when edge touched + * + * @param edgeFlag edge flag describing the edge being touched + * @see #EDGE_LEFT + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void onEdgeTouch(int edgeFlag); + + /** + * Invoke when scroll percent over the threshold for the first time + */ + public void onScrollOverThreshold(); + + /** + * Invoke when state change + * + * @param state flag to describe scroll state + * @see #STATE_IDLE + * @see #STATE_DRAGGING + * @see #STATE_SETTLING + * @param scrollPercent scroll percent of this view + */ + public void onScrollStateChange(int state, float scrollPercent); + } + + private class ViewDragCallback extends ViewDragHelper.Callback { + private boolean mIsScrollOverValid; + + @Override + public int clampViewPositionHorizontal(final View child, final int left, final int dx) { + int ret = 0; + if ((mTrackingEdge & EDGE_LEFT) != 0) { + ret = Math.min(child.getWidth(), Math.max(left, 0)); + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + ret = Math.min(0, Math.max(left, -child.getWidth())); + } + return ret; + } + + @Override + public int clampViewPositionVertical(final View child, final int top, final int dy) { + int ret = 0; + if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + ret = Math.min(0, Math.max(top, -child.getHeight())); + } + return ret; + } + + @Override + public int getViewHorizontalDragRange(final View child) { + return mEdgeFlags & (EDGE_LEFT | EDGE_RIGHT); + } + + @Override + public int getViewVerticalDragRange(final View child) { + return mEdgeFlags & EDGE_BOTTOM; + } + + @Override + public void onViewDragStateChanged(final int state) { + super.onViewDragStateChanged(state); + if (mSwipeListener != null) { + mSwipeListener.onScrollStateChange(state, mScrollPercent); + } + } + + @Override + public void onViewPositionChanged(final View changedView, final int left, final int top, final int dx, + final int dy) { + super.onViewPositionChanged(changedView, left, top, dx, dy); + if ((mTrackingEdge & EDGE_LEFT) != 0) { + mScrollPercent = Math.abs((float) left / (mContentView.getWidth() + mShadowLeft.getIntrinsicWidth())); + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + mScrollPercent = Math.abs((float) left / (mContentView.getWidth() + mShadowRight.getIntrinsicWidth())); + } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + mScrollPercent = Math + .abs((float) top / (mContentView.getHeight() + mShadowBottom.getIntrinsicHeight())); + } + mContentLeft = left; + mContentTop = top; + invalidate(); + if (mScrollPercent < mScrollThreshold && !mIsScrollOverValid) { + mIsScrollOverValid = true; + } + if (mSwipeListener != null && mDragHelper.getViewDragState() == STATE_DRAGGING + && mScrollPercent >= mScrollThreshold && mIsScrollOverValid) { + mIsScrollOverValid = false; + mSwipeListener.onScrollOverThreshold(); + } + + if (mScrollPercent >= 1) { + if (!mActivity.isFinishing()) { + mActivity.finish(); + mActivity.overridePendingTransition(0, 0); + } + } + } + + @Override + public void onViewReleased(final View releasedChild, final float xvel, final float yvel) { + final int childWidth = releasedChild.getWidth(); + final int childHeight = releasedChild.getHeight(); + + int left = 0, top = 0; + if ((mTrackingEdge & EDGE_LEFT) != 0) { + left = xvel > 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? childWidth + + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE : 0; + } else if ((mTrackingEdge & EDGE_RIGHT) != 0) { + left = xvel < 0 || xvel == 0 && mScrollPercent > mScrollThreshold ? -(childWidth + + mShadowLeft.getIntrinsicWidth() + OVERSCROLL_DISTANCE) : 0; + } else if ((mTrackingEdge & EDGE_BOTTOM) != 0) { + top = yvel < 0 || yvel == 0 && mScrollPercent > mScrollThreshold ? -(childHeight + + mShadowBottom.getIntrinsicHeight() + OVERSCROLL_DISTANCE) : 0; + } + + mDragHelper.settleCapturedViewAt(left, top); + invalidate(); + } + + @Override + public boolean tryCaptureView(final View view, final int i) { + final boolean ret = mDragHelper.isEdgeTouched(mEdgeFlags, i); + if (ret) { + if (mDragHelper.isEdgeTouched(EDGE_LEFT, i)) { + mTrackingEdge = EDGE_LEFT; + } else if (mDragHelper.isEdgeTouched(EDGE_RIGHT, i)) { + mTrackingEdge = EDGE_RIGHT; + } else if (mDragHelper.isEdgeTouched(EDGE_BOTTOM, i)) { + mTrackingEdge = EDGE_BOTTOM; + } + if (mSwipeListener != null) { + mSwipeListener.onEdgeTouch(mTrackingEdge); + } + mIsScrollOverValid = true; + } + return ret; + } + } + +} diff --git a/twidere/src/main/java/me/imid/swipebacklayout/lib/ViewDragHelper.java b/twidere/src/main/java/me/imid/swipebacklayout/lib/ViewDragHelper.java new file mode 100644 index 000000000..93c08feec --- /dev/null +++ b/twidere/src/main/java/me/imid/swipebacklayout/lib/ViewDragHelper.java @@ -0,0 +1,1511 @@ +/* + * 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 me.imid.swipebacklayout.lib; + +import android.content.Context; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.VelocityTrackerCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ScrollerCompat; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.Interpolator; + +import java.util.Arrays; + +/** + * ViewDragHelper is a utility class for writing custom ViewGroups. It offers a + * number of useful operations and state tracking for allowing a user to drag + * and reposition views within their parent ViewGroup. + */ +public class ViewDragHelper { + + /** + * A null/invalid pointer ID. + */ + public static final int INVALID_POINTER = -1; + + /** + * A view is not currently being dragged or animating as a result of a + * fling/snap. + */ + public static final int STATE_IDLE = 0; + + /** + * A view is currently being dragged. The position is currently changing as + * a result of user input or simulated user input. + */ + public static final int STATE_DRAGGING = 1; + + /** + * A view is currently settling into place as a result of a fling or + * predefined non-interactive motion. + */ + public static final int STATE_SETTLING = 2; + + /** + * Edge flag indicating that the left edge should be affected. + */ + public static final int EDGE_LEFT = 1 << 0; + + /** + * Edge flag indicating that the right edge should be affected. + */ + public static final int EDGE_RIGHT = 1 << 1; + + /** + * Edge flag indicating that the top edge should be affected. + */ + public static final int EDGE_TOP = 1 << 2; + + /** + * Edge flag indicating that the bottom edge should be affected. + */ + public static final int EDGE_BOTTOM = 1 << 3; + + /** + * Edge flag set indicating all edges should be affected. + */ + public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM; + + /** + * Indicates that a check should occur along the horizontal axis + */ + public static final int DIRECTION_HORIZONTAL = 1 << 0; + + /** + * Indicates that a check should occur along the vertical axis + */ + public static final int DIRECTION_VERTICAL = 1 << 1; + + /** + * Indicates that a check should occur along all axes + */ + public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; + + public static final int EDGE_SIZE = 20; // dp + + private static final int BASE_SETTLE_DURATION = 256; // ms + + private static final int MAX_SETTLE_DURATION = 600; // ms + + // Current drag state; idle, dragging or settling + private int mDragState; + + // Distance to travel before a drag may begin + private int mTouchSlop; + + // Last known position/pointer tracking + private int mActivePointerId = INVALID_POINTER; + + private float[] mInitialMotionX; + + private float[] mInitialMotionY; + + private float[] mLastMotionX; + + private float[] mLastMotionY; + + private int[] mInitialEdgesTouched; + + private int[] mEdgeDragsInProgress; + + private int[] mEdgeDragsLocked; + + private int mPointersDown; + + private VelocityTracker mVelocityTracker; + + private final float mMaxVelocity; + + private float mMinVelocity; + + private int mEdgeSize; + + private int mTrackingEdges; + + private final ScrollerCompat mScroller; + + private final Callback mCallback; + + private View mCapturedView; + + private boolean mReleaseInProgress; + + private final ViewGroup mParentView; + + /** + * Interpolator defining the animation curve for mScroller + */ + private static final Interpolator sInterpolator = new Interpolator() { + @Override + public float getInterpolation(float t) { + t -= 1.0f; + return t * t * t * t * t + 1.0f; + } + }; + + private final Runnable mSetIdleRunnable = new Runnable() { + @Override + public void run() { + setDragState(STATE_IDLE); + } + }; + + /** + * Apps should use ViewDragHelper.create() to get a new instance. This will + * allow VDH to use internal compatibility implementations for different + * platform versions. + * + * @param context Context to initialize config-dependent params from + * @param forParent Parent view to monitor + */ + private ViewDragHelper(final Context context, final ViewGroup forParent, final Callback cb) { + if (forParent == null) throw new IllegalArgumentException("Parent view may not be null"); + if (cb == null) throw new IllegalArgumentException("Callback may not be null"); + + mParentView = forParent; + mCallback = cb; + + final ViewConfiguration vc = ViewConfiguration.get(context); + final float density = context.getResources().getDisplayMetrics().density; + mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); + + mTouchSlop = vc.getScaledTouchSlop(); + mMaxVelocity = vc.getScaledMaximumFlingVelocity(); + mMinVelocity = vc.getScaledMinimumFlingVelocity(); + mScroller = ScrollerCompat.create(context, sInterpolator); + } + + /** + * {@link #cancel()}, but also abort all motion in progress and snap to the + * end of any animation. + */ + public void abort() { + cancel(); + if (mDragState == STATE_SETTLING) { + final int oldX = mScroller.getCurrX(); + final int oldY = mScroller.getCurrY(); + mScroller.abortAnimation(); + final int newX = mScroller.getCurrX(); + final int newY = mScroller.getCurrY(); + mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY); + } + setDragState(STATE_IDLE); + } + + /** + * The result of a call to this method is equivalent to + * {@link #processTouchEvent(android.view.MotionEvent)} receiving an + * ACTION_CANCEL event. + */ + public void cancel() { + mActivePointerId = INVALID_POINTER; + clearMotionHistory(); + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + /** + * Capture a specific child view for dragging within the parent. The + * callback will be notified but + * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#tryCaptureView(android.view.View, int)} + * will not be asked permission to capture this view. + * + * @param childView Child view to capture + * @param activePointerId ID of the pointer that is dragging the captured + * child view + */ + public void captureChildView(final View childView, final int activePointerId) { + if (childView.getParent() != mParentView) + throw new IllegalArgumentException("captureChildView: parameter must be a descendant " + + "of the ViewDragHelper's tracked parent view (" + mParentView + ")"); + + mCapturedView = childView; + mActivePointerId = activePointerId; + mCallback.onViewCaptured(childView, activePointerId); + setDragState(STATE_DRAGGING); + } + + /** + * Check if any pointer tracked in the current gesture has crossed the + * required slop threshold. + *

+ * This depends on internal state populated by + * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or + * {@link #processTouchEvent(android.view.MotionEvent)}. You should only + * rely on the results of this method after all currently available touch + * data has been provided to one of these two methods. + *

+ * + * @param directions Combination of direction flags, see + * {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, + * {@link #DIRECTION_ALL} + * @return true if the slop threshold has been crossed, false otherwise + */ + public boolean checkTouchSlop(final int directions) { + final int count = mInitialMotionX.length; + for (int i = 0; i < count; i++) { + if (checkTouchSlop(directions, i)) return true; + } + return false; + } + + /** + * Check if the specified pointer tracked in the current gesture has crossed + * the required slop threshold. + *

+ * This depends on internal state populated by + * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or + * {@link #processTouchEvent(android.view.MotionEvent)}. You should only + * rely on the results of this method after all currently available touch + * data has been provided to one of these two methods. + *

+ * + * @param directions Combination of direction flags, see + * {@link #DIRECTION_HORIZONTAL}, {@link #DIRECTION_VERTICAL}, + * {@link #DIRECTION_ALL} + * @param pointerId ID of the pointer to slop check as specified by + * MotionEvent + * @return true if the slop threshold has been crossed, false otherwise + */ + public boolean checkTouchSlop(final int directions, final int pointerId) { + if (!isPointerDown(pointerId)) return false; + + final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL; + final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL; + + final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId]; + final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId]; + + if (checkHorizontal && checkVertical) + return dx * dx + dy * dy > mTouchSlop * mTouchSlop; + else if (checkHorizontal) + return Math.abs(dx) > mTouchSlop; + else if (checkVertical) return Math.abs(dy) > mTouchSlop; + return false; + } + + /** + * Move the captured settling view by the appropriate amount for the current + * time. If continueSettling returns true, the caller should + * call it again on the next frame to continue. + * + * @param deferCallbacks true if state callbacks should be deferred via + * posted message. Set this to true if you are calling this + * method from {@link android.view.View#computeScroll()} or + * similar methods invoked as part of layout or drawing. + * @return true if settle is still in progress + */ + public boolean continueSettling(final boolean deferCallbacks) { + if (mDragState == STATE_SETTLING) { + boolean keepGoing = mScroller.computeScrollOffset(); + final int x = mScroller.getCurrX(); + final int y = mScroller.getCurrY(); + final int dx = x - mCapturedView.getLeft(); + final int dy = y - mCapturedView.getTop(); + + if (dx != 0) { + mCapturedView.offsetLeftAndRight(dx); + } + if (dy != 0) { + mCapturedView.offsetTopAndBottom(dy); + } + + if (dx != 0 || dy != 0) { + mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy); + } + + if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) { + // Close enough. The interpolator/scroller might think we're + // still moving + // but the user sure doesn't. + mScroller.abortAnimation(); + keepGoing = mScroller.isFinished(); + } + + if (!keepGoing) { + if (deferCallbacks) { + mParentView.post(mSetIdleRunnable); + } else { + setDragState(STATE_IDLE); + } + } + } + + return mDragState == STATE_SETTLING; + } + + /** + * Find the topmost child under the given point within the parent view's + * coordinate system. The child order is determined using + * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#getOrderedChildIndex(int)} + * . + * + * @param x X position to test in the parent's coordinate system + * @param y Y position to test in the parent's coordinate system + * @return The topmost child view under (x, y) or null if none found. + */ + public View findTopChildUnder(final int x, final int y) { + final int childCount = mParentView.getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); + if (x >= child.getLeft() && x < child.getRight() && y >= child.getTop() && y < child.getBottom()) + return child; + } + return null; + } + + /** + * Settle the captured view based on standard free-moving fling behavior. + * The caller should invoke {@link #continueSettling(boolean)} on each + * subsequent frame to continue the motion until it returns false. + * + * @param minLeft Minimum X position for the view's left edge + * @param minTop Minimum Y position for the view's top edge + * @param maxLeft Maximum X position for the view's left edge + * @param maxTop Maximum Y position for the view's top edge + */ + public void flingCapturedView(final int minLeft, final int minTop, final int maxLeft, final int maxTop) { + if (!mReleaseInProgress) + throw new IllegalStateException("Cannot flingCapturedView outside of a call to " + + "Callback#onViewReleased"); + + mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), + (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), + (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), minLeft, maxLeft, minTop, + maxTop); + + setDragState(STATE_SETTLING); + } + + /** + * @return The ID of the pointer currently dragging the captured view, or + * {@link #INVALID_POINTER}. + */ + public int getActivePointerId() { + return mActivePointerId; + } + + /** + * @return The currently captured view, or null if no view has been + * captured. + */ + public View getCapturedView() { + return mCapturedView; + } + + /** + * Return the size of an edge. This is the range in pixels along the edges + * of this view that will actively detect edge touches or drags if edge + * tracking is enabled. + * + * @return The size of an edge in pixels + * @see #setEdgeTrackingEnabled(int) + */ + public int getEdgeSize() { + return mEdgeSize; + } + + /** + * Return the currently configured minimum velocity. Any flings with a + * magnitude less than this value in pixels per second. Callback methods + * accepting a velocity will receive zero as a velocity value if the real + * detected velocity was below this threshold. + * + * @return the minimum velocity that will be detected + */ + public float getMinVelocity() { + return mMinVelocity; + } + + /** + * @return The minimum distance in pixels that the user must travel to + * initiate a drag + */ + public int getTouchSlop() { + return mTouchSlop; + } + + /** + * Retrieve the current drag state of this helper. This will return one of + * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. + * + * @return The current drag state + */ + public int getViewDragState() { + return mDragState; + } + + /** + * Determine if the currently captured view is under the given point in the + * parent view's coordinate system. If there is no captured view this method + * will return false. + * + * @param x X position to test in the parent's coordinate system + * @param y Y position to test in the parent's coordinate system + * @return true if the captured view is under the given point, false + * otherwise + */ + public boolean isCapturedViewUnder(final int x, final int y) { + return isViewUnder(mCapturedView, x, y); + } + + /** + * Check if any of the edges specified were initially touched in the + * currently active gesture. If there is no currently active gesture this + * method will return false. + * + * @param edges Edges to check for an initial edge touch. See + * {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, + * {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} + * @return true if any of the edges specified were initially touched in the + * current gesture + */ + public boolean isEdgeTouched(final int edges) { + final int count = mInitialEdgesTouched.length; + for (int i = 0; i < count; i++) { + if (isEdgeTouched(edges, i)) return true; + } + return false; + } + + /** + * Check if any of the edges specified were initially touched by the pointer + * with the specified ID. If there is no currently active gesture or if + * there is no pointer with the given ID currently down this method will + * return false. + * + * @param edges Edges to check for an initial edge touch. See + * {@link #EDGE_LEFT}, {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, + * {@link #EDGE_BOTTOM} and {@link #EDGE_ALL} + * @return true if any of the edges specified were initially touched in the + * current gesture + */ + public boolean isEdgeTouched(final int edges, final int pointerId) { + return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0; + } + + /** + * Check if the given pointer ID represents a pointer that is currently down + * (to the best of the ViewDragHelper's knowledge). + *

+ * The state used to report this information is populated by the methods + * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or + * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these + * methods has not been called for all relevant MotionEvents to track, the + * information reported by this method may be stale or incorrect. + *

+ * + * @param pointerId pointer ID to check; corresponds to IDs provided by + * MotionEvent + * @return true if the pointer with the given ID is still down + */ + public boolean isPointerDown(final int pointerId) { + return (mPointersDown & 1 << pointerId) != 0; + } + + /** + * Determine if the supplied view is under the given point in the parent + * view's coordinate system. + * + * @param view Child view of the parent to hit test + * @param x X position to test in the parent's coordinate system + * @param y Y position to test in the parent's coordinate system + * @return true if the supplied view is under the given point, false + * otherwise + */ + public boolean isViewUnder(final View view, final int x, final int y) { + if (view == null) return false; + return x >= view.getLeft() && x < view.getRight() && y >= view.getTop() && y < view.getBottom(); + } + + /** + * Process a touch event received by the parent view. This method will + * dispatch callback events as needed before returning. The parent view's + * onTouchEvent implementation should call this. + * + * @param ev The touch event received by the parent view + */ + public void processTouchEvent(final MotionEvent ev) { + final int action = MotionEventCompat.getActionMasked(ev); + final int actionIndex = MotionEventCompat.getActionIndex(ev); + + if (action == MotionEvent.ACTION_DOWN) { + // Reset things for a new event stream, just in case we didn't get + // the whole previous stream. + cancel(); + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (action) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + final int pointerId = MotionEventCompat.getPointerId(ev, 0); + final View toCapture = findTopChildUnder((int) x, (int) y); + + saveInitialMotion(x, y, pointerId); + + // Since the parent is already directly processing this touch + // event, + // there is no reason to delay for a slop before dragging. + // Start immediately if possible. + tryCaptureViewForDrag(toCapture, pointerId); + + final int edgesTouched = mInitialEdgesTouched[pointerId]; + if ((edgesTouched & mTrackingEdges) != 0) { + mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); + } + break; + } + + case MotionEventCompat.ACTION_POINTER_DOWN: { + final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); + final float x = MotionEventCompat.getX(ev, actionIndex); + final float y = MotionEventCompat.getY(ev, actionIndex); + + saveInitialMotion(x, y, pointerId); + + // A ViewDragHelper can only manipulate one view at a time. + if (mDragState == STATE_IDLE) { + // If we're idle we can do anything! Treat it like a normal + // down event. + + final View toCapture = findTopChildUnder((int) x, (int) y); + tryCaptureViewForDrag(toCapture, pointerId); + + final int edgesTouched = mInitialEdgesTouched[pointerId]; + if ((edgesTouched & mTrackingEdges) != 0) { + mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); + } + } else if (isCapturedViewUnder((int) x, (int) y)) { + // We're still tracking a captured view. If the same view is + // under this + // point, we'll swap to controlling it with this pointer + // instead. + // (This will still work if we're "catching" a settling + // view.) + + tryCaptureViewForDrag(mCapturedView, pointerId); + } + break; + } + + case MotionEvent.ACTION_MOVE: { + if (mDragState == STATE_DRAGGING) { + final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); + final float x = MotionEventCompat.getX(ev, index); + final float y = MotionEventCompat.getY(ev, index); + final int idx = (int) (x - mLastMotionX[mActivePointerId]); + final int idy = (int) (y - mLastMotionY[mActivePointerId]); + + dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); + + saveLastMotion(ev); + } else { + // Check to see if any pointer is now over a draggable view. + final int pointerCount = MotionEventCompat.getPointerCount(ev); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = MotionEventCompat.getPointerId(ev, i); + final float x = MotionEventCompat.getX(ev, i); + final float y = MotionEventCompat.getY(ev, i); + final float dx = x - mInitialMotionX[pointerId]; + final float dy = y - mInitialMotionY[pointerId]; + + reportNewEdgeDrags(dx, dy, pointerId); + if (mDragState == STATE_DRAGGING) { + // Callback might have started an edge drag. + break; + } + + final View toCapture = findTopChildUnder((int) x, (int) y); + if (checkTouchSlop(toCapture, dx, dy) && tryCaptureViewForDrag(toCapture, pointerId)) { + break; + } + } + saveLastMotion(ev); + } + break; + } + + case MotionEventCompat.ACTION_POINTER_UP: { + final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); + if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { + // Try to find another pointer that's still holding on to + // the captured view. + int newActivePointer = INVALID_POINTER; + final int pointerCount = MotionEventCompat.getPointerCount(ev); + for (int i = 0; i < pointerCount; i++) { + final int id = MotionEventCompat.getPointerId(ev, i); + if (id == mActivePointerId) { + // This one's going away, skip. + continue; + } + + final float x = MotionEventCompat.getX(ev, i); + final float y = MotionEventCompat.getY(ev, i); + if (findTopChildUnder((int) x, (int) y) == mCapturedView + && tryCaptureViewForDrag(mCapturedView, id)) { + newActivePointer = mActivePointerId; + break; + } + } + + if (newActivePointer == INVALID_POINTER) { + // We didn't find another pointer still touching the + // view, release it. + releaseViewForPointerUp(); + } + } + clearMotionHistory(pointerId); + break; + } + + case MotionEvent.ACTION_UP: { + if (mDragState == STATE_DRAGGING) { + releaseViewForPointerUp(); + } + cancel(); + break; + } + + case MotionEvent.ACTION_CANCEL: { + if (mDragState == STATE_DRAGGING) { + dispatchViewReleased(0, 0); + } + cancel(); + break; + } + } + } + + /** + * Set the size of an edge. This is the range in pixels along the edges of + * this view that will actively detect edge touches or drags if edge + * tracking is enabled. + * + * @param size The size of an edge in pixels + */ + public void setEdgeSize(final int size) { + mEdgeSize = size; + } + + /** + * Enable edge tracking for the selected edges of the parent view. The + * callback's + * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeTouched(int, int)} + * and + * {@link me.imid.swipebacklayout.lib.ViewDragHelper.Callback#onEdgeDragStarted(int, int)} + * methods will only be invoked for edges for which edge tracking has been + * enabled. + * + * @param edgeFlags Combination of edge flags describing the edges to watch + * @see #EDGE_LEFT + * @see #EDGE_TOP + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void setEdgeTrackingEnabled(final int edgeFlags) { + mTrackingEdges = edgeFlags; + } + + /** + * Set the minimum velocity that will be detected as having a magnitude + * greater than zero in pixels per second. Callback methods accepting a + * velocity will be clamped appropriately. + * + * @param minVel Minimum velocity to detect + */ + public void setMinVelocity(final float minVel) { + mMinVelocity = minVel; + } + + /** + * Settle the captured view at the given (left, top) position. The + * appropriate velocity from prior motion will be taken into account. If + * this method returns true, the caller should invoke + * {@link #continueSettling(boolean)} on each subsequent frame to continue + * the motion until it returns false. If this method returns false there is + * no further work to do to complete the movement. + * + * @param finalLeft Settled left edge position for the captured view + * @param finalTop Settled top edge position for the captured view + * @return true if animation should continue through + * {@link #continueSettling(boolean)} calls + */ + public boolean settleCapturedViewAt(final int finalLeft, final int finalTop) { + if (!mReleaseInProgress) + throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " + + "Callback#onViewReleased"); + + return forceSettleCapturedViewAt(finalLeft, finalTop, + (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), + (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId)); + } + + /** + * Check if this event as provided to the parent view's + * onInterceptTouchEvent should cause the parent to intercept the touch + * event stream. + * + * @param ev MotionEvent provided to onInterceptTouchEvent + * @return true if the parent view should return true from + * onInterceptTouchEvent + */ + public boolean shouldInterceptTouchEvent(final MotionEvent ev) { + final int action = MotionEventCompat.getActionMasked(ev); + final int actionIndex = MotionEventCompat.getActionIndex(ev); + + if (action == MotionEvent.ACTION_DOWN) { + // Reset things for a new event stream, just in case we didn't get + // the whole previous stream. + cancel(); + } + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (action) { + case MotionEvent.ACTION_DOWN: { + final float x = ev.getX(); + final float y = ev.getY(); + final int pointerId = MotionEventCompat.getPointerId(ev, 0); + saveInitialMotion(x, y, pointerId); + + final View toCapture = findTopChildUnder((int) x, (int) y); + + // Catch a settling view if possible. + if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { + tryCaptureViewForDrag(toCapture, pointerId); + } + + final int edgesTouched = mInitialEdgesTouched[pointerId]; + if ((edgesTouched & mTrackingEdges) != 0) { + mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); + } + break; + } + + case MotionEventCompat.ACTION_POINTER_DOWN: { + final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); + final float x = MotionEventCompat.getX(ev, actionIndex); + final float y = MotionEventCompat.getY(ev, actionIndex); + + saveInitialMotion(x, y, pointerId); + + // A ViewDragHelper can only manipulate one view at a time. + if (mDragState == STATE_IDLE) { + final int edgesTouched = mInitialEdgesTouched[pointerId]; + if ((edgesTouched & mTrackingEdges) != 0) { + mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); + } + } else if (mDragState == STATE_SETTLING) { + // Catch a settling view if possible. + final View toCapture = findTopChildUnder((int) x, (int) y); + if (toCapture == mCapturedView) { + tryCaptureViewForDrag(toCapture, pointerId); + } + } + break; + } + + case MotionEvent.ACTION_MOVE: { + // First to cross a touch slop over a draggable view wins. Also + // report edge drags. + final int pointerCount = MotionEventCompat.getPointerCount(ev); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = MotionEventCompat.getPointerId(ev, i); + final float x = MotionEventCompat.getX(ev, i); + final float y = MotionEventCompat.getY(ev, i); + final float dx = x - mInitialMotionX[pointerId]; + final float dy = y - mInitialMotionY[pointerId]; + + reportNewEdgeDrags(dx, dy, pointerId); + if (mDragState == STATE_DRAGGING) { + // Callback might have started an edge drag + break; + } + + final View toCapture = findTopChildUnder((int) x, (int) y); + if (toCapture != null && checkTouchSlop(toCapture, dx, dy) + && tryCaptureViewForDrag(toCapture, pointerId)) { + break; + } + } + saveLastMotion(ev); + break; + } + + case MotionEventCompat.ACTION_POINTER_UP: { + final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); + clearMotionHistory(pointerId); + break; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + cancel(); + break; + } + } + + return mDragState == STATE_DRAGGING; + } + + /** + * Animate the view child to the given (left, top) position. If + * this method returns true, the caller should invoke + * {@link #continueSettling(boolean)} on each subsequent frame to continue + * the motion until it returns false. If this method returns false there is + * no further work to do to complete the movement. + *

+ * This operation does not count as a capture event, though + * {@link #getCapturedView()} will still report the sliding view while the + * slide is in progress. + *

+ * + * @param child Child view to capture and animate + * @param finalLeft Final left position of child + * @param finalTop Final top position of child + * @return true if animation should continue through + * {@link #continueSettling(boolean)} calls + */ + public boolean smoothSlideViewTo(final View child, final int finalLeft, final int finalTop) { + mCapturedView = child; + mActivePointerId = INVALID_POINTER; + + return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); + } + + /** + * Tests scrollability within child views of v given a delta of dx. + * + * @param v View to test for horizontal scrollability + * @param checkV Whether the view v passed should itself be checked for + * scrollability (true), or just its children (false). + * @param dx Delta scrolled in pixels along the X axis + * @param dy Delta scrolled in pixels along the Y axis + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point + * @return true if child views of v can be scrolled by delta of dx. + */ + protected boolean canScroll(final View v, final boolean checkV, final int dx, final int dy, final int x, final int y) { + if (v instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) v; + final int scrollX = v.getScrollX(); + final int scrollY = v.getScrollY(); + final int count = group.getChildCount(); + // Count backwards - let topmost views consume scroll distance + // first. + for (int i = count - 1; i >= 0; i--) { + // TODO: Add versioned support here for transformed views. + // This will not work for transformed views in Honeycomb+ + final View child = group.getChildAt(i); + if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop() + && y + scrollY < child.getBottom() + && canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) + return true; + } + } + + return checkV && (ViewCompat.canScrollHorizontally(v, -dx) || ViewCompat.canScrollVertically(v, -dy)); + } + + private boolean checkNewEdgeDrag(final float delta, final float odelta, final int pointerId, final int edge) { + final float absDelta = Math.abs(delta); + final float absODelta = Math.abs(odelta); + + if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 + || (mEdgeDragsLocked[pointerId] & edge) == edge || (mEdgeDragsInProgress[pointerId] & edge) == edge + || absDelta <= mTouchSlop && absODelta <= mTouchSlop) return false; + if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) { + mEdgeDragsLocked[pointerId] |= edge; + return false; + } + return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop; + } + + /** + * Check if we've crossed a reasonable touch slop for the given child view. + * If the child cannot be dragged along the horizontal or vertical axis, + * motion along that axis will not count toward the slop check. + * + * @param child Child to check + * @param dx Motion since initial position along X axis + * @param dy Motion since initial position along Y axis + * @return true if the touch slop has been crossed + */ + private boolean checkTouchSlop(final View child, final float dx, final float dy) { + if (child == null) return false; + final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; + final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; + + if (checkHorizontal && checkVertical) + return dx * dx + dy * dy > mTouchSlop * mTouchSlop; + else if (checkHorizontal) + return Math.abs(dx) > mTouchSlop; + else if (checkVertical) return Math.abs(dy) > mTouchSlop; + return false; + } + + /** + * Clamp the magnitude of value for absMin and absMax. If the value is below + * the minimum, it will be clamped to zero. If the value is above the + * maximum, it will be clamped to the maximum. + * + * @param value Value to clamp + * @param absMin Absolute value of the minimum significant value to return + * @param absMax Absolute value of the maximum value to return + * @return The clamped value with the same sign as value + */ + private float clampMag(final float value, final float absMin, final float absMax) { + final float absValue = Math.abs(value); + if (absValue < absMin) return 0; + if (absValue > absMax) return value > 0 ? absMax : -absMax; + return value; + } + + /** + * Clamp the magnitude of value for absMin and absMax. If the value is below + * the minimum, it will be clamped to zero. If the value is above the + * maximum, it will be clamped to the maximum. + * + * @param value Value to clamp + * @param absMin Absolute value of the minimum significant value to return + * @param absMax Absolute value of the maximum value to return + * @return The clamped value with the same sign as value + */ + private int clampMag(final int value, final int absMin, final int absMax) { + final int absValue = Math.abs(value); + if (absValue < absMin) return 0; + if (absValue > absMax) return value > 0 ? absMax : -absMax; + return value; + } + + private void clearMotionHistory() { + if (mInitialMotionX == null) return; + Arrays.fill(mInitialMotionX, 0); + Arrays.fill(mInitialMotionY, 0); + Arrays.fill(mLastMotionX, 0); + Arrays.fill(mLastMotionY, 0); + Arrays.fill(mInitialEdgesTouched, 0); + Arrays.fill(mEdgeDragsInProgress, 0); + Arrays.fill(mEdgeDragsLocked, 0); + mPointersDown = 0; + } + + private void clearMotionHistory(final int pointerId) { + if (mInitialMotionX == null) return; + mInitialMotionX[pointerId] = 0; + mInitialMotionY[pointerId] = 0; + mLastMotionX[pointerId] = 0; + mLastMotionY[pointerId] = 0; + mInitialEdgesTouched[pointerId] = 0; + mEdgeDragsInProgress[pointerId] = 0; + mEdgeDragsLocked[pointerId] = 0; + mPointersDown &= ~(1 << pointerId); + } + + private int computeAxisDuration(final int delta, int velocity, final int motionRange) { + if (delta == 0) return 0; + + final int width = mParentView.getWidth(); + final int halfWidth = width / 2; + final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); + final float distance = halfWidth + halfWidth * distanceInfluenceForSnapDuration(distanceRatio); + + int duration; + velocity = Math.abs(velocity); + if (velocity > 0) { + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + } else { + final float range = (float) Math.abs(delta) / motionRange; + duration = (int) ((range + 1) * BASE_SETTLE_DURATION); + } + return Math.min(duration, MAX_SETTLE_DURATION); + } + + private int computeSettleDuration(final View child, final int dx, final int dy, int xvel, int yvel) { + xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); + yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); + final int absDx = Math.abs(dx); + final int absDy = Math.abs(dy); + final int absXVel = Math.abs(xvel); + final int absYVel = Math.abs(yvel); + final int addedVel = absXVel + absYVel; + final int addedDistance = absDx + absDy; + + final float xweight = xvel != 0 ? (float) absXVel / addedVel : (float) absDx / addedDistance; + final float yweight = yvel != 0 ? (float) absYVel / addedVel : (float) absDy / addedDistance; + + final int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); + final int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); + + return (int) (xduration * xweight + yduration * yweight); + } + + /** + * Like all callback events this must happen on the UI thread, but release + * involves some extra semantics. During a release (mReleaseInProgress) is + * the only time it is valid to call {@link #settleCapturedViewAt(int, int)} + * or {@link #flingCapturedView(int, int, int, int)}. + */ + private void dispatchViewReleased(final float xvel, final float yvel) { + mReleaseInProgress = true; + mCallback.onViewReleased(mCapturedView, xvel, yvel); + mReleaseInProgress = false; + + if (mDragState == STATE_DRAGGING) { + // onViewReleased didn't call a method that would have changed this. + // Go idle. + setDragState(STATE_IDLE); + } + } + + private float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } + + private void dragTo(final int left, final int top, final int dx, final int dy) { + int clampedX = left; + int clampedY = top; + final int oldLeft = mCapturedView.getLeft(); + final int oldTop = mCapturedView.getTop(); + if (dx != 0) { + clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); + mCapturedView.offsetLeftAndRight(clampedX - oldLeft); + } + if (dy != 0) { + clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); + mCapturedView.offsetTopAndBottom(clampedY - oldTop); + } + + if (dx != 0 || dy != 0) { + final int clampedDx = clampedX - oldLeft; + final int clampedDy = clampedY - oldTop; + mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, clampedDx, clampedDy); + } + } + + private void ensureMotionHistorySizeForId(final int pointerId) { + if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) { + final float[] imx = new float[pointerId + 1]; + final float[] imy = new float[pointerId + 1]; + final float[] lmx = new float[pointerId + 1]; + final float[] lmy = new float[pointerId + 1]; + final int[] iit = new int[pointerId + 1]; + final int[] edip = new int[pointerId + 1]; + final int[] edl = new int[pointerId + 1]; + + if (mInitialMotionX != null) { + System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length); + System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length); + System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length); + System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length); + System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length); + System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length); + System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length); + } + + mInitialMotionX = imx; + mInitialMotionY = imy; + mLastMotionX = lmx; + mLastMotionY = lmy; + mInitialEdgesTouched = iit; + mEdgeDragsInProgress = edip; + mEdgeDragsLocked = edl; + } + } + + /** + * Settle the captured view at the given (left, top) position. + * + * @param finalLeft Target left position for the captured view + * @param finalTop Target top position for the captured view + * @param xvel Horizontal velocity + * @param yvel Vertical velocity + * @return true if animation should continue through + * {@link #continueSettling(boolean)} calls + */ + private boolean forceSettleCapturedViewAt(final int finalLeft, final int finalTop, final int xvel, final int yvel) { + final int startLeft = mCapturedView.getLeft(); + final int startTop = mCapturedView.getTop(); + final int dx = finalLeft - startLeft; + final int dy = finalTop - startTop; + + if (dx == 0 && dy == 0) { + // Nothing to do. Send callbacks, be done. + mScroller.abortAnimation(); + setDragState(STATE_IDLE); + return false; + } + + final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); + mScroller.startScroll(startLeft, startTop, dx, dy, duration); + + setDragState(STATE_SETTLING); + return true; + } + + private int getEdgesTouched(final int x, final int y) { + int result = 0; + + if (x < mParentView.getLeft() + mEdgeSize) { + result |= EDGE_LEFT; + } + if (y < mParentView.getTop() + mEdgeSize) { + result |= EDGE_TOP; + } + if (x > mParentView.getRight() - mEdgeSize) { + result |= EDGE_RIGHT; + } + if (y > mParentView.getBottom() - mEdgeSize) { + result |= EDGE_BOTTOM; + } + + return result; + } + + private void releaseViewForPointerUp() { + mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); + final float xvel = clampMag(VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), + mMinVelocity, mMaxVelocity); + final float yvel = clampMag(VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), + mMinVelocity, mMaxVelocity); + dispatchViewReleased(xvel, yvel); + } + + private void reportNewEdgeDrags(final float dx, final float dy, final int pointerId) { + int dragsStarted = 0; + if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) { + dragsStarted |= EDGE_LEFT; + } + if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) { + dragsStarted |= EDGE_TOP; + } + if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) { + dragsStarted |= EDGE_RIGHT; + } + if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) { + dragsStarted |= EDGE_BOTTOM; + } + + if (dragsStarted != 0) { + mEdgeDragsInProgress[pointerId] |= dragsStarted; + mCallback.onEdgeDragStarted(dragsStarted, pointerId); + } + } + + private void saveInitialMotion(final float x, final float y, final int pointerId) { + ensureMotionHistorySizeForId(pointerId); + mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x; + mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y; + mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y); + mPointersDown |= 1 << pointerId; + } + + private void saveLastMotion(final MotionEvent ev) { + final int pointerCount = MotionEventCompat.getPointerCount(ev); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = MotionEventCompat.getPointerId(ev, i); + final float x = MotionEventCompat.getX(ev, i); + final float y = MotionEventCompat.getY(ev, i); + mLastMotionX[pointerId] = x; + mLastMotionY[pointerId] = y; + } + } + + void setDragState(final int state) { + if (mDragState != state) { + mDragState = state; + mCallback.onViewDragStateChanged(state); + if (state == STATE_IDLE) { + mCapturedView = null; + } + } + } + + /** + * Attempt to capture the view with the given pointer ID. The callback will + * be involved. This will put us into the "dragging" state. If we've already + * captured this view with this pointer this method will immediately return + * true without consulting the callback. + * + * @param toCapture View to capture + * @param pointerId Pointer to capture with + * @return true if capture was successful + */ + boolean tryCaptureViewForDrag(final View toCapture, final int pointerId) { + if (toCapture == mCapturedView && mActivePointerId == pointerId) // Already + // done! + return true; + if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) { + mActivePointerId = pointerId; + captureChildView(toCapture, pointerId); + return true; + } + return false; + } + + /** + * Factory method to create a new ViewDragHelper. + * + * @param forParent Parent view to monitor + * @param cb Callback to provide information and receive events + * @return a new ViewDragHelper instance + */ + public static ViewDragHelper create(final ViewGroup forParent, final Callback cb) { + return new ViewDragHelper(forParent.getContext(), forParent, cb); + } + + /** + * Factory method to create a new ViewDragHelper. + * + * @param forParent Parent view to monitor + * @param sensitivity Multiplier for how sensitive the helper should be + * about detecting the start of a drag. Larger values are more + * sensitive. 1.0f is normal. + * @param cb Callback to provide information and receive events + * @return a new ViewDragHelper instance + */ + public static ViewDragHelper create(final ViewGroup forParent, final float sensitivity, final Callback cb) { + final ViewDragHelper helper = create(forParent, cb); + helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); + return helper; + } + + /** + * A Callback is used as a communication channel with the ViewDragHelper + * back to the parent view using it. on*methods are invoked on + * siginficant events and several accessor methods are expected to provide + * the ViewDragHelper with more information about the state of the parent + * view upon request. The callback also makes decisions governing the range + * and draggability of child views. + */ + public static abstract class Callback { + /** + * Restrict the motion of the dragged child view along the horizontal + * axis. The default implementation does not allow horizontal motion; + * the extending class must override this method and provide the desired + * clamping. + * + * @param child Child view being dragged + * @param left Attempted motion along the X axis + * @param dx Proposed change in position for left + * @return The new clamped position for left + */ + public int clampViewPositionHorizontal(final View child, final int left, final int dx) { + return 0; + } + + /** + * Restrict the motion of the dragged child view along the vertical + * axis. The default implementation does not allow vertical motion; the + * extending class must override this method and provide the desired + * clamping. + * + * @param child Child view being dragged + * @param top Attempted motion along the Y axis + * @param dy Proposed change in position for top + * @return The new clamped position for top + */ + public int clampViewPositionVertical(final View child, final int top, final int dy) { + return 0; + } + + /** + * Called to determine the Z-order of child views. + * + * @param index the ordered position to query for + * @return index of the view that should be ordered at position + * index + */ + public int getOrderedChildIndex(final int index) { + return index; + } + + /** + * Return the magnitude of a draggable child view's horizontal range of + * motion in pixels. This method should return 0 for views that cannot + * move horizontally. + * + * @param child Child view to check + * @return range of horizontal motion in pixels + */ + public int getViewHorizontalDragRange(final View child) { + return 0; + } + + /** + * Return the magnitude of a draggable child view's vertical range of + * motion in pixels. This method should return 0 for views that cannot + * move vertically. + * + * @param child Child view to check + * @return range of vertical motion in pixels + */ + public int getViewVerticalDragRange(final View child) { + return 0; + } + + /** + * Called when the user has started a deliberate drag away from one of + * the subscribed edges in the parent view while no child view is + * currently captured. + * + * @param edgeFlags A combination of edge flags describing the edge(s) + * dragged + * @param pointerId ID of the pointer touching the described edge(s) + * @see #EDGE_LEFT + * @see #EDGE_TOP + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void onEdgeDragStarted(final int edgeFlags, final int pointerId) { + } + + /** + * Called when the given edge may become locked. This can happen if an + * edge drag was preliminarily rejected before beginning, but after + * {@link #onEdgeTouched(int, int)} was called. This method should + * return true to lock this edge or false to leave it unlocked. The + * default behavior is to leave edges unlocked. + * + * @param edgeFlags A combination of edge flags describing the edge(s) + * locked + * @return true to lock the edge, false to leave it unlocked + */ + public boolean onEdgeLock(final int edgeFlags) { + return false; + } + + /** + * Called when one of the subscribed edges in the parent view has been + * touched by the user while no child view is currently captured. + * + * @param edgeFlags A combination of edge flags describing the edge(s) + * currently touched + * @param pointerId ID of the pointer touching the described edge(s) + * @see #EDGE_LEFT + * @see #EDGE_TOP + * @see #EDGE_RIGHT + * @see #EDGE_BOTTOM + */ + public void onEdgeTouched(final int edgeFlags, final int pointerId) { + } + + /** + * Called when a child view is captured for dragging or settling. The ID + * of the pointer currently dragging the captured view is supplied. If + * activePointerId is identified as {@link #INVALID_POINTER} the capture + * is programmatic instead of pointer-initiated. + * + * @param capturedChild Child view that was captured + * @param activePointerId Pointer id tracking the child capture + */ + public void onViewCaptured(final View capturedChild, final int activePointerId) { + } + + /** + * Called when the drag state changes. See the STATE_* + * constants for more information. + * + * @param state The new drag state + * @see #STATE_IDLE + * @see #STATE_DRAGGING + * @see #STATE_SETTLING + */ + public void onViewDragStateChanged(final int state) { + } + + /** + * Called when the captured view's position changes as the result of a + * drag or settle. + * + * @param changedView View whose position changed + * @param left New X coordinate of the left edge of the view + * @param top New Y coordinate of the top edge of the view + * @param dx Change in X position from the last call + * @param dy Change in Y position from the last call + */ + public void onViewPositionChanged(final View changedView, final int left, final int top, final int dx, + final int dy) { + } + + /** + * Called when the child view is no longer being actively dragged. The + * fling velocity is also supplied, if relevant. The velocity values may + * be clamped to system minimums or maximums. + *

+ * Calling code may decide to fling or otherwise release the view to let + * it settle into place. It should do so using + * {@link #settleCapturedViewAt(int, int)} or + * {@link #flingCapturedView(int, int, int, int)}. If the Callback + * invokes one of these methods, the ViewDragHelper will enter + * {@link #STATE_SETTLING} and the view capture will not fully end until + * it comes to a complete stop. If neither of these methods is invoked + * before onViewReleased returns, the view will stop in + * place and the ViewDragHelper will return to {@link #STATE_IDLE}. + *

+ * + * @param releasedChild The captured child view now being released + * @param xvel X velocity of the pointer as it left the screen in pixels + * per second. + * @param yvel Y velocity of the pointer as it left the screen in pixels + * per second. + */ + public void onViewReleased(final View releasedChild, final float xvel, final float yvel) { + } + + /** + * Called when the user's input indicates that they want to capture the + * given child view with the pointer indicated by pointerId. The + * callback should return true if the user is permitted to drag the + * given view with the indicated pointer. + *

+ * ViewDragHelper may call this method multiple times for the same view + * even if the view is already captured; this indicates that a new + * pointer is trying to take control of the view. + *

+ *

+ * If this method returns true, a call to + * {@link #onViewCaptured(android.view.View, int)} will follow if the + * capture is successful. + *

+ * + * @param child Child the user is attempting to capture + * @param pointerId ID of the pointer attempting the capture + * @return true if capture should be allowed, false otherwise + */ + public abstract boolean tryCaptureView(View child, int pointerId); + } +} diff --git a/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivity.java b/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivity.java new file mode 100644 index 000000000..a3170e1eb --- /dev/null +++ b/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivity.java @@ -0,0 +1,55 @@ +package me.imid.swipebacklayout.lib.app; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.view.View; + +import me.imid.swipebacklayout.lib.SwipeBackLayout; + +@SuppressLint("Registered") +public class SwipeBackActivity extends FragmentActivity implements SwipeBackActivityBase { + + private SwipeBackActivityHelper mSwipebackHelper; + + @Override + public View findViewById(final int id) { + final View v = super.findViewById(id); + if (v == null && mSwipebackHelper != null) return mSwipebackHelper.findViewById(id); + return v; + } + + @Override + public SwipeBackLayout getSwipeBackLayout() { + return mSwipebackHelper.getSwipeBackLayout(); + } + + @Override + public void scrollToFinishActivity() { + getSwipeBackLayout().scrollToFinishActivity(); + } + + @Override + public void setSwipeBackEnable(final boolean enable) { + getSwipeBackLayout().setEnableGesture(enable); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mSwipebackHelper = new SwipeBackActivityHelper(this); + mSwipebackHelper.onActivtyCreate(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mSwipebackHelper.onDestroy(); + } + + @Override + protected void onPostCreate(final Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + mSwipebackHelper.onPostCreate(); + } +} diff --git a/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityBase.java b/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityBase.java new file mode 100644 index 000000000..d67c8e04e --- /dev/null +++ b/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityBase.java @@ -0,0 +1,21 @@ +package me.imid.swipebacklayout.lib.app; + +import me.imid.swipebacklayout.lib.SwipeBackLayout; + +/** + * @author Yrom + */ +public interface SwipeBackActivityBase { + /** + * @return the SwipeBackLayout associated with this activity. + */ + public abstract SwipeBackLayout getSwipeBackLayout(); + + /** + * Scroll out contentView and finish the activity + */ + public abstract void scrollToFinishActivity(); + + public abstract void setSwipeBackEnable(boolean enable); + +} diff --git a/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityHelper.java b/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityHelper.java new file mode 100644 index 000000000..e26db39c2 --- /dev/null +++ b/twidere/src/main/java/me/imid/swipebacklayout/lib/app/SwipeBackActivityHelper.java @@ -0,0 +1,67 @@ +package me.imid.swipebacklayout.lib.app; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; + +import me.imid.swipebacklayout.lib.SwipeBackLayout; + +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.TwidereConstants; +import org.mariotaku.twidere.app.TwidereApplication; +import org.mariotaku.twidere.graphic.EmptyDrawable; +import org.mariotaku.twidere.util.SwipebackActivityUtils.SwipebackScreenshotManager; + +/** + * @author Yrom + * + */ +public class SwipeBackActivityHelper implements TwidereConstants { + private final Activity mActivity; + private SwipeBackLayout mSwipeBackLayout; + + public SwipeBackActivityHelper(final Activity activity) { + mActivity = activity; + } + + public View findViewById(final int id) { + if (mSwipeBackLayout != null) return mSwipeBackLayout.findViewById(id); + return null; + } + + public SwipeBackLayout getSwipeBackLayout() { + return mSwipeBackLayout; + } + + public void onActivtyCreate() { + final Window w = mActivity.getWindow(); + w.setBackgroundDrawable(new EmptyDrawable()); + mSwipeBackLayout = (SwipeBackLayout) LayoutInflater.from(mActivity).inflate(R.layout.swipeback_layout, null); + } + + public void onDestroy() { + if (mActivity.isFinishing()) { + final Intent intent = mActivity.getIntent(); + final TwidereApplication app = TwidereApplication.getInstance(mActivity); + final SwipebackScreenshotManager sm = app.getSwipebackScreenshotManager(); + sm.remove(intent.getLongExtra(EXTRA_ACTIVITY_SCREENSHOT_ID, -1)); + } + } + + public void onPostCreate() { + mSwipeBackLayout.attachToActivity(mActivity); + final Intent intent = mActivity.getIntent(); + final TwidereApplication app = TwidereApplication.getInstance(mActivity); + final SwipebackScreenshotManager sm = app.getSwipebackScreenshotManager(); + final Bitmap b = sm.get(intent.getLongExtra(EXTRA_ACTIVITY_SCREENSHOT_ID, -1)); + if (b != null) { + mSwipeBackLayout.setWindowBackgroundDrawable(new BitmapDrawable(mActivity.getResources(), b)); + } + mSwipeBackLayout.setEnableGesture(b != null); + } + +} diff --git a/twidere/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java b/twidere/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java new file mode 100644 index 000000000..93a2e7bba --- /dev/null +++ b/twidere/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3; + +import org.apache.commons.lang3.text.translate.AggregateTranslator; +import org.apache.commons.lang3.text.translate.CharSequenceTranslator; +import org.apache.commons.lang3.text.translate.EntityArrays; +import org.apache.commons.lang3.text.translate.LookupTranslator; +import org.apache.commons.lang3.text.translate.NumericEntityUnescaper; + +/** + *

+ * Escapes and unescapes {@code String}s for Java, Java Script, HTML and XML. + *

+ *

+ * #ThreadSafe# + *

+ * + * @since 2.0 + * @version $Id: StringEscapeUtils.java 1148520 2011-07-19 20:53:23Z ggregory $ + */ +public class StringEscapeUtils { + + /* ESCAPE TRANSLATORS */ + + /** + * Translator object for escaping HTML version 3.0. While + * {@link #escapeHtml3(String)} is the expected method of use, this object + * allows the HTML escaping functionality to be used as the foundation for a + * custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_HTML3 = new AggregateTranslator(new LookupTranslator( + EntityArrays.BASIC_ESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE())); + + /** + * Translator object for escaping HTML version 4.0. While + * {@link #escapeHtml4(String)} is the expected method of use, this object + * allows the HTML escaping functionality to be used as the foundation for a + * custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator ESCAPE_HTML4 = new AggregateTranslator(new LookupTranslator( + EntityArrays.BASIC_ESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()), new LookupTranslator( + EntityArrays.HTML40_EXTENDED_ESCAPE())); + + /* UNESCAPE TRANSLATORS */ + + /** + * Translator object for unescaping escaped HTML 3.0. While + * {@link #unescapeHtml3(String)} is the expected method of use, this object + * allows the HTML unescaping functionality to be used as the foundation for + * a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_HTML3 = new AggregateTranslator(new LookupTranslator( + EntityArrays.BASIC_UNESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()), + new NumericEntityUnescaper()); + + /** + * Translator object for unescaping escaped HTML 4.0. While + * {@link #unescapeHtml4(String)} is the expected method of use, this object + * allows the HTML unescaping functionality to be used as the foundation for + * a custom translator. + * + * @since 3.0 + */ + public static final CharSequenceTranslator UNESCAPE_HTML4 = new AggregateTranslator(new LookupTranslator( + EntityArrays.BASIC_UNESCAPE()), new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()), + new LookupTranslator(EntityArrays.HTML40_EXTENDED_UNESCAPE()), new NumericEntityUnescaper()); + + /* Helper functions */ + + /** + *

+ * {@code StringEscapeUtils} instances should NOT be constructed in standard + * programming. + *

+ *

+ * Instead, the class should be used as: + * + *

+	 * StringEscapeUtils.escapeJava("foo");
+	 * 
+ * + *

+ *

+ * This constructor is public to permit tools that require a JavaBean + * instance to operate. + *

+ */ + public StringEscapeUtils() { + super(); + } + + /** + *

+ * Escapes the characters in a {@code String} using HTML entities. + *

+ *

+ * Supports only the HTML 3.0 entities. + *

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @since 3.0 + */ + public static final String escapeHtml3(final String input) { + return ESCAPE_HTML3.translate(input); + } + + // HTML and XML + // -------------------------------------------------------------------------- + /** + *

+ * Escapes the characters in a {@code String} using HTML entities. + *

+ *

+ * For example: + *

+ *

+ * "bread" & "butter" + *

+ * becomes: + *

+ * &quot;bread&quot; &amp; &quot;butter&quot; + * . + *

+ *

+ * Supports all known HTML 4.0 entities, including funky accents. Note that + * the commonly used apostrophe escape character (&apos;) is not a legal + * entity and so is not supported). + *

+ * + * @param input the {@code String} to escape, may be null + * @return a new escaped {@code String}, {@code null} if null string input + * @see ISO + * Entities + * @see HTML 3.2 Character + * Entities for ISO Latin-1 + * @see HTML + * 4.0 Character entity references + * @see HTML 4.01 + * Character References + * @see HTML + * 4.01 Code positions + * @since 3.0 + */ + public static final String escapeHtml4(final String input) { + return ESCAPE_HTML4.translate(input); + } + + /** + *

+ * Unescapes a string containing entity escapes to a string containing the + * actual Unicode characters corresponding to the escapes. Supports only + * HTML 3.0 entities. + *

+ * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * @since 3.0 + */ + public static final String unescapeHtml3(final String input) { + return UNESCAPE_HTML3.translate(input); + } + + // ----------------------------------------------------------------------- + /** + *

+ * Unescapes a string containing entity escapes to a string containing the + * actual Unicode characters corresponding to the escapes. Supports HTML 4.0 + * entities. + *

+ *

+ * For example, the string "&lt;Fran&ccedil;ais&gt;" will become + * "<Français>" + *

+ *

+ * If an entity is unrecognized, it is left alone, and inserted verbatim + * into the result string. e.g. "&gt;&zzzz;x" will become + * ">&zzzz;x". + *

+ * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * @since 3.0 + */ + public static final String unescapeHtml4(final String input) { + return UNESCAPE_HTML4.translate(input); + } + +} diff --git a/twidere/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java b/twidere/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java new file mode 100644 index 000000000..58b858cb4 --- /dev/null +++ b/twidere/src/main/java/org/apache/commons/lang3/text/translate/AggregateTranslator.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; + +/** + * Executes a sequence of translators one after the other. Execution ends + * whenever the first translator consumes codepoints from the input. + * + * @since 3.0 + * @version $Id: AggregateTranslator.java 1088899 2011-04-05 05:31:27Z bayard $ + */ +public class AggregateTranslator extends CharSequenceTranslator { + + private final CharSequenceTranslator[] translators; + + /** + * Specify the translators to be used at creation time. + * + * @param translators CharSequenceTranslator array to aggregate + */ + public AggregateTranslator(final CharSequenceTranslator... translators) { + this.translators = clone(translators); + } + + /** + * The first translator to consume codepoints from the input is the + * 'winner'. Execution stops with the number of consumed codepoints being + * returned. {@inheritDoc} + */ + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + for (final CharSequenceTranslator translator : translators) { + final int consumed = translator.translate(input, index, out); + if (consumed != 0) return consumed; + } + return 0; + } + + /** + *

+ * Shallow clones an array returning a typecast result and handling + * {@code null}. + *

+ *

+ * The objects in the array are not cloned, thus there is no special + * handling for multi-dimensional arrays. + *

+ *

+ * This method returns {@code null} for a {@code null} input array. + *

+ * + * @param the component type of the array + * @param array the array to shallow clone, may be {@code null} + * @return the cloned array, {@code null} if {@code null} input + */ + private static T[] clone(final T[] array) { + if (array == null) return null; + return array.clone(); + } + +} diff --git a/twidere/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java b/twidere/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java new file mode 100644 index 000000000..2432daadc --- /dev/null +++ b/twidere/src/main/java/org/apache/commons/lang3/text/translate/CharSequenceTranslator.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Locale; + +/** + * An API for translating text. Its core use is to escape and unescape text. + * Because escaping and unescaping is completely contextual, the API does not + * present two separate signatures. + * + * @since 3.0 + * @version $Id: CharSequenceTranslator.java 1146844 2011-07-14 18:49:51Z + * mbenson $ + */ +public abstract class CharSequenceTranslator { + + /** + * Helper for non-Writer usage. + * + * @param input CharSequence to be translated + * @return String output of translation + */ + public final String translate(final CharSequence input) { + if (input == null) return null; + try { + final StringWriter writer = new StringWriter(input.length() * 2); + translate(input, writer); + return writer.toString(); + } catch (final IOException ioe) { + // this should never ever happen while writing to a StringWriter + throw new RuntimeException(ioe); + } + } + + /** + * Translate a set of codepoints, represented by an int index into a + * CharSequence, into another set of codepoints. The number of codepoints + * consumed must be returned, and the only IOExceptions thrown must be from + * interacting with the Writer so that the top level API may reliable ignore + * StringWriter IOExceptions. + * + * @param input CharSequence that is being translated + * @param index int representing the current point of translation + * @param out Writer to translate the text to + * @return int count of codepoints consumed + * @throws IOException if and only if the Writer produces an IOException + */ + public abstract int translate(CharSequence input, int index, Writer out) throws IOException; + + /** + * Translate an input onto a Writer. This is intentionally final as its + * algorithm is tightly coupled with the abstract method of this class. + * + * @param input CharSequence that is being translated + * @param out Writer to translate the text to + * @throws IOException if and only if the Writer produces an IOException + */ + public final void translate(final CharSequence input, final Writer out) throws IOException { + if (out == null) throw new IllegalArgumentException("The Writer must not be null"); + if (input == null) return; + int pos = 0; + final int len = input.length(); + while (pos < len) { + final int consumed = translate(input, pos, out); + if (consumed == 0) { + final char[] c = Character.toChars(Character.codePointAt(input, pos)); + out.write(c); + pos += c.length; + continue; + } + // // contract with translators is that they have to understand + // codepoints + // // and they just took care of a surrogate pair + for (int pt = 0; pt < consumed; pt++) { + pos += Character.charCount(Character.codePointAt(input, pos)); + } + } + } + + /** + * Helper method to create a merger of this translator with another set of + * translators. Useful in customizing the standard functionality. + * + * @param translators CharSequenceTranslator array of translators to merge + * with this one + * @return CharSequenceTranslator merging this translator with the others + */ + public final CharSequenceTranslator with(final CharSequenceTranslator... translators) { + final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1]; + newArray[0] = this; + System.arraycopy(translators, 0, newArray, 1, translators.length); + return new AggregateTranslator(newArray); + } + + /** + *

+ * Returns an upper case hexadecimal String for the given + * character. + *

+ * + * @param codepoint The codepoint to convert. + * @return An upper case hexadecimal String + */ + public static String hex(final int codepoint) { + return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH); + } + +} diff --git a/twidere/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java b/twidere/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java new file mode 100644 index 000000000..bec11d86d --- /dev/null +++ b/twidere/src/main/java/org/apache/commons/lang3/text/translate/EntityArrays.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.text.translate; + +/** + * Class holding various entity data for HTML and XML - generally for use with + * the LookupTranslator. All arrays are of length [*][2]. + * + * @since 3.0 + * @version $Id: EntityArrays.java 1088899 2011-04-05 05:31:27Z bayard $ + */ +public class EntityArrays { + + private static final String[][] ISO8859_1_ESCAPE = { { "\u00A0", " " }, // non-breaking + // space + { "\u00A1", "¡" }, // inverted exclamation mark + { "\u00A2", "¢" }, // cent sign + { "\u00A3", "£" }, // pound sign + { "\u00A4", "¤" }, // currency sign + { "\u00A5", "¥" }, // yen sign = yuan sign + { "\u00A6", "¦" }, // broken bar = broken vertical bar + { "\u00A7", "§" }, // section sign + { "\u00A8", "¨" }, // diaeresis = spacing diaeresis + { "\u00A9", "©" }, // © - copyright sign + { "\u00AA", "ª" }, // feminine ordinal indicator + { "\u00AB", "«" }, // left-pointing double angle quotation + // mark = left pointing guillemet + { "\u00AC", "¬" }, // not sign + { "\u00AD", "­" }, // soft hyphen = discretionary hyphen + { "\u00AE", "®" }, // ® - registered trademark sign + { "\u00AF", "¯" }, // macron = spacing macron = overline = APL + // overbar + { "\u00B0", "°" }, // degree sign + { "\u00B1", "±" }, // plus-minus sign = plus-or-minus sign + { "\u00B2", "²" }, // superscript two = superscript digit two = + // squared + { "\u00B3", "³" }, // superscript three = superscript digit + // three = cubed + { "\u00B4", "´" }, // acute accent = spacing acute + { "\u00B5", "µ" }, // micro sign + { "\u00B6", "¶" }, // pilcrow sign = paragraph sign + { "\u00B7", "·" }, // middle dot = Georgian comma = Greek + // middle dot + { "\u00B8", "¸" }, // cedilla = spacing cedilla + { "\u00B9", "¹" }, // superscript one = superscript digit one + { "\u00BA", "º" }, // masculine ordinal indicator + { "\u00BB", "»" }, // right-pointing double angle quotation + // mark = right pointing guillemet + { "\u00BC", "¼" }, // vulgar fraction one quarter = fraction + // one quarter + { "\u00BD", "½" }, // vulgar fraction one half = fraction one + // half + { "\u00BE", "¾" }, // vulgar fraction three quarters = + // fraction three quarters + { "\u00BF", "¿" }, // inverted question mark = turned + // question mark + { "\u00C0", "À" }, // À - uppercase A, grave accent + { "\u00C1", "Á" }, // Á - uppercase A, acute accent + { "\u00C2", "Â" }, //  - uppercase A, circumflex accent + { "\u00C3", "Ã" }, // à - uppercase A, tilde + { "\u00C4", "Ä" }, // Ä - uppercase A, umlaut + { "\u00C5", "Å" }, // Å - uppercase A, ring + { "\u00C6", "Æ" }, // Æ - uppercase AE + { "\u00C7", "Ç" }, // Ç - uppercase C, cedilla + { "\u00C8", "È" }, // È - uppercase E, grave accent + { "\u00C9", "É" }, // É - uppercase E, acute accent + { "\u00CA", "Ê" }, // Ê - uppercase E, circumflex accent + { "\u00CB", "Ë" }, // Ë - uppercase E, umlaut + { "\u00CC", "Ì" }, // Ì - uppercase I, grave accent + { "\u00CD", "Í" }, // Í - uppercase I, acute accent + { "\u00CE", "Î" }, // Î - uppercase I, circumflex accent + { "\u00CF", "Ï" }, // Ï - uppercase I, umlaut + { "\u00D0", "Ð" }, // Ð - uppercase Eth, Icelandic + { "\u00D1", "Ñ" }, // Ñ - uppercase N, tilde + { "\u00D2", "Ò" }, // Ò - uppercase O, grave accent + { "\u00D3", "Ó" }, // Ó - uppercase O, acute accent + { "\u00D4", "Ô" }, // Ô - uppercase O, circumflex accent + { "\u00D5", "Õ" }, // Õ - uppercase O, tilde + { "\u00D6", "Ö" }, // Ö - uppercase O, umlaut + { "\u00D7", "×" }, // multiplication sign + { "\u00D8", "Ø" }, // Ø - uppercase O, slash + { "\u00D9", "Ù" }, // Ù - uppercase U, grave accent + { "\u00DA", "Ú" }, // Ú - uppercase U, acute accent + { "\u00DB", "Û" }, // Û - uppercase U, circumflex accent + { "\u00DC", "Ü" }, // Ü - uppercase U, umlaut + { "\u00DD", "Ý" }, // Ý - uppercase Y, acute accent + { "\u00DE", "Þ" }, // Þ - uppercase THORN, Icelandic + { "\u00DF", "ß" }, // ß - lowercase sharps, German + { "\u00E0", "à" }, // à - lowercase a, grave accent + { "\u00E1", "á" }, // á - lowercase a, acute accent + { "\u00E2", "â" }, // â - lowercase a, circumflex accent + { "\u00E3", "ã" }, // ã - lowercase a, tilde + { "\u00E4", "ä" }, // ä - lowercase a, umlaut + { "\u00E5", "å" }, // å - lowercase a, ring + { "\u00E6", "æ" }, // æ - lowercase ae + { "\u00E7", "ç" }, // ç - lowercase c, cedilla + { "\u00E8", "è" }, // è - lowercase e, grave accent + { "\u00E9", "é" }, // é - lowercase e, acute accent + { "\u00EA", "ê" }, // ê - lowercase e, circumflex accent + { "\u00EB", "ë" }, // ë - lowercase e, umlaut + { "\u00EC", "ì" }, // ì - lowercase i, grave accent + { "\u00ED", "í" }, // í - lowercase i, acute accent + { "\u00EE", "î" }, // î - lowercase i, circumflex accent + { "\u00EF", "ï" }, // ï - lowercase i, umlaut + { "\u00F0", "ð" }, // ð - lowercase eth, Icelandic + { "\u00F1", "ñ" }, // ñ - lowercase n, tilde + { "\u00F2", "ò" }, // ò - lowercase o, grave accent + { "\u00F3", "ó" }, // ó - lowercase o, acute accent + { "\u00F4", "ô" }, // ô - lowercase o, circumflex accent + { "\u00F5", "õ" }, // õ - lowercase o, tilde + { "\u00F6", "ö" }, // ö - lowercase o, umlaut + { "\u00F7", "÷" }, // division sign + { "\u00F8", "ø" }, // ø - lowercase o, slash + { "\u00F9", "ù" }, // ù - lowercase u, grave accent + { "\u00FA", "ú" }, // ú - lowercase u, acute accent + { "\u00FB", "û" }, // û - lowercase u, circumflex accent + { "\u00FC", "ü" }, // ü - lowercase u, umlaut + { "\u00FD", "ý" }, // ý - lowercase y, acute accent + { "\u00FE", "þ" }, // þ - lowercase thorn, Icelandic + { "\u00FF", "ÿ" }, // ÿ - lowercase y, umlaut + }; + private static final String[][] ISO8859_1_UNESCAPE = invert(ISO8859_1_ESCAPE); + + private static final String[][] HTML40_EXTENDED_ESCAPE = { + // + { "\u0192", "ƒ" }, // latin small f with hook = function= + // florin, U+0192 ISOtech --> + // + { "\u0391", "Α" }, // greek capital letter alpha, U+0391 --> + { "\u0392", "Β" }, // greek capital letter beta, U+0392 --> + { "\u0393", "Γ" }, // greek capital letter gamma,U+0393 + // ISOgrk3 --> + { "\u0394", "Δ" }, // greek capital letter delta,U+0394 + // ISOgrk3 --> + { "\u0395", "Ε" }, // greek capital letter epsilon, U+0395 + // --> + { "\u0396", "Ζ" }, // greek capital letter zeta, U+0396 --> + { "\u0397", "Η" }, // greek capital letter eta, U+0397 --> + { "\u0398", "Θ" }, // greek capital letter theta,U+0398 + // ISOgrk3 --> + { "\u0399", "Ι" }, // greek capital letter iota, U+0399 --> + { "\u039A", "Κ" }, // greek capital letter kappa, U+039A --> + { "\u039B", "Λ" }, // greek capital letter lambda,U+039B + // ISOgrk3 --> + { "\u039C", "Μ" }, // greek capital letter mu, U+039C --> + { "\u039D", "Ν" }, // greek capital letter nu, U+039D --> + { "\u039E", "Ξ" }, // greek capital letter xi, U+039E ISOgrk3 --> + { "\u039F", "Ο" }, // greek capital letter omicron, U+039F + // --> + { "\u03A0", "Π" }, // greek capital letter pi, U+03A0 ISOgrk3 --> + { "\u03A1", "Ρ" }, // greek capital letter rho, U+03A1 --> + // + { "\u03A3", "Σ" }, // greek capital letter sigma,U+03A3 + // ISOgrk3 --> + { "\u03A4", "Τ" }, // greek capital letter tau, U+03A4 --> + { "\u03A5", "Υ" }, // greek capital letter upsilon,U+03A5 + // ISOgrk3 --> + { "\u03A6", "Φ" }, // greek capital letter phi,U+03A6 ISOgrk3 + // --> + { "\u03A7", "Χ" }, // greek capital letter chi, U+03A7 --> + { "\u03A8", "Ψ" }, // greek capital letter psi,U+03A8 ISOgrk3 + // --> + { "\u03A9", "Ω" }, // greek capital letter omega,U+03A9 + // ISOgrk3 --> + { "\u03B1", "α" }, // greek small letter alpha,U+03B1 ISOgrk3 + // --> + { "\u03B2", "β" }, // greek small letter beta, U+03B2 ISOgrk3 + // --> + { "\u03B3", "γ" }, // greek small letter gamma,U+03B3 ISOgrk3 + // --> + { "\u03B4", "δ" }, // greek small letter delta,U+03B4 ISOgrk3 + // --> + { "\u03B5", "ε" }, // greek small letter epsilon,U+03B5 + // ISOgrk3 --> + { "\u03B6", "ζ" }, // greek small letter zeta, U+03B6 ISOgrk3 + // --> + { "\u03B7", "η" }, // greek small letter eta, U+03B7 ISOgrk3 --> + { "\u03B8", "θ" }, // greek small letter theta,U+03B8 ISOgrk3 + // --> + { "\u03B9", "ι" }, // greek small letter iota, U+03B9 ISOgrk3 + // --> + { "\u03BA", "κ" }, // greek small letter kappa,U+03BA ISOgrk3 + // --> + { "\u03BB", "λ" }, // greek small letter lambda,U+03BB + // ISOgrk3 --> + { "\u03BC", "μ" }, // greek small letter mu, U+03BC ISOgrk3 --> + { "\u03BD", "ν" }, // greek small letter nu, U+03BD ISOgrk3 --> + { "\u03BE", "ξ" }, // greek small letter xi, U+03BE ISOgrk3 --> + { "\u03BF", "ο" }, // greek small letter omicron, U+03BF NEW + // --> + { "\u03C0", "π" }, // greek small letter pi, U+03C0 ISOgrk3 --> + { "\u03C1", "ρ" }, // greek small letter rho, U+03C1 ISOgrk3 --> + { "\u03C2", "ς" }, // greek small letter final sigma,U+03C2 + // ISOgrk3 --> + { "\u03C3", "σ" }, // greek small letter sigma,U+03C3 ISOgrk3 + // --> + { "\u03C4", "τ" }, // greek small letter tau, U+03C4 ISOgrk3 --> + { "\u03C5", "υ" }, // greek small letter upsilon,U+03C5 + // ISOgrk3 --> + { "\u03C6", "φ" }, // greek small letter phi, U+03C6 ISOgrk3 --> + { "\u03C7", "χ" }, // greek small letter chi, U+03C7 ISOgrk3 --> + { "\u03C8", "ψ" }, // greek small letter psi, U+03C8 ISOgrk3 --> + { "\u03C9", "ω" }, // greek small letter omega,U+03C9 ISOgrk3 + // --> + { "\u03D1", "ϑ" }, // greek small letter theta + // symbol,U+03D1 NEW --> + { "\u03D2", "ϒ" }, // greek upsilon with hook symbol,U+03D2 + // NEW --> + { "\u03D6", "ϖ" }, // greek pi symbol, U+03D6 ISOgrk3 --> + // + { "\u2022", "•" }, // bullet = black small circle,U+2022 ISOpub + // --> + // + { "\u2026", "…" }, // horizontal ellipsis = three dot + // leader,U+2026 ISOpub --> + { "\u2032", "′" }, // prime = minutes = feet, U+2032 ISOtech + // --> + { "\u2033", "″" }, // double prime = seconds = inches,U+2033 + // ISOtech --> + { "\u203E", "‾" }, // overline = spacing overscore,U+203E NEW + // --> + { "\u2044", "⁄" }, // fraction slash, U+2044 NEW --> + // + { "\u2118", "℘" }, // script capital P = power set= + // Weierstrass p, U+2118 ISOamso --> + { "\u2111", "ℑ" }, // blackletter capital I = imaginary + // part,U+2111 ISOamso --> + { "\u211C", "ℜ" }, // blackletter capital R = real part + // symbol,U+211C ISOamso --> + { "\u2122", "™" }, // trade mark sign, U+2122 ISOnum --> + { "\u2135", "ℵ" }, // alef symbol = first transfinite + // cardinal,U+2135 NEW --> + // + // + { "\u2190", "←" }, // leftwards arrow, U+2190 ISOnum --> + { "\u2191", "↑" }, // upwards arrow, U+2191 ISOnum--> + { "\u2192", "→" }, // rightwards arrow, U+2192 ISOnum --> + { "\u2193", "↓" }, // downwards arrow, U+2193 ISOnum --> + { "\u2194", "↔" }, // left right arrow, U+2194 ISOamsa --> + { "\u21B5", "↵" }, // downwards arrow with corner leftwards= + // carriage return, U+21B5 NEW --> + { "\u21D0", "⇐" }, // leftwards double arrow, U+21D0 ISOtech + // --> + // + { "\u21D1", "⇑" }, // upwards double arrow, U+21D1 ISOamsa --> + { "\u21D2", "⇒" }, // rightwards double arrow,U+21D2 ISOtech + // --> + // + { "\u21D3", "⇓" }, // downwards double arrow, U+21D3 ISOamsa + // --> + { "\u21D4", "⇔" }, // left right double arrow,U+21D4 ISOamsa + // --> + // + { "\u2200", "∀" }, // for all, U+2200 ISOtech --> + { "\u2202", "∂" }, // partial differential, U+2202 ISOtech --> + { "\u2203", "∃" }, // there exists, U+2203 ISOtech --> + { "\u2205", "∅" }, // empty set = null set = diameter,U+2205 + // ISOamso --> + { "\u2207", "∇" }, // nabla = backward difference,U+2207 + // ISOtech --> + { "\u2208", "∈" }, // element of, U+2208 ISOtech --> + { "\u2209", "∉" }, // not an element of, U+2209 ISOtech --> + { "\u220B", "∋" }, // contains as member, U+220B ISOtech --> + // + { "\u220F", "∏" }, // n-ary product = product sign,U+220F + // ISOamsb --> + // + { "\u2211", "∑" }, // n-ary summation, U+2211 ISOamsb --> + // + { "\u2212", "−" }, // minus sign, U+2212 ISOtech --> + { "\u2217", "∗" }, // asterisk operator, U+2217 ISOtech --> + { "\u221A", "√" }, // square root = radical sign,U+221A + // ISOtech --> + { "\u221D", "∝" }, // proportional to, U+221D ISOtech --> + { "\u221E", "∞" }, // infinity, U+221E ISOtech --> + { "\u2220", "∠" }, // angle, U+2220 ISOamso --> + { "\u2227", "∧" }, // logical and = wedge, U+2227 ISOtech --> + { "\u2228", "∨" }, // logical or = vee, U+2228 ISOtech --> + { "\u2229", "∩" }, // intersection = cap, U+2229 ISOtech --> + { "\u222A", "∪" }, // union = cup, U+222A ISOtech --> + { "\u222B", "∫" }, // integral, U+222B ISOtech --> + { "\u2234", "∴" }, // therefore, U+2234 ISOtech --> + { "\u223C", "∼" }, // tilde operator = varies with = similar + // to,U+223C ISOtech --> + // + { "\u2245", "≅" }, // approximately equal to, U+2245 ISOtech + // --> + { "\u2248", "≈" }, // almost equal to = asymptotic to,U+2248 + // ISOamsr --> + { "\u2260", "≠" }, // not equal to, U+2260 ISOtech --> + { "\u2261", "≡" }, // identical to, U+2261 ISOtech --> + { "\u2264", "≤" }, // less-than or equal to, U+2264 ISOtech --> + { "\u2265", "≥" }, // greater-than or equal to,U+2265 ISOtech --> + { "\u2282", "⊂" }, // subset of, U+2282 ISOtech --> + { "\u2283", "⊃" }, // superset of, U+2283 ISOtech --> + // + { "\u2286", "⊆" }, // subset of or equal to, U+2286 ISOtech --> + { "\u2287", "⊇" }, // superset of or equal to,U+2287 ISOtech + // --> + { "\u2295", "⊕" }, // circled plus = direct sum,U+2295 ISOamsb + // --> + { "\u2297", "⊗" }, // circled times = vector product,U+2297 + // ISOamsb --> + { "\u22A5", "⊥" }, // up tack = orthogonal to = + // perpendicular,U+22A5 ISOtech --> + { "\u22C5", "⋅" }, // dot operator, U+22C5 ISOamsb --> + // + // + { "\u2308", "⌈" }, // left ceiling = apl upstile,U+2308 + // ISOamsc --> + { "\u2309", "⌉" }, // right ceiling, U+2309 ISOamsc --> + { "\u230A", "⌊" }, // left floor = apl downstile,U+230A + // ISOamsc --> + { "\u230B", "⌋" }, // right floor, U+230B ISOamsc --> + { "\u2329", "⟨" }, // left-pointing angle bracket = bra,U+2329 + // ISOtech --> + // + { "\u232A", "⟩" }, // right-pointing angle bracket = ket,U+232A + // ISOtech --> + // + // + { "\u25CA", "◊" }, // lozenge, U+25CA ISOpub --> + // + { "\u2660", "♠" }, // black spade suit, U+2660 ISOpub --> + // + { "\u2663", "♣" }, // black club suit = shamrock,U+2663 ISOpub + // --> + { "\u2665", "♥" }, // black heart suit = valentine,U+2665 + // ISOpub --> + { "\u2666", "♦" }, // black diamond suit, U+2666 ISOpub --> + + // + { "\u0152", "Œ" }, // -- latin capital ligature OE,U+0152 + // ISOlat2 --> + { "\u0153", "œ" }, // -- latin small ligature oe, U+0153 + // ISOlat2 --> + // + { "\u0160", "Š" }, // -- latin capital letter S with + // caron,U+0160 ISOlat2 --> + { "\u0161", "š" }, // -- latin small letter s with + // caron,U+0161 ISOlat2 --> + { "\u0178", "Ÿ" }, // -- latin capital letter Y with + // diaeresis,U+0178 ISOlat2 --> + // + { "\u02C6", "ˆ" }, // -- modifier letter circumflex + // accent,U+02C6 ISOpub --> + { "\u02DC", "˜" }, // small tilde, U+02DC ISOdia --> + // + { "\u2002", " " }, // en space, U+2002 ISOpub --> + { "\u2003", " " }, // em space, U+2003 ISOpub --> + { "\u2009", " " }, // thin space, U+2009 ISOpub --> + { "\u200C", "‌" }, // zero width non-joiner,U+200C NEW RFC 2070 + // --> + { "\u200D", "‍" }, // zero width joiner, U+200D NEW RFC 2070 --> + { "\u200E", "‎" }, // left-to-right mark, U+200E NEW RFC 2070 + // --> + { "\u200F", "‏" }, // right-to-left mark, U+200F NEW RFC 2070 + // --> + { "\u2013", "–" }, // en dash, U+2013 ISOpub --> + { "\u2014", "—" }, // em dash, U+2014 ISOpub --> + { "\u2018", "‘" }, // left single quotation mark,U+2018 ISOnum + // --> + { "\u2019", "’" }, // right single quotation mark,U+2019 + // ISOnum --> + { "\u201A", "‚" }, // single low-9 quotation mark, U+201A NEW + // --> + { "\u201C", "“" }, // left double quotation mark,U+201C ISOnum + // --> + { "\u201D", "”" }, // right double quotation mark,U+201D + // ISOnum --> + { "\u201E", "„" }, // double low-9 quotation mark, U+201E NEW + // --> + { "\u2020", "†" }, // dagger, U+2020 ISOpub --> + { "\u2021", "‡" }, // double dagger, U+2021 ISOpub --> + { "\u2030", "‰" }, // per mille sign, U+2030 ISOtech --> + { "\u2039", "‹" }, // single left-pointing angle quotation + // mark,U+2039 ISO proposed --> + // + { "\u203A", "›" }, // single right-pointing angle quotation + // mark,U+203A ISO proposed --> + // + { "\u20AC", "€" }, // -- euro sign, U+20AC NEW --> + }; + private static final String[][] HTML40_EXTENDED_UNESCAPE = invert(HTML40_EXTENDED_ESCAPE); + + private static final String[][] BASIC_ESCAPE = { { "\"", """ }, // " - + // double-quote + { "&", "&" }, // & - ampersand + { "<", "<" }, // < - less-than + { ">", ">" }, // > - greater-than + }; + private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE); + + /** + * Mapping to escape the basic XML and HTML character entities. Namely: + * {@code " & < >} + * + * @return the mapping table + */ + public static String[][] BASIC_ESCAPE() { + return BASIC_ESCAPE.clone(); + } + + /** + * Reverse of {@link #BASIC_ESCAPE()} for unescaping purposes. + * + * @return the mapping table + */ + public static String[][] BASIC_UNESCAPE() { + return BASIC_UNESCAPE.clone(); + } + + /** + * Mapping to escape additional character + * entity references. Note that this must be used with + * {@link #ISO8859_1_ESCAPE()} to get the full list of HTML 4.0 character + * entities. + * + * @return the mapping table + */ + public static String[][] HTML40_EXTENDED_ESCAPE() { + return HTML40_EXTENDED_ESCAPE.clone(); + } + + /** + * Reverse of {@link #HTML40_EXTENDED_ESCAPE()} for unescaping purposes. + * + * @return the mapping table + */ + public static String[][] HTML40_EXTENDED_UNESCAPE() { + return HTML40_EXTENDED_UNESCAPE.clone(); + } + + /** + * Used to invert an escape array into an unescape array + * + * @param array String[][] to be inverted + * @return String[][] inverted array + */ + public static String[][] invert(final String[][] array) { + final String[][] newarray = new String[array.length][2]; + for (int i = 0; i < array.length; i++) { + newarray[i][0] = array[i][1]; + newarray[i][1] = array[i][0]; + } + return newarray; + } + + /** + * Mapping to escape ISO-8859-1 characters to their named HTML 3.x equivalents. + * + * @return the mapping table + */ + public static String[][] ISO8859_1_ESCAPE() { + return ISO8859_1_ESCAPE.clone(); + } + + /** + * Reverse of {@link #ISO8859_1_ESCAPE()} for unescaping purposes. + * + * @return the mapping table + */ + public static String[][] ISO8859_1_UNESCAPE() { + return ISO8859_1_UNESCAPE.clone(); + } + +} diff --git a/twidere/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java b/twidere/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java new file mode 100644 index 000000000..a49e6d986 --- /dev/null +++ b/twidere/src/main/java/org/apache/commons/lang3/text/translate/LookupTranslator.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; + +/** + * Translates a value using a lookup table. + * + * @since 3.0 + * @version $Id: LookupTranslator.java 1091096 2011-04-11 15:07:29Z mbenson $ + */ +public class LookupTranslator extends CharSequenceTranslator { + + private final HashMap lookupMap; + private final int shortest; + private final int longest; + + /** + * Define the lookup table to be used in translation + * + * @param lookup CharSequence[][] table of size [*][2] + */ + public LookupTranslator(final CharSequence[]... lookup) { + lookupMap = new HashMap(); + int _shortest = Integer.MAX_VALUE; + int _longest = 0; + if (lookup != null) { + for (final CharSequence[] seq : lookup) { + lookupMap.put(seq[0], seq[1]); + final int sz = seq[0].length(); + if (sz < _shortest) { + _shortest = sz; + } + if (sz > _longest) { + _longest = sz; + } + } + } + shortest = _shortest; + longest = _longest; + } + + /** + * {@inheritDoc} + */ + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + int max = longest; + if (index + longest > input.length()) { + max = input.length() - index; + } + // descend so as to get a greedy algorithm + for (int i = max; i >= shortest; i--) { + final CharSequence subSeq = input.subSequence(index, index + i); + final CharSequence result = lookupMap.get(subSeq); + if (result != null) { + out.write(result.toString()); + return i; + } + } + return 0; + } +} diff --git a/twidere/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java b/twidere/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java new file mode 100644 index 000000000..aa6f8b4ec --- /dev/null +++ b/twidere/src/main/java/org/apache/commons/lang3/text/translate/NumericEntityUnescaper.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.lang3.text.translate; + +import java.io.IOException; +import java.io.Writer; +import java.util.Arrays; +import java.util.EnumSet; + +/** + * Translate XML numeric entities of the form &#[xX]?\d+;? to the specific + * codepoint. Note that the semi-colon is optional. + * + * @since 3.0 + * @version $Id: NumericEntityUnescaper.java 1199894 2011-11-09 17:53:59Z + * ggregory $ + */ +public class NumericEntityUnescaper extends CharSequenceTranslator { + + // TODO?: Create an OptionsSet class to hide some of the conditional logic + // below + private final EnumSet