From deae5ed7430ea8856ec8b4e445cec09a264033f8 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Tue, 6 Jan 2015 23:46:07 +0800 Subject: [PATCH] started to implement new search bar improving new card ui --- settings.gradle | 7 +- .../.gitignore | 0 .../build.gradle | 0 .../proguard-rules.pro | 0 .../twidere/nyan/ApplicationTest.java | 0 .../src/main/AndroidManifest.xml | 0 .../mariotaku/twidere/nyan/NyanConstants.java | 0 .../twidere/nyan/NyanDaydreamService.java | 0 .../twidere/nyan/NyanDaydreamView.java | 0 .../twidere/nyan/NyanDrawingHelper.java | 0 .../twidere/nyan/NyanSurfaceHelper.java | 0 .../twidere/nyan/NyanWallpaperService.java | 0 .../nyan_rainbow_frame00_tile.png | Bin .../nyan_rainbow_frame01_tile.png | Bin .../nyan_rainbow_frame02_tile.png | Bin .../nyan_rainbow_frame03_tile.png | Bin .../nyan_rainbow_frame04_tile.png | Bin .../nyan_rainbow_frame05_tile.png | Bin .../nyan_rainbow_frame06_tile.png | Bin .../nyan_rainbow_frame07_tile.png | Bin .../nyan_rainbow_frame08_tile.png | Bin .../nyan_rainbow_frame09_tile.png | Bin .../nyan_rainbow_frame10_tile.png | Bin .../nyan_rainbow_frame11_tile.png | Bin .../drawable-nodpi/nyan_sakamoto_frame00.png | Bin .../drawable-nodpi/nyan_sakamoto_frame01.png | Bin .../drawable-nodpi/nyan_sakamoto_frame02.png | Bin .../drawable-nodpi/nyan_sakamoto_frame03.png | Bin .../drawable-nodpi/nyan_sakamoto_frame04.png | Bin .../drawable-nodpi/nyan_sakamoto_frame05.png | Bin .../drawable-nodpi/nyan_sakamoto_frame06.png | Bin .../drawable-nodpi/nyan_sakamoto_frame07.png | Bin .../drawable-nodpi/nyan_sakamoto_frame08.png | Bin .../drawable-nodpi/nyan_sakamoto_frame09.png | Bin .../drawable-nodpi/nyan_sakamoto_frame10.png | Bin .../drawable-nodpi/nyan_sakamoto_frame11.png | Bin .../nyan_sakamoto_santa_frame00.png | Bin .../nyan_sakamoto_santa_frame01.png | Bin .../nyan_sakamoto_santa_frame02.png | Bin .../nyan_sakamoto_santa_frame03.png | Bin .../nyan_sakamoto_santa_frame04.png | Bin .../nyan_sakamoto_santa_frame05.png | Bin .../nyan_sakamoto_santa_frame06.png | Bin .../nyan_sakamoto_santa_frame07.png | Bin .../nyan_sakamoto_santa_frame08.png | Bin .../nyan_sakamoto_santa_frame09.png | Bin .../nyan_sakamoto_santa_frame10.png | Bin .../nyan_sakamoto_santa_frame11.png | Bin .../src/main/res/drawable/nyan_sakamoto.xml | 0 .../main/res/drawable/nyan_sakamoto_santa.xml | 0 .../src/main/res/layout/nyan_daydream.xml | 0 .../src/main/res/values-land/integers.xml | 0 .../main/res/values-large-land/integers.xml | 0 .../src/main/res/values-large/integers.xml | 0 .../main/res/values-xlarge-land/integers.xml | 0 .../src/main/res/values-xlarge/integers.xml | 0 .../src/main/res/values/colors.xml | 0 .../src/main/res/values/integers.xml | 0 .../src/main/res/values/strings.xml | 0 twidere.component.viewer.media/.gitignore | 1 + twidere.component.viewer.media/build.gradle | 43 + .../libs/tileviewlib-1.0.13.jar | Bin 0 -> 103457 bytes .../proguard-rules.pro | 17 + .../viewer/imageviewer/ApplicationTest.java | 32 + .../src/main/AndroidManifest.xml | 30 + .../tileimageview/decoder/AbsTileDecoder.java | 39 + .../decoder/BitmapRegionTileDecoder.java | 188 +++ .../decoder/DummyTileDecoder.java | 71 + .../tileimageview/widget/TileImageView.java | 282 ++++ .../viewer/media/MediaViewerActivity.java | 12 +- .../src/main/res/values/strings.xml | 21 + twidere.donate.nyanwp.wear/build.gradle | 4 +- twidere.donate.nyanwp/build.gradle | 2 +- twidere/build.gradle | 3 +- twidere/src/main/AndroidManifest.xml | 14 +- ...ity.java => ImageViewerGLActivityOld.java} | 40 +- .../mariotaku/gallery3d/ui/BasicTexture.java | 6 +- .../org/mariotaku/gallery3d/ui/PhotoView.java | 1120 ++++++------- .../mariotaku/gallery3d/ui/TileImageView.java | 1441 +++++++++-------- .../gallery3d/util/GalleryUtils.java | 25 +- .../twidere/activity/SettingsActivity.java | 4 +- .../activity/support/BaseSupportActivity.java | 2 +- .../support/BaseSupportDialogActivity.java | 2 +- .../support/GlobalSearchBoxActivity.java | 85 + .../activity/support/ImagePickerActivity.java | 2 +- .../activity/support/MediaViewerActivity.java | 347 ++++ ...ivity.java => ThemedFragmentActivity.java} | 11 +- .../support/UserListSelectorActivity.java | 500 +++--- .../twidere/adapter/AbsActivitiesAdapter.java | 77 +- .../twidere/adapter/AbsStatusesAdapter.java | 4 +- .../adapter/ParcelableActivitiesAdapter.java | 6 +- .../adapter/ParcelableStatusesAdapter.java | 5 +- ...va => ParcelableUserListsListAdapter.java} | 34 +- .../adapter/ParcelableUsersAdapter.java | 10 +- .../decorator/DividerItemDecoration.java | 40 +- ...ter.java => ContentCardClickListener.java} | 2 +- .../adapter/iface/IActivitiesAdapter.java | 6 - .../adapter/iface/IContentCardAdapter.java | 3 +- .../adapter/iface/IStatusesAdapter.java | 6 +- .../twidere/fragment/BaseWebViewFragment.java | 8 +- .../support/AbsActivitiesFragment.java | 4 +- .../fragment/support/AbsStatusesFragment.java | 9 +- .../support/AccountsDashboardFragment.java | 20 +- .../support/BaseSupportWebViewFragment.java | 7 +- .../support/BaseUserListsListFragment.java | 8 +- .../fragment/support/CardBrowserFragment.java | 45 + .../support/CursorStatusesFragment.java | 5 + .../DirectMessagesConversationFragment.java | 110 +- .../support/ParcelableActivitiesFragment.java | 2 +- .../support/ParcelableStatusesFragment.java | 8 + .../support/RetweetQuoteDialogFragment.java | 15 +- .../fragment/support/StatusFragment.java | 31 +- .../support/SupportBrowserFragment.java | 7 - .../fragment/support/UserFragment.java | 4 +- .../fragment/support/UserListFragment.java | 12 +- .../UserListMembershipsListFragment.java | 10 +- .../fragment/support/UserListsFragment.java | 1 + .../support/UserListsListFragment.java | 6 +- .../{ => support}/ParcelableStatusLoader.java | 2 +- .../loader/support/TileImageLoader.java} | 39 +- .../loader/support/UserListsLoader.java | 48 +- .../preference/CardPreviewPreference.java | 2 +- .../util/BitmapUtils.java | 8 +- .../org/mariotaku/twidere/util/MathUtils.java | 22 + .../mariotaku/twidere/util/ThemeUtils.java | 17 +- .../util/TwitterCardFragmentFactory.java | 4 +- .../org/mariotaku/twidere/util/Utils.java | 16 +- .../twidere/view/ShapedImageView.java | 19 +- .../twidere/view/TabPagerIndicator.java | 2 + .../view/holder/ActivityListViewHolder.java | 59 - .../twidere/view/holder/CardViewHolder.java | 2 +- .../DirectMessageConversationViewHolder.java | 2 +- .../holder/DirectMessageEntryViewHolder.java | 2 +- .../twidere/view/holder/DraftViewHolder.java | 2 +- .../twidere/view/holder/StatusViewHolder.java | 40 +- ...wHolder.java => StatusViewListHolder.java} | 4 +- .../holder/TwoLineWithIconViewHolder.java | 2 +- ...older.java => UserListViewListHolder.java} | 44 +- ...iewHolder.java => UserViewListHolder.java} | 4 +- ...istViewHolder.java => ViewListHolder.java} | 4 +- .../src/main/java/twitter4j/Relationship.java | 140 +- .../main/java/twitter4j/SettingsUpdate.java | 84 + twidere/src/main/java/twitter4j/Twitter.java | 17 +- .../main/java/twitter4j/TwitterConstants.java | 2 + .../src/main/java/twitter4j/TwitterImpl.java | 46 +- .../java/twitter4j/api/ListsResources.java | 8 +- ...ces.java => PrivateActivityResources.java} | 2 +- .../api/PrivateDirectMessagesResources.java | 31 + ... => PrivateFriendsFollowersResources.java} | 2 +- .../java/twitter4j/api/PrivateResources.java | 5 + ...es.java => PrivateTimelinesResources.java} | 2 +- ...ources.java => PrivateTweetResources.java} | 2 +- .../twitter4j/api/UndocumentedResources.java | 5 - .../java/twitter4j/api/UsersResources.java | 1027 ++++++------ .../internal/json/RelationshipJSONImpl.java | 418 ++--- .../res/layout/activity_global_search_box.xml | 69 + .../res/layout/card_item_activity_summary.xml | 152 ++ ...=> card_item_activity_summary_compact.xml} | 0 .../layout/card_item_message_conversation.xml | 22 +- .../res/layout/card_item_status_compact.xml | 232 +++ .../res/layout/card_item_status_compat.xml | 227 --- .../res/layout/fragment_content_pages.xml | 63 +- .../layout/fragment_messages_conversation.xml | 19 + .../main/res/layout/header_status_common.xml | 17 +- .../main/res/layout/header_status_compact.xml | 29 + .../res/layout/header_user_list_details.xml | 27 +- twidere/src/main/res/layout/image_viewer.xml | 42 + .../main/res/layout/list_item_user_list.xml | 69 + .../res/layout/spinner_item_account_icon.xml | 5 +- twidere/src/main/res/values/strings.xml | 4 + twidere/src/main/res/values/themes.xml | 21 + 171 files changed, 4912 insertions(+), 3077 deletions(-) rename {twidere.nyan => twidere.component.nyan}/.gitignore (100%) rename {twidere.nyan => twidere.component.nyan}/build.gradle (100%) rename {twidere.nyan => twidere.component.nyan}/proguard-rules.pro (100%) rename {twidere.nyan => twidere.component.nyan}/src/androidTest/java/org/mariotaku/twidere/nyan/ApplicationTest.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/AndroidManifest.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/java/org/mariotaku/twidere/nyan/NyanConstants.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamService.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamView.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/java/org/mariotaku/twidere/nyan/NyanDrawingHelper.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/java/org/mariotaku/twidere/nyan/NyanSurfaceHelper.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/java/org/mariotaku/twidere/nyan/NyanWallpaperService.java (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame00_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame01_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame02_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame03_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame04_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame05_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame06_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame07_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame08_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame09_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame10_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_rainbow_frame11_tile.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame00.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame01.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame02.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame03.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame04.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame05.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame06.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame07.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame08.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame09.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame10.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_frame11.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame00.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame01.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame02.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame03.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame04.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame05.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame06.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame07.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame08.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame09.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame10.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame11.png (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable/nyan_sakamoto.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/drawable/nyan_sakamoto_santa.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/layout/nyan_daydream.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values-land/integers.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values-large-land/integers.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values-large/integers.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values-xlarge-land/integers.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values-xlarge/integers.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values/colors.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values/integers.xml (100%) rename {twidere.nyan => twidere.component.nyan}/src/main/res/values/strings.xml (100%) create mode 100644 twidere.component.viewer.media/.gitignore create mode 100644 twidere.component.viewer.media/build.gradle create mode 100644 twidere.component.viewer.media/libs/tileviewlib-1.0.13.jar create mode 100644 twidere.component.viewer.media/proguard-rules.pro create mode 100644 twidere.component.viewer.media/src/androidTest/java/org/mariotaku/twidere/viewer/imageviewer/ApplicationTest.java create mode 100644 twidere.component.viewer.media/src/main/AndroidManifest.xml create mode 100644 twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/AbsTileDecoder.java create mode 100644 twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/BitmapRegionTileDecoder.java create mode 100644 twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/DummyTileDecoder.java create mode 100644 twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/widget/TileImageView.java rename twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAboutMeAdapter.java => twidere.component.viewer.media/src/main/java/org/mariotaku/twidere/viewer/media/MediaViewerActivity.java (73%) create mode 100644 twidere.component.viewer.media/src/main/res/values/strings.xml rename twidere/src/main/java/org/mariotaku/gallery3d/{ImageViewerGLActivity.java => ImageViewerGLActivityOld.java} (91%) create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/GlobalSearchBoxActivity.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java rename twidere/src/main/java/org/mariotaku/twidere/activity/support/{BaseSupportThemedActivity.java => ThemedFragmentActivity.java} (89%) rename twidere/src/main/java/org/mariotaku/twidere/adapter/{ParcelableUserListsAdapter.java => ParcelableUserListsListAdapter.java} (80%) rename twidere/src/main/java/org/mariotaku/twidere/adapter/iface/{ICardSupportedAdapter.java => ContentCardClickListener.java} (96%) create mode 100644 twidere/src/main/java/org/mariotaku/twidere/fragment/support/CardBrowserFragment.java rename twidere/src/main/java/org/mariotaku/twidere/loader/{ => support}/ParcelableStatusLoader.java (98%) rename twidere/src/main/java/org/mariotaku/{gallery3d/GLImageLoader.java => twidere/loader/support/TileImageLoader.java} (89%) rename twidere/src/main/java/org/mariotaku/{gallery3d => twidere}/util/BitmapUtils.java (90%) delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityListViewHolder.java rename twidere/src/main/java/org/mariotaku/twidere/view/holder/{StatusListViewHolder.java => StatusViewListHolder.java} (98%) rename twidere/src/main/java/org/mariotaku/twidere/view/holder/{UserListListViewHolder.java => UserListViewListHolder.java} (51%) rename twidere/src/main/java/org/mariotaku/twidere/view/holder/{UserListViewHolder.java => UserViewListHolder.java} (96%) rename twidere/src/main/java/org/mariotaku/twidere/view/holder/{ListViewHolder.java => ViewListHolder.java} (93%) create mode 100644 twidere/src/main/java/twitter4j/SettingsUpdate.java rename twidere/src/main/java/twitter4j/api/{UndocumentedActivityResources.java => PrivateActivityResources.java} (86%) create mode 100644 twidere/src/main/java/twitter4j/api/PrivateDirectMessagesResources.java rename twidere/src/main/java/twitter4j/api/{UndocumentedFriendsFollowersResources.java => PrivateFriendsFollowersResources.java} (80%) create mode 100644 twidere/src/main/java/twitter4j/api/PrivateResources.java rename twidere/src/main/java/twitter4j/api/{UndocumentedTimelinesResources.java => PrivateTimelinesResources.java} (88%) rename twidere/src/main/java/twitter4j/api/{UndocumentedTweetResources.java => PrivateTweetResources.java} (90%) delete mode 100644 twidere/src/main/java/twitter4j/api/UndocumentedResources.java create mode 100644 twidere/src/main/res/layout/activity_global_search_box.xml create mode 100644 twidere/src/main/res/layout/card_item_activity_summary.xml rename twidere/src/main/res/layout/{list_item_activity_title_summary.xml => card_item_activity_summary_compact.xml} (100%) create mode 100644 twidere/src/main/res/layout/card_item_status_compact.xml delete mode 100644 twidere/src/main/res/layout/card_item_status_compat.xml create mode 100644 twidere/src/main/res/layout/header_status_compact.xml create mode 100644 twidere/src/main/res/layout/image_viewer.xml create mode 100644 twidere/src/main/res/layout/list_item_user_list.xml diff --git a/settings.gradle b/settings.gradle index c7290ea6f..c0bb430cf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,9 @@ -include ':twidere', ':twidere.wear', ':twidere.donate.nyanwp', ':twidere.nyan', ':twidere.donate.nyanwp.wear' +include ':twidere' +include ':twidere.wear' +include ':twidere.donate.nyanwp' +include ':twidere.donate.nyanwp.wear' +include ':twidere.component.nyan' +include ':twidere.component.viewer.media' include ':SlidingMenu', ':DragSortListView', ':MenuComponent', ':RefreshNow', ':PullToRefresh', ':MessageBubbleView' project(':SlidingMenu').projectDir = file('libraries/SlidingMenu/library') diff --git a/twidere.nyan/.gitignore b/twidere.component.nyan/.gitignore similarity index 100% rename from twidere.nyan/.gitignore rename to twidere.component.nyan/.gitignore diff --git a/twidere.nyan/build.gradle b/twidere.component.nyan/build.gradle similarity index 100% rename from twidere.nyan/build.gradle rename to twidere.component.nyan/build.gradle diff --git a/twidere.nyan/proguard-rules.pro b/twidere.component.nyan/proguard-rules.pro similarity index 100% rename from twidere.nyan/proguard-rules.pro rename to twidere.component.nyan/proguard-rules.pro diff --git a/twidere.nyan/src/androidTest/java/org/mariotaku/twidere/nyan/ApplicationTest.java b/twidere.component.nyan/src/androidTest/java/org/mariotaku/twidere/nyan/ApplicationTest.java similarity index 100% rename from twidere.nyan/src/androidTest/java/org/mariotaku/twidere/nyan/ApplicationTest.java rename to twidere.component.nyan/src/androidTest/java/org/mariotaku/twidere/nyan/ApplicationTest.java diff --git a/twidere.nyan/src/main/AndroidManifest.xml b/twidere.component.nyan/src/main/AndroidManifest.xml similarity index 100% rename from twidere.nyan/src/main/AndroidManifest.xml rename to twidere.component.nyan/src/main/AndroidManifest.xml diff --git a/twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanConstants.java b/twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanConstants.java similarity index 100% rename from twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanConstants.java rename to twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanConstants.java diff --git a/twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamService.java b/twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamService.java similarity index 100% rename from twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamService.java rename to twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamService.java diff --git a/twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamView.java b/twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamView.java similarity index 100% rename from twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamView.java rename to twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDaydreamView.java diff --git a/twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDrawingHelper.java b/twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDrawingHelper.java similarity index 100% rename from twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDrawingHelper.java rename to twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanDrawingHelper.java diff --git a/twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanSurfaceHelper.java b/twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanSurfaceHelper.java similarity index 100% rename from twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanSurfaceHelper.java rename to twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanSurfaceHelper.java diff --git a/twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanWallpaperService.java b/twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanWallpaperService.java similarity index 100% rename from twidere.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanWallpaperService.java rename to twidere.component.nyan/src/main/java/org/mariotaku/twidere/nyan/NyanWallpaperService.java diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame00_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame00_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame00_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame00_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame01_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame01_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame01_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame01_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame02_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame02_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame02_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame02_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame03_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame03_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame03_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame03_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame04_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame04_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame04_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame04_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame05_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame05_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame05_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame05_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame06_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame06_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame06_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame06_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame07_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame07_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame07_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame07_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame08_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame08_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame08_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame08_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame09_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame09_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame09_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame09_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame10_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame10_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame10_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame10_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame11_tile.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame11_tile.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame11_tile.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_rainbow_frame11_tile.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame00.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame00.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame00.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame00.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame01.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame01.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame01.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame01.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame02.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame02.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame02.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame02.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame03.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame03.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame03.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame03.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame04.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame04.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame04.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame04.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame05.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame05.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame05.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame05.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame06.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame06.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame06.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame06.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame07.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame07.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame07.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame07.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame08.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame08.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame08.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame08.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame09.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame09.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame09.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame09.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame10.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame10.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame10.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame10.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame11.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame11.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame11.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_frame11.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame00.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame00.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame00.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame00.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame01.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame01.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame01.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame01.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame02.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame02.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame02.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame02.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame03.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame03.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame03.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame03.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame04.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame04.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame04.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame04.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame05.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame05.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame05.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame05.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame06.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame06.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame06.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame06.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame07.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame07.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame07.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame07.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame08.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame08.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame08.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame08.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame09.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame09.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame09.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame09.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame10.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame10.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame10.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame10.png diff --git a/twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame11.png b/twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame11.png similarity index 100% rename from twidere.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame11.png rename to twidere.component.nyan/src/main/res/drawable-nodpi/nyan_sakamoto_santa_frame11.png diff --git a/twidere.nyan/src/main/res/drawable/nyan_sakamoto.xml b/twidere.component.nyan/src/main/res/drawable/nyan_sakamoto.xml similarity index 100% rename from twidere.nyan/src/main/res/drawable/nyan_sakamoto.xml rename to twidere.component.nyan/src/main/res/drawable/nyan_sakamoto.xml diff --git a/twidere.nyan/src/main/res/drawable/nyan_sakamoto_santa.xml b/twidere.component.nyan/src/main/res/drawable/nyan_sakamoto_santa.xml similarity index 100% rename from twidere.nyan/src/main/res/drawable/nyan_sakamoto_santa.xml rename to twidere.component.nyan/src/main/res/drawable/nyan_sakamoto_santa.xml diff --git a/twidere.nyan/src/main/res/layout/nyan_daydream.xml b/twidere.component.nyan/src/main/res/layout/nyan_daydream.xml similarity index 100% rename from twidere.nyan/src/main/res/layout/nyan_daydream.xml rename to twidere.component.nyan/src/main/res/layout/nyan_daydream.xml diff --git a/twidere.nyan/src/main/res/values-land/integers.xml b/twidere.component.nyan/src/main/res/values-land/integers.xml similarity index 100% rename from twidere.nyan/src/main/res/values-land/integers.xml rename to twidere.component.nyan/src/main/res/values-land/integers.xml diff --git a/twidere.nyan/src/main/res/values-large-land/integers.xml b/twidere.component.nyan/src/main/res/values-large-land/integers.xml similarity index 100% rename from twidere.nyan/src/main/res/values-large-land/integers.xml rename to twidere.component.nyan/src/main/res/values-large-land/integers.xml diff --git a/twidere.nyan/src/main/res/values-large/integers.xml b/twidere.component.nyan/src/main/res/values-large/integers.xml similarity index 100% rename from twidere.nyan/src/main/res/values-large/integers.xml rename to twidere.component.nyan/src/main/res/values-large/integers.xml diff --git a/twidere.nyan/src/main/res/values-xlarge-land/integers.xml b/twidere.component.nyan/src/main/res/values-xlarge-land/integers.xml similarity index 100% rename from twidere.nyan/src/main/res/values-xlarge-land/integers.xml rename to twidere.component.nyan/src/main/res/values-xlarge-land/integers.xml diff --git a/twidere.nyan/src/main/res/values-xlarge/integers.xml b/twidere.component.nyan/src/main/res/values-xlarge/integers.xml similarity index 100% rename from twidere.nyan/src/main/res/values-xlarge/integers.xml rename to twidere.component.nyan/src/main/res/values-xlarge/integers.xml diff --git a/twidere.nyan/src/main/res/values/colors.xml b/twidere.component.nyan/src/main/res/values/colors.xml similarity index 100% rename from twidere.nyan/src/main/res/values/colors.xml rename to twidere.component.nyan/src/main/res/values/colors.xml diff --git a/twidere.nyan/src/main/res/values/integers.xml b/twidere.component.nyan/src/main/res/values/integers.xml similarity index 100% rename from twidere.nyan/src/main/res/values/integers.xml rename to twidere.component.nyan/src/main/res/values/integers.xml diff --git a/twidere.nyan/src/main/res/values/strings.xml b/twidere.component.nyan/src/main/res/values/strings.xml similarity index 100% rename from twidere.nyan/src/main/res/values/strings.xml rename to twidere.component.nyan/src/main/res/values/strings.xml diff --git a/twidere.component.viewer.media/.gitignore b/twidere.component.viewer.media/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/twidere.component.viewer.media/.gitignore @@ -0,0 +1 @@ +/build diff --git a/twidere.component.viewer.media/build.gradle b/twidere.component.viewer.media/build.gradle new file mode 100644 index 000000000..053589028 --- /dev/null +++ b/twidere.component.viewer.media/build.gradle @@ -0,0 +1,43 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 . + */ + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.2" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 21 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile 'com.android.support:support-v4:21.0.3' + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/twidere.component.viewer.media/libs/tileviewlib-1.0.13.jar b/twidere.component.viewer.media/libs/tileviewlib-1.0.13.jar new file mode 100644 index 0000000000000000000000000000000000000000..999ecfd07a73247237f0aca8ea048dc332d92138 GIT binary patch literal 103457 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
BitmapRegionTileDecoder decoder = new + * BitmapRegionTileDecoder("/path/to/image.jpg", true);
+ * decoder.attachToTileView(tileView);
+ * + * @author mariotaku + */ +@SuppressWarnings("unused") +public final class BitmapRegionTileDecoder extends AbsTileDecoder { + + private static final Pattern PATTERN_TILE_FORMAT = Pattern.compile("(\\d+):(\\d+)\\-(\\d+)", + Pattern.CASE_INSENSITIVE); + + private final BitmapRegionDecoder mDecoder; + private final int mTileSize; + + private final DownSampleDecoder mDownSampleDecoder; + + public BitmapRegionTileDecoder(final BitmapRegionDecoder decoder) { + this(decoder, 128, 512); + } + + /** + * @param decoder {@link android.graphics.BitmapRegionDecoder} instance + * @param tileSize size of each tile + * @param maxDownSampleSize max size of downsample image + */ + public BitmapRegionTileDecoder(final BitmapRegionDecoder decoder, final int tileSize, final int maxDownSampleSize) { + if (decoder == null) throw new NullPointerException(); + mDecoder = decoder; + mTileSize = tileSize; + final int downsampleInSampleSize = nextPowerOfTwo(Math.max(1, Math.max(getWidth(), getHeight()) + / maxDownSampleSize)); + mDownSampleDecoder = new DownSampleDecoder(decoder, downsampleInSampleSize); + } + + public BitmapRegionTileDecoder(final FileDescriptor fd, final boolean isShareable) throws IOException { + this(BitmapRegionDecoder.newInstance(fd, isShareable)); + } + + public BitmapRegionTileDecoder(final InputStream is, final boolean isShareable) throws IOException { + this(BitmapRegionDecoder.newInstance(is, isShareable)); + } + + public BitmapRegionTileDecoder(final String pathName, final boolean isShareable) throws IOException { + this(BitmapRegionDecoder.newInstance(pathName, isShareable)); + } + + @Override + public void attachToTileView(final TileView tileView) { + tileView.resetDetailLevels(); + tileView.setTileDecoder(this); + tileView.setDownsampleDecoder(getDownSampleDecoder()); + final int width = getWidth(), height = getHeight(), tileSize = getTileSize(); + tileView.setSize(width, height); + for (int i = 0, j = Math.max(width, height) / tileSize; i < j; i++) { + final int s = i + 1; + if (isPowerOfTwo(s)) { + tileView.addDetailLevel(1f / s, BitmapRegionTileDecoder.getDecodeName(s), "sample", tileSize, tileSize); + } + } + } + + @Override + public Bitmap decode(final String fileName, final Context context) { + final Matcher m = PATTERN_TILE_FORMAT.matcher(fileName); + if (!m.matches()) return null; + final int inSampleSize = Integer.parseInt(m.group(1)); + final int col = Integer.parseInt(m.group(2)), row = Integer.parseInt(m.group(3)); + final int tileSize = inSampleSize * mTileSize; + final int left = col * tileSize, top = row * tileSize; + final int right = Math.min(left + tileSize, getWidth()); + final int bottom = Math.min(top + tileSize, getHeight()); + final Rect rect = new Rect(left, top, right, bottom); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = inSampleSize; + return mDecoder.decodeRegion(rect, options); + } + + public BitmapDecoder getDownSampleDecoder() { + return mDownSampleDecoder; + } + + public int getHeight() { + return mDecoder.getHeight(); + } + + public int getTileSize() { + return mTileSize; + } + + public int getWidth() { + return mDecoder.getWidth(); + } + + @Override + public boolean isRecycled() { + return mDecoder.isRecycled(); + } + + @Override + public void recycle() { + mDecoder.recycle(); + } + + public boolean isSameDecoder(final BitmapRegionDecoder decoder) { + return mDecoder.equals(decoder); + } + + public static String getDecodeName(final int inSampleSize) { + return String.format("%d:%s-%s", inSampleSize, "%col%", "%row%"); + } + + private static boolean isPowerOfTwo(final int n) { + return n != 0 && (n & n - 1) == 0; + } + + private static int nextPowerOfTwo(final int i) { + int n = i; + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; + } + + private static final class DownSampleDecoder implements BitmapDecoder { + + private final BitmapRegionDecoder mDecoder; + private final int mInSampleSize; + + private DownSampleDecoder(final BitmapRegionDecoder decoder, final int inSampleSize) { + mDecoder = decoder; + mInSampleSize = inSampleSize; + } + + @Override + public Bitmap decode(final String fileName, final Context context) { + final Rect rect = new Rect(0, 0, mDecoder.getWidth(), mDecoder.getHeight()); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = mInSampleSize; + return mDecoder.decodeRegion(rect, options); + } + + } + +} \ No newline at end of file diff --git a/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/DummyTileDecoder.java b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/DummyTileDecoder.java new file mode 100644 index 000000000..97435340b --- /dev/null +++ b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/DummyTileDecoder.java @@ -0,0 +1,71 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.tileimageview.decoder; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapRegionDecoder; + +import com.qozix.tileview.TileView; + +public final class DummyTileDecoder extends AbsTileDecoder { + + private final Bitmap mFallback; + + public DummyTileDecoder(Bitmap fallback) { + mFallback = fallback; + } + + @Override + public void attachToTileView(final TileView tileView) { + tileView.setTileDecoder(this); + tileView.setDownsampleDecoder(this); + tileView.resetDetailLevels(); + if (mFallback != null) { + final int width = mFallback.getWidth(), height = mFallback.getHeight(); + tileView.setSize(width, height); + tileView.addDetailLevel(1, "", "sample", width, height); + } else { + tileView.setSize(0, 0); + } + } + + @Override + public Bitmap decode(final String fileName, final Context context) { + return null; + } + + @Override + public boolean isRecycled() { + return mFallback != null && mFallback.isRecycled(); + } + + @Override + public boolean isSameDecoder(BitmapRegionDecoder decoder) { + return false; + } + + @Override + public void recycle() { + if (mFallback != null) { + mFallback.recycle(); + } + } +} \ No newline at end of file diff --git a/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/widget/TileImageView.java b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/widget/TileImageView.java new file mode 100644 index 000000000..2fd6d31f8 --- /dev/null +++ b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/widget/TileImageView.java @@ -0,0 +1,282 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.tileimageview.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Point; +import android.os.Build; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.qozix.layouts.ZoomPanLayout.GestureListener; +import com.qozix.layouts.ZoomPanLayout.ZoomPanListener; +import com.qozix.tileview.TileView; +import com.qozix.tileview.TileView.TileViewEventListener; +import com.qozix.tileview.hotspots.HotSpot; +import com.qozix.tileview.hotspots.HotSpotEventListener; +import com.qozix.tileview.markers.MarkerEventListener; + +import org.mariotaku.tileimageview.decoder.AbsTileDecoder; +import org.mariotaku.tileimageview.decoder.BitmapRegionTileDecoder; +import org.mariotaku.tileimageview.decoder.DummyTileDecoder; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("unused") +public final class TileImageView extends FrameLayout { + + private final TileView mTileView; + private AbsTileDecoder mTileDecoder; + + public TileImageView(final Context context) { + this(context, null); + } + + public TileImageView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + public TileImageView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + mTileView = new TileView(context); + super.addView(mTileView, 0, generateDefaultLayoutParams()); + } + + public View addCallout(final View view, final double x, final double y) { + return mTileView.addCallout(view, x, y); + } + + public View addCallout(final View view, final double x, final double y, final float anchorX, final float anchorY) { + return mTileView.addCallout(view, x, y, anchorX, anchorY); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + public void addChildrenForAccessibility(@NonNull final ArrayList childrenForAccessibility) { + mTileView.addChildrenForAccessibility(childrenForAccessibility); + } + + @Override + public void addFocusables(final ArrayList views, final int direction) { + mTileView.addFocusables(views, direction); + } + + @Override + public void addFocusables(@NonNull final ArrayList views, final int direction, final int focusableMode) { + mTileView.addFocusables(views, direction, focusableMode); + } + + public boolean addGestureListener(final GestureListener listener) { + return mTileView.addGestureListener(listener); + } + + public HotSpot addHotSpot(final HotSpot hotSpot) { + return mTileView.addHotSpot(hotSpot); + } + + public HotSpot addHotSpot(final List positions) { + return mTileView.addHotSpot(positions); + } + + public HotSpot addHotSpot(final List positions, final HotSpotEventListener listener) { + return mTileView.addHotSpot(positions, listener); + } + + public void addHotSpotEventListener(final HotSpotEventListener listener) { + mTileView.addHotSpotEventListener(listener); + } + + public View addMarker(final View view, final double x, final double y) { + return mTileView.addMarker(view, x, y); + } + + public View addMarker(final View view, final double x, final double y, final float anchorX, final float anchorY) { + return mTileView.addMarker(view, x, y, anchorX, anchorY); + } + + public void addMarkerEventListener(final MarkerEventListener listener) { + mTileView.addMarkerEventListener(listener); + } + + public void addTileViewEventListener(final TileViewEventListener listener) { + mTileView.addTileViewEventListener(listener); + } + + @Override + public void addView(@NonNull final View child) { + throw new UnsupportedOperationException(); + } + + @Override + public void addView(@NonNull final View child, final int index) { + throw new UnsupportedOperationException(); + } + + @Override + public void addView(@NonNull final View child, final int width, final int height) { + throw new UnsupportedOperationException(); + } + + @Override + public void addView(@NonNull final View child, final int index, final ViewGroup.LayoutParams params) { + throw new UnsupportedOperationException(); + } + + @Override + public void addView(@NonNull final View child, final ViewGroup.LayoutParams params) { + throw new UnsupportedOperationException(); + } + + public boolean addZoomPanListener(final ZoomPanListener listener) { + return mTileView.addZoomPanListener(listener); + } + + public void cancelRender() { + mTileView.cancelRender(); + } + + @Override + public boolean canScrollHorizontally(final int direction) { + final double scaledBoundaryX = mTileView.getScaledWidth(); + final int scrollX = mTileView.getScrollX(); + return scrollX + direction > 0 && scrollX + mTileView.getWidth() + direction < scaledBoundaryX; + } + + @Override + public boolean canScrollVertically(final int direction) { + final double scaledBoundaryY = mTileView.getScaledHeight(); + final int scrollY = mTileView.getScrollY(); + return scrollY + direction > 0 && scrollY + mTileView.getHeight() + direction < scaledBoundaryY; + } + + @Override + public boolean dispatchTouchEvent(@NonNull final MotionEvent ev) { + return mTileView.dispatchTouchEvent(ev); + } + + public int getBaseHeight() { + return mTileView.getBaseHeight(); + } + + public int getBaseWidth() { + return mTileView.getBaseWidth(); + } + + public Point getCenter() { + return new Point(mTileView.getScrollX(), mTileView.getScrollY()); + } + + public double getScale() { + return mTileView.getScale(); + } + + public boolean removeGestureListener(final GestureListener listener) { + return mTileView.removeGestureListener(listener); + } + + public void removeHotSpot(final HotSpot hotSpot) { + mTileView.removeHotSpot(hotSpot); + } + + public void removeHotSpotEventListener(final HotSpotEventListener listener) { + mTileView.removeHotSpotEventListener(listener); + } + + public void removeMarkerEventListener(final MarkerEventListener listener) { + mTileView.removeMarkerEventListener(listener); + } + + public void requestRender() { + mTileView.requestRender(); + } + + public void resume() { + mTileView.resume(); + } + + public void scrollToAndCenter(final Point point) { + mTileView.scrollToAndCenter(point); + } + + public void scrollToPoint(final Point point) { + mTileView.scrollToPoint(point); + } + + public void setBitmapRegionDecoder(final BitmapRegionDecoder decoder, final Bitmap fallback) { + if (mTileDecoder != null && mTileDecoder.isSameDecoder(decoder)) return; + if (mTileDecoder != null) { + mTileDecoder.recycle(); + mTileDecoder = null; + } + if (decoder == null) { + mTileDecoder = new DummyTileDecoder(fallback); + } else { + mTileDecoder = new BitmapRegionTileDecoder(decoder); + } + mTileDecoder.attachToTileView(mTileView); + } + + public void setDragStartThreshold(final int threshold) { + mTileView.setDragStartThreshold(threshold); + } + + public void setScale(final double d) { + mTileView.setScale(d); + } + + public void setScaleLimits(final double min, final double max) { + mTileView.setScaleLimits(min, max); + } + + public void setScaleToFit(final boolean shouldScaleToFit) { + mTileView.setScaleToFit(shouldScaleToFit); + } + + public void slideToAndCenter(final double x, final double y) { + mTileView.slideToAndCenter(x, y); + } + + public void slideToAndCenter(final Point point) { + mTileView.slideToAndCenter(point); + } + + public void slideToPoint(final Point point) { + mTileView.slideToPoint(point); + } + + public void smoothScaleTo(final double destination, final int duration) { + mTileView.smoothScaleTo(destination, duration); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER); + } + +} \ No newline at end of file diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAboutMeAdapter.java b/twidere.component.viewer.media/src/main/java/org/mariotaku/twidere/viewer/media/MediaViewerActivity.java similarity index 73% rename from twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAboutMeAdapter.java rename to twidere.component.viewer.media/src/main/java/org/mariotaku/twidere/viewer/media/MediaViewerActivity.java index d14162a69..e32e4617d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAboutMeAdapter.java +++ b/twidere.component.viewer.media/src/main/java/org/mariotaku/twidere/viewer/media/MediaViewerActivity.java @@ -17,15 +17,13 @@ * along with this program. If not, see . */ -package org.mariotaku.twidere.adapter; +package org.mariotaku.twidere.viewer.media; -import android.content.Context; +import android.app.Activity; +import android.support.v4.app.FragmentActivity; /** - * Created by mariotaku on 15/1/3. + * Created by mariotaku on 15/1/5. */ -public class ParcelableActivitiesAboutMeAdapter extends ParcelableActivitiesAdapter { - protected ParcelableActivitiesAboutMeAdapter(Context context) { - super(context); - } +public class MediaViewerActivity extends FragmentActivity { } diff --git a/twidere.component.viewer.media/src/main/res/values/strings.xml b/twidere.component.viewer.media/src/main/res/values/strings.xml new file mode 100644 index 000000000..c124e77df --- /dev/null +++ b/twidere.component.viewer.media/src/main/res/values/strings.xml @@ -0,0 +1,21 @@ + + + + diff --git a/twidere.donate.nyanwp.wear/build.gradle b/twidere.donate.nyanwp.wear/build.gradle index 6b62cddf7..f22318b42 100644 --- a/twidere.donate.nyanwp.wear/build.gradle +++ b/twidere.donate.nyanwp.wear/build.gradle @@ -74,7 +74,7 @@ android { } dependencies { - compile project(':twidere.nyan') - compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.android.support:wearable:1.1.0' + compile project(':twidere.component.nyan') + compile fileTree(dir: 'libs', include: ['*.jar']) } diff --git a/twidere.donate.nyanwp/build.gradle b/twidere.donate.nyanwp/build.gradle index e2f3df84f..ea2e30beb 100644 --- a/twidere.donate.nyanwp/build.gradle +++ b/twidere.donate.nyanwp/build.gradle @@ -74,6 +74,6 @@ android { dependencies { wearApp project(':twidere.donate.nyanwp.wear') - compile project(':twidere.nyan') + compile project(':twidere.component.nyan') compile fileTree(dir: 'libs', include: ['*.jar']) } diff --git a/twidere/build.gradle b/twidere/build.gradle index 5ed7d9b0a..c5f9f3195 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -104,11 +104,12 @@ dependencies { googleCompile 'com.google.android.gms:play-services:6.5.87' fdroidCompile 'org.osmdroid:osmdroid-android:4.2' fdroidCompile 'org.slf4j:slf4j-simple:1.7.9' + compile project(':twidere.component.nyan') + compile project(':twidere.component.viewer.media') compile project(':SlidingMenu') compile project(':DragSortListView') compile project(':MenuComponent') compile project(':MessageBubbleView') - compile project(':twidere.nyan') compile fileTree(dir: 'libs/main', include: ['*.jar']) googleCompile fileTree(dir: 'libs/google', include: ['*.jar']) } diff --git a/twidere/src/main/AndroidManifest.xml b/twidere/src/main/AndroidManifest.xml index d1835c4c2..9dbb3c06d 100644 --- a/twidere/src/main/AndroidManifest.xml +++ b/twidere/src/main/AndroidManifest.xml @@ -169,6 +169,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.support.HomeActivity"/> + + + + + + diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivity.java b/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java similarity index 91% rename from twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivity.java rename to twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java index 7b2905f79..1fef8f970 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivity.java +++ b/twidere/src/main/java/org/mariotaku/gallery3d/ImageViewerGLActivityOld.java @@ -1,17 +1,20 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * Twidere - Twitter client for Android * - * 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 + * Copyright (C) 2012-2015 Mariotaku Lee * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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. * - * 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. + * 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 org.mariotaku.gallery3d; @@ -49,14 +52,15 @@ import org.mariotaku.menucomponent.widget.MenuBar.MenuBarListener; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.support.BaseSupportActivity; +import org.mariotaku.twidere.loader.support.TileImageLoader; import org.mariotaku.twidere.util.SaveImageTask; import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.Utils; import java.io.File; -public final class ImageViewerGLActivity extends BaseSupportActivity implements Constants, PhotoView.Listener, - GLImageLoader.DownloadListener, LoaderManager.LoaderCallbacks, OnMenuVisibilityListener, +public final class ImageViewerGLActivityOld extends BaseSupportActivity implements Constants, PhotoView.Listener, + TileImageLoader.DownloadListener, LoaderManager.LoaderCallbacks, OnMenuVisibilityListener, MenuBarListener { private final GLView mRootPane = new GLView() { @@ -139,13 +143,13 @@ public final class ImageViewerGLActivity extends BaseSupportActivity implements } @Override - public Loader onCreateLoader(final int id, final Bundle args) { + public Loader onCreateLoader(final int id, final Bundle args) { mProgress.setVisibility(View.VISIBLE); mProgress.setIndeterminate(true); invalidateOptionsMenu(); final Uri uri = args.getParcelable(EXTRA_URI); final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1); - return new GLImageLoader(this, this, accountId, uri); + return new TileImageLoader(this, this, accountId, uri); } @Override @@ -178,12 +182,12 @@ public final class ImageViewerGLActivity extends BaseSupportActivity implements @Override - public void onLoaderReset(final Loader loader) { + public void onLoaderReset(final Loader loader) { } @Override - public void onLoadFinished(final Loader loader, final GLImageLoader.Result data) { + public void onLoadFinished(final Loader loader, final TileImageLoader.Result data) { if (data != null && (data.decoder != null || data.bitmap != null)) { if (data.decoder != null) { mGLRootView.setVisibility(View.VISIBLE); @@ -525,9 +529,9 @@ public final class ImageViewerGLActivity extends BaseSupportActivity implements } private static class MyHandler extends SynchronizedHandler { - ImageViewerGLActivity activity; + ImageViewerGLActivityOld activity; - private MyHandler(final ImageViewerGLActivity activity) { + private MyHandler(final ImageViewerGLActivityOld activity) { super(activity.getGLRoot()); this.activity = activity; } diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java index 0c1dc81a6..55e796ed6 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java +++ b/twidere/src/main/java/org/mariotaku/gallery3d/ui/BasicTexture.java @@ -18,7 +18,7 @@ package org.mariotaku.gallery3d.ui; import android.util.Log; -import org.mariotaku.gallery3d.util.GalleryUtils; +import org.mariotaku.twidere.util.MathUtils; import java.util.WeakHashMap; @@ -153,8 +153,8 @@ abstract class BasicTexture implements Texture { protected void setSize(final int width, final int height) { mWidth = width; mHeight = height; - mTextureWidth = GalleryUtils.nextPowerOf2(width); - mTextureHeight = GalleryUtils.nextPowerOf2(height); + mTextureWidth = MathUtils.nextPowerOf2(width); + mTextureHeight = MathUtils.nextPowerOf2(height); if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) { Log.w(TAG, String.format("texture is too large: %d x %d", mTextureWidth, mTextureHeight), new Exception()); } diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java index 0ce930e7c..ff17798cc 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java +++ b/twidere/src/main/java/org/mariotaku/gallery3d/ui/PhotoView.java @@ -25,568 +25,568 @@ import android.os.Build; import android.os.Message; import android.view.MotionEvent; -import org.mariotaku.gallery3d.ImageViewerGLActivity; +import org.mariotaku.gallery3d.ImageViewerGLActivityOld; import org.mariotaku.gallery3d.util.BitmapPool; public class PhotoView extends GLView { - private static final int MSG_CANCEL_EXTRA_SCALING = 2; - private static final int MSG_CAPTURE_ANIMATION_DONE = 4; - - private static final int HOLD_TOUCH_DOWN = 1; - private static final int HOLD_CAPTURE_ANIMATION = 2; - - private final GestureListener mGestureListener; - - private final GestureRecognizer mGestureRecognizer; - private final PositionController mPositionController; - - private Listener mListener; - private ITileImageAdapter mModel; - - private final TileImageView mTileView; - private final EdgeView mEdgeView; - private final SynchronizedHandler mHandler; - private boolean mCancelExtraScalingPending; - private boolean mWantPictureCenterCallbacks = false; - - private int mDisplayRotation = 0; - - private int mCompensation = 0; - // This variable prevents us doing snapback until its values goes to 0. This - // happens if the user gesture is still in progress or we are in a capture - // animation. - private int mHolding; - - // This is the index of the last deleted item. This is only used as a hint - // to hide the undo button when we are too far away from the deleted - // item. The value Integer.MAX_VALUE means there is no such hint. - private final Context mContext; - private final FullPicture mPicture; - - public PhotoView(final ImageViewerGLActivity activity) { - mTileView = new TileImageView(activity); - addComponent(mTileView); - mContext = activity; - mEdgeView = new EdgeView(mContext); - addComponent(mEdgeView); - mHandler = new MyHandler(activity); - mPicture = new FullPicture(); - mGestureListener = new GestureListener(); - mGestureRecognizer = new GestureRecognizer(mContext, mGestureListener); - mPositionController = new PositionController(new EventListener()); - } - - public Rect getPhotoRect() { - return mPositionController.getPosition(); - } - - public void notifyImageChange() { - mListener.onCurrentImageUpdated(); - mPicture.reload(); - setPictureSize(); - invalidate(); - } - - public void pause() { - mPositionController.skipAnimation(); - mTileView.freeTextures(); - for (int i = -0; i <= 0; i++) { - mPicture.setScreenNail(null); - } - } - - public void resume() { - mTileView.prepareTextures(); - mPositionController.skipToFinalPosition(); - } - - public void setListener(final Listener listener) { - mListener = listener; - } - - public void setModel(final ITileImageAdapter model) { - mModel = model; - mTileView.setModel(mModel); - } - - public void setOpenAnimationRect(final Rect rect) { - mPositionController.setOpenAnimationRect(rect); - } - - public void setWantPictureCenterCallbacks(final boolean wanted) { - mWantPictureCenterCallbacks = wanted; - } - - @Override - protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { - final int w = right - left; - final int h = bottom - top; - mTileView.layout(0, 0, w, h); - mEdgeView.layout(0, 0, w, h); - - final GLRoot root = getGLRoot(); - final int displayRotation = root.getDisplayRotation(); - final int compensation = root.getCompensation(); - if (mDisplayRotation != displayRotation || mCompensation != compensation) { - mDisplayRotation = displayRotation; - mCompensation = compensation; - } - - if (changeSize) { - mPositionController.setViewSize(getWidth(), getHeight()); - } - } - - // ////////////////////////////////////////////////////////////////////////// - // Pictures - // ////////////////////////////////////////////////////////////////////////// - - @Override - protected boolean onTouch(final MotionEvent event) { - mGestureRecognizer.onTouchEvent(event); - return true; - } - - @Override - protected void render(final GLCanvas canvas) { - - // Draw photos from back to front - final Rect r = mPositionController.getPosition(); - mPicture.draw(canvas, r); - - renderChild(canvas, mEdgeView); - - mPositionController.advanceAnimation(); - } - - private void captureAnimationDone(final int offset) { - mHolding &= ~HOLD_CAPTURE_ANIMATION; - if (offset == 1) { - // Now the capture animation is done, enable the action bar. - mListener.onActionBarAllowed(true); - mListener.onActionBarWanted(); - } - snapback(); - } - - private void setPictureSize() { - mPositionController.setImageSize(mPicture.getSize(), null); - } - - private void snapback() { - mPositionController.snapback(); - } - - // ////////////////////////////////////////////////////////////////////////// - // Film mode focus switching - // ////////////////////////////////////////////////////////////////////////// - - private static int getRotated(final int degree, final int original, final int theother) { - return degree % 180 == 0 ? original : theother; - } - - public interface ITileImageAdapter { - - public int getImageHeight(); - - // Returns the rotation for the specified picture. - public int getImageRotation(); - - public int getImageWidth(); - - public int getLevelCount(); - - public ScreenNail getScreenNail(); - - /** - * The tile returned by this method can be specified this way: Assuming - * the image size is (width, height), first take the intersection of (0, - * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then - * extend this intersection region by borderSize pixels on each side. If - * in extending the region, we found some part of the region are outside - * the image, those pixels are filled with black.
- *
- * If level > 0, it does the same operation on a down-scaled version of - * the original image (down-scaled by a factor of 2^level), but (x, y) - * still refers to the coordinate on the original image.
- *
- * The method would be called in another thread. - */ - public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize, BitmapPool pool); - - public void recycleScreenNail(); - - public boolean setData(BitmapRegionDecoder decoder, Bitmap bitmap, int orientation); - - } - - public interface Listener { - public void onActionBarAllowed(boolean allowed); - - public void onActionBarWanted(); - - public void onCurrentImageUpdated(); - - public void onPictureCenter(); - - public void onSingleTapUp(int x, int y); - } - - public static class Size { - public int width; - public int height; - } - - private class GestureListener implements GestureRecognizer.Listener { - private boolean mIgnoreUpEvent = false; - // If we can change mode for this scale gesture. - private boolean mCanChangeMode; - // If we have changed the film mode in this scaling gesture. - private boolean mModeChanged; - // If a scrolling has happened after a down gesture. - private boolean mScrolledAfterDown; - // The accumulated scaling change from a scaling gesture. - private float mAccScale; - - @Override - public boolean onDoubleTap(final float x, final float y) { - final PositionController controller = mPositionController; - final float scale = controller.getImageScale(); - // onDoubleTap happened on the second ACTION_DOWN. - // We need to ignore the next UP event. - mIgnoreUpEvent = true; - if (scale <= .75f || controller.isAtMinimalScale()) { - controller.zoomIn(x, y, Math.max(1.0f, scale * 1.5f)); - } else { - controller.resetToFullView(); - } - return true; - } - - @Override - public void onDown(final float x, final float y) { - - mModeChanged = false; - - mHolding |= HOLD_TOUCH_DOWN; - - mScrolledAfterDown = false; - } - - @Override - public boolean onFling(final float velocityX, final float velocityY) { - if (mModeChanged) return true; - flingImages(velocityX, velocityY); - return true; - } - - @Override - public boolean onScale(final float focusX, final float focusY, final float scale) { - if (mModeChanged) return true; - if (Float.isNaN(scale) || Float.isInfinite(scale)) return false; - - final int outOfRange = mPositionController.scaleBy(scale, focusX, focusY); - - // We wait for a large enough scale change before changing mode. - // Otherwise we may mistakenly treat a zoom-in gesture as zoom-out - // or vice versa. - mAccScale *= scale; - final boolean largeEnough = mAccScale < 0.97f || mAccScale > 1.03f; - - // If mode changes, we treat this scaling gesture has ended. - if (mCanChangeMode && largeEnough) { - if (outOfRange < 0 || outOfRange > 0) { - stopExtraScalingIfNeeded(); - - // Removing the touch down flag allows snapback to happen - // for film mode change. - mHolding &= ~HOLD_TOUCH_DOWN; - - // We need to call onScaleEnd() before setting mModeChanged - // to true. - onScaleEnd(); - mModeChanged = true; - return true; - } - } - - if (outOfRange != 0) { - startExtraScalingIfNeeded(); - } else { - stopExtraScalingIfNeeded(); - } - return true; - } - - @Override - public boolean onScaleBegin(final float focusX, final float focusY) { - // We ignore the scaling gesture if it is a camera preview. - mPositionController.beginScale(focusX, focusY); - // We can change mode if we are in film mode, or we are in page - // mode and at minimal scale. - mCanChangeMode = mPositionController.isAtMinimalScale(); - mAccScale = 1f; - return true; - } - - @Override - public void onScaleEnd() { - if (mModeChanged) return; - mPositionController.endScale(); - } - - @Override - public boolean onScroll(final float dx, final float dy, final float totalX, final float totalY) { - if (!mScrolledAfterDown) { - mScrolledAfterDown = true; - } - - final int dxi = (int) (-dx + 0.5f); - final int dyi = (int) (-dy + 0.5f); - mPositionController.scrollPage(dxi, dyi); - return true; - } - - @Override - public boolean onSingleTapUp(final float x, final float y) { - // On crespo running Android 2.3.6 (gingerbread), a pinch out - // gesture results in the - // following call sequence: onDown(), onUp() and then - // onSingleTapUp(). The correct - // sequence for a single-tap-up gesture should be: onDown(), - // onSingleTapUp() and onUp(). - // The call sequence for a pinch out gesture in JB is: onDown(), - // then onUp() and there's - // no onSingleTapUp(). Base on these observations, the following - // condition is added to - // filter out the false alarm where onSingleTapUp() is called within - // a pinch out - // gesture. The framework fix went into ICS. Refer to b/4588114. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - if ((mHolding & HOLD_TOUCH_DOWN) == 0) return true; - } - - // We do this in addition to onUp() because we want the snapback of - // setFilmMode to happen. - mHolding &= ~HOLD_TOUCH_DOWN; - - if (mListener != null) { - // Do the inverse transform of the touch coordinates. - final Matrix m = getGLRoot().getCompensationMatrix(); - final Matrix inv = new Matrix(); - m.invert(inv); - final float[] pts = new float[] { x, y }; - inv.mapPoints(pts); - mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f)); - } - return true; - } - - @Override - public void onUp() { - mHolding &= ~HOLD_TOUCH_DOWN; - mEdgeView.onRelease(); - - if (mIgnoreUpEvent) { - mIgnoreUpEvent = false; - return; - } - - snapback(); - } - - private boolean flingImages(final float velocityX, final float velocityY) { - final int vx = (int) (velocityX + 0.5f); - final int vy = (int) (velocityY + 0.5f); - return mPositionController.flingPage(vx, vy); - } - - private void startExtraScalingIfNeeded() { - if (!mCancelExtraScalingPending) { - mHandler.sendEmptyMessageDelayed(MSG_CANCEL_EXTRA_SCALING, 700); - mPositionController.setExtraScalingRange(true); - mCancelExtraScalingPending = true; - } - } - - private void stopExtraScalingIfNeeded() { - if (mCancelExtraScalingPending) { - mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING); - mPositionController.setExtraScalingRange(false); - mCancelExtraScalingPending = false; - } - } - } - - private class MyHandler extends SynchronizedHandler { - - private MyHandler(final ImageViewerGLActivity activity) { - super(activity.getGLRoot()); - } - - @Override - public void handleMessage(final Message message) { - switch (message.what) { - case MSG_CANCEL_EXTRA_SCALING: { - mGestureRecognizer.cancelScale(); - mPositionController.setExtraScalingRange(false); - mCancelExtraScalingPending = false; - break; - } - case MSG_CAPTURE_ANIMATION_DONE: { - // message.arg1 is the offset parameter passed to - // switchWithCaptureAnimation(). - captureAnimationDone(message.arg1); - break; - } - default: - throw new AssertionError(message.what); - } - } - } - - private interface Picture { - void draw(GLCanvas canvas, Rect r); - - void forceSize(); // called when mCompensation changes - - Size getSize(); - - void reload(); - - void setScreenNail(ScreenNail s); - } - - class EventListener implements PositionController.Listener { - - @Override - public boolean isHoldingDown() { - return (mHolding & HOLD_TOUCH_DOWN) != 0; - } - - @Override - public void onAbsorb(final int velocity, final int direction) { - mEdgeView.onAbsorb(velocity, direction); - } - - @Override - public void onInvalidate() { - invalidate(); - } - - @Override - public void onPull(final int offset, final int direction) { - mEdgeView.onPull(offset, direction); - } - } - - class FullPicture implements Picture { - private int mRotation; - private final Size mSize = new Size(); - - @Override - public void draw(final GLCanvas canvas, final Rect r) { - drawTileView(canvas, r); - - // We want to have the following transitions: - // (1) Move camera preview out of its place: switch to film mode - // (2) Move camera preview into its place: switch to page mode - // The extra mWasCenter check makes sure (1) does not apply if in - // page mode, we move _to_ the camera preview from another picture. - - // Holdings except touch-down prevent the transitions. - if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return; - - if (mWantPictureCenterCallbacks && mPositionController.isCenter()) { - mListener.onPictureCenter(); - } - } - - @Override - public void forceSize() { - updateSize(); - mPositionController.forceImageSize(mSize); - } - - @Override - public Size getSize() { - return mSize; - } - - @Override - public void reload() { - // mImageWidth and mImageHeight will get updated - mTileView.notifyModelInvalidated(); - setScreenNail(mModel.getScreenNail()); - updateSize(); - } - - @Override - public void setScreenNail(final ScreenNail s) { - mTileView.setScreenNail(s); - } - - private void drawTileView(final GLCanvas canvas, final Rect r) { - final float imageScale = mPositionController.getImageScale(); - final int viewW = getWidth(); - final int viewH = getHeight(); - final float cx = r.exactCenterX(); - final float cy = r.exactCenterY(); - - canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA); - // Draw the tile view. - setTileViewPosition(cx, cy, viewW, viewH, imageScale); - renderChild(canvas, mTileView); - - // Draw the play video icon and the message. - canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f)); - - canvas.restore(); - } - - // Set the position of the tile view - private void setTileViewPosition(final float cx, final float cy, final int viewW, final int viewH, - final float scale) { - // Find out the bitmap coordinates of the center of the view - final int imageW = mPositionController.getImageWidth(); - final int imageH = mPositionController.getImageHeight(); - final int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f); - final int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f); - - final int inverseX = imageW - centerX; - final int inverseY = imageH - centerY; - int x, y; - switch (mRotation) { - case 0: - x = centerX; - y = centerY; - break; - case 90: - x = centerY; - y = inverseX; - break; - case 180: - x = inverseX; - y = inverseY; - break; - case 270: - x = inverseY; - y = centerX; - break; - default: - throw new RuntimeException(String.valueOf(mRotation)); - } - mTileView.setPosition(x, y, scale, mRotation); - } - - private void updateSize() { - mRotation = mModel.getImageRotation(); - - final int w = mTileView.mImageWidth; - final int h = mTileView.mImageHeight; - mSize.width = getRotated(mRotation, w, h); - mSize.height = getRotated(mRotation, h, w); - } - } + private static final int MSG_CANCEL_EXTRA_SCALING = 2; + private static final int MSG_CAPTURE_ANIMATION_DONE = 4; + + private static final int HOLD_TOUCH_DOWN = 1; + private static final int HOLD_CAPTURE_ANIMATION = 2; + + private final GestureListener mGestureListener; + + private final GestureRecognizer mGestureRecognizer; + private final PositionController mPositionController; + + private Listener mListener; + private ITileImageAdapter mModel; + + private final TileImageView mTileView; + private final EdgeView mEdgeView; + private final SynchronizedHandler mHandler; + private boolean mCancelExtraScalingPending; + private boolean mWantPictureCenterCallbacks = false; + + private int mDisplayRotation = 0; + + private int mCompensation = 0; + // This variable prevents us doing snapback until its values goes to 0. This + // happens if the user gesture is still in progress or we are in a capture + // animation. + private int mHolding; + + // This is the index of the last deleted item. This is only used as a hint + // to hide the undo button when we are too far away from the deleted + // item. The value Integer.MAX_VALUE means there is no such hint. + private final Context mContext; + private final FullPicture mPicture; + + public PhotoView(final ImageViewerGLActivityOld activity) { + mTileView = new TileImageView(activity); + addComponent(mTileView); + mContext = activity; + mEdgeView = new EdgeView(mContext); + addComponent(mEdgeView); + mHandler = new MyHandler(activity); + mPicture = new FullPicture(); + mGestureListener = new GestureListener(); + mGestureRecognizer = new GestureRecognizer(mContext, mGestureListener); + mPositionController = new PositionController(new EventListener()); + } + + public Rect getPhotoRect() { + return mPositionController.getPosition(); + } + + public void notifyImageChange() { + mListener.onCurrentImageUpdated(); + mPicture.reload(); + setPictureSize(); + invalidate(); + } + + public void pause() { + mPositionController.skipAnimation(); + mTileView.freeTextures(); + for (int i = -0; i <= 0; i++) { + mPicture.setScreenNail(null); + } + } + + public void resume() { + mTileView.prepareTextures(); + mPositionController.skipToFinalPosition(); + } + + public void setListener(final Listener listener) { + mListener = listener; + } + + public void setModel(final ITileImageAdapter model) { + mModel = model; + mTileView.setModel(mModel); + } + + public void setOpenAnimationRect(final Rect rect) { + mPositionController.setOpenAnimationRect(rect); + } + + public void setWantPictureCenterCallbacks(final boolean wanted) { + mWantPictureCenterCallbacks = wanted; + } + + @Override + protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { + final int w = right - left; + final int h = bottom - top; + mTileView.layout(0, 0, w, h); + mEdgeView.layout(0, 0, w, h); + + final GLRoot root = getGLRoot(); + final int displayRotation = root.getDisplayRotation(); + final int compensation = root.getCompensation(); + if (mDisplayRotation != displayRotation || mCompensation != compensation) { + mDisplayRotation = displayRotation; + mCompensation = compensation; + } + + if (changeSize) { + mPositionController.setViewSize(getWidth(), getHeight()); + } + } + + // ////////////////////////////////////////////////////////////////////////// + // Pictures + // ////////////////////////////////////////////////////////////////////////// + + @Override + protected boolean onTouch(final MotionEvent event) { + mGestureRecognizer.onTouchEvent(event); + return true; + } + + @Override + protected void render(final GLCanvas canvas) { + + // Draw photos from back to front + final Rect r = mPositionController.getPosition(); + mPicture.draw(canvas, r); + + renderChild(canvas, mEdgeView); + + mPositionController.advanceAnimation(); + } + + private void captureAnimationDone(final int offset) { + mHolding &= ~HOLD_CAPTURE_ANIMATION; + if (offset == 1) { + // Now the capture animation is done, enable the action bar. + mListener.onActionBarAllowed(true); + mListener.onActionBarWanted(); + } + snapback(); + } + + private void setPictureSize() { + mPositionController.setImageSize(mPicture.getSize(), null); + } + + private void snapback() { + mPositionController.snapback(); + } + + // ////////////////////////////////////////////////////////////////////////// + // Film mode focus switching + // ////////////////////////////////////////////////////////////////////////// + + private static int getRotated(final int degree, final int original, final int theother) { + return degree % 180 == 0 ? original : theother; + } + + public interface ITileImageAdapter { + + public int getImageHeight(); + + // Returns the rotation for the specified picture. + public int getImageRotation(); + + public int getImageWidth(); + + public int getLevelCount(); + + public ScreenNail getScreenNail(); + + /** + * The tile returned by this method can be specified this way: Assuming + * the image size is (width, height), first take the intersection of (0, + * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then + * extend this intersection region by borderSize pixels on each side. If + * in extending the region, we found some part of the region are outside + * the image, those pixels are filled with black.
+ *
+ * If level > 0, it does the same operation on a down-scaled version of + * the original image (down-scaled by a factor of 2^level), but (x, y) + * still refers to the coordinate on the original image.
+ *
+ * The method would be called in another thread. + */ + public Bitmap getTile(int level, int x, int y, int tileSize, int borderSize, BitmapPool pool); + + public void recycleScreenNail(); + + public boolean setData(BitmapRegionDecoder decoder, Bitmap bitmap, int orientation); + + } + + public interface Listener { + public void onActionBarAllowed(boolean allowed); + + public void onActionBarWanted(); + + public void onCurrentImageUpdated(); + + public void onPictureCenter(); + + public void onSingleTapUp(int x, int y); + } + + public static class Size { + public int width; + public int height; + } + + private class GestureListener implements GestureRecognizer.Listener { + private boolean mIgnoreUpEvent = false; + // If we can change mode for this scale gesture. + private boolean mCanChangeMode; + // If we have changed the film mode in this scaling gesture. + private boolean mModeChanged; + // If a scrolling has happened after a down gesture. + private boolean mScrolledAfterDown; + // The accumulated scaling change from a scaling gesture. + private float mAccScale; + + @Override + public boolean onDoubleTap(final float x, final float y) { + final PositionController controller = mPositionController; + final float scale = controller.getImageScale(); + // onDoubleTap happened on the second ACTION_DOWN. + // We need to ignore the next UP event. + mIgnoreUpEvent = true; + if (scale <= .75f || controller.isAtMinimalScale()) { + controller.zoomIn(x, y, Math.max(1.0f, scale * 1.5f)); + } else { + controller.resetToFullView(); + } + return true; + } + + @Override + public void onDown(final float x, final float y) { + + mModeChanged = false; + + mHolding |= HOLD_TOUCH_DOWN; + + mScrolledAfterDown = false; + } + + @Override + public boolean onFling(final float velocityX, final float velocityY) { + if (mModeChanged) return true; + flingImages(velocityX, velocityY); + return true; + } + + @Override + public boolean onScale(final float focusX, final float focusY, final float scale) { + if (mModeChanged) return true; + if (Float.isNaN(scale) || Float.isInfinite(scale)) return false; + + final int outOfRange = mPositionController.scaleBy(scale, focusX, focusY); + + // We wait for a large enough scale change before changing mode. + // Otherwise we may mistakenly treat a zoom-in gesture as zoom-out + // or vice versa. + mAccScale *= scale; + final boolean largeEnough = mAccScale < 0.97f || mAccScale > 1.03f; + + // If mode changes, we treat this scaling gesture has ended. + if (mCanChangeMode && largeEnough) { + if (outOfRange < 0 || outOfRange > 0) { + stopExtraScalingIfNeeded(); + + // Removing the touch down flag allows snapback to happen + // for film mode change. + mHolding &= ~HOLD_TOUCH_DOWN; + + // We need to call onScaleEnd() before setting mModeChanged + // to true. + onScaleEnd(); + mModeChanged = true; + return true; + } + } + + if (outOfRange != 0) { + startExtraScalingIfNeeded(); + } else { + stopExtraScalingIfNeeded(); + } + return true; + } + + @Override + public boolean onScaleBegin(final float focusX, final float focusY) { + // We ignore the scaling gesture if it is a camera preview. + mPositionController.beginScale(focusX, focusY); + // We can change mode if we are in film mode, or we are in page + // mode and at minimal scale. + mCanChangeMode = mPositionController.isAtMinimalScale(); + mAccScale = 1f; + return true; + } + + @Override + public void onScaleEnd() { + if (mModeChanged) return; + mPositionController.endScale(); + } + + @Override + public boolean onScroll(final float dx, final float dy, final float totalX, final float totalY) { + if (!mScrolledAfterDown) { + mScrolledAfterDown = true; + } + + final int dxi = (int) (-dx + 0.5f); + final int dyi = (int) (-dy + 0.5f); + mPositionController.scrollPage(dxi, dyi); + return true; + } + + @Override + public boolean onSingleTapUp(final float x, final float y) { + // On crespo running Android 2.3.6 (gingerbread), a pinch out + // gesture results in the + // following call sequence: onDown(), onUp() and then + // onSingleTapUp(). The correct + // sequence for a single-tap-up gesture should be: onDown(), + // onSingleTapUp() and onUp(). + // The call sequence for a pinch out gesture in JB is: onDown(), + // then onUp() and there's + // no onSingleTapUp(). Base on these observations, the following + // condition is added to + // filter out the false alarm where onSingleTapUp() is called within + // a pinch out + // gesture. The framework fix went into ICS. Refer to b/4588114. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if ((mHolding & HOLD_TOUCH_DOWN) == 0) return true; + } + + // We do this in addition to onUp() because we want the snapback of + // setFilmMode to happen. + mHolding &= ~HOLD_TOUCH_DOWN; + + if (mListener != null) { + // Do the inverse transform of the touch coordinates. + final Matrix m = getGLRoot().getCompensationMatrix(); + final Matrix inv = new Matrix(); + m.invert(inv); + final float[] pts = new float[]{x, y}; + inv.mapPoints(pts); + mListener.onSingleTapUp((int) (pts[0] + 0.5f), (int) (pts[1] + 0.5f)); + } + return true; + } + + @Override + public void onUp() { + mHolding &= ~HOLD_TOUCH_DOWN; + mEdgeView.onRelease(); + + if (mIgnoreUpEvent) { + mIgnoreUpEvent = false; + return; + } + + snapback(); + } + + private boolean flingImages(final float velocityX, final float velocityY) { + final int vx = (int) (velocityX + 0.5f); + final int vy = (int) (velocityY + 0.5f); + return mPositionController.flingPage(vx, vy); + } + + private void startExtraScalingIfNeeded() { + if (!mCancelExtraScalingPending) { + mHandler.sendEmptyMessageDelayed(MSG_CANCEL_EXTRA_SCALING, 700); + mPositionController.setExtraScalingRange(true); + mCancelExtraScalingPending = true; + } + } + + private void stopExtraScalingIfNeeded() { + if (mCancelExtraScalingPending) { + mHandler.removeMessages(MSG_CANCEL_EXTRA_SCALING); + mPositionController.setExtraScalingRange(false); + mCancelExtraScalingPending = false; + } + } + } + + private class MyHandler extends SynchronizedHandler { + + private MyHandler(final ImageViewerGLActivityOld activity) { + super(activity.getGLRoot()); + } + + @Override + public void handleMessage(final Message message) { + switch (message.what) { + case MSG_CANCEL_EXTRA_SCALING: { + mGestureRecognizer.cancelScale(); + mPositionController.setExtraScalingRange(false); + mCancelExtraScalingPending = false; + break; + } + case MSG_CAPTURE_ANIMATION_DONE: { + // message.arg1 is the offset parameter passed to + // switchWithCaptureAnimation(). + captureAnimationDone(message.arg1); + break; + } + default: + throw new AssertionError(message.what); + } + } + } + + private interface Picture { + void draw(GLCanvas canvas, Rect r); + + void forceSize(); // called when mCompensation changes + + Size getSize(); + + void reload(); + + void setScreenNail(ScreenNail s); + } + + class EventListener implements PositionController.Listener { + + @Override + public boolean isHoldingDown() { + return (mHolding & HOLD_TOUCH_DOWN) != 0; + } + + @Override + public void onAbsorb(final int velocity, final int direction) { + mEdgeView.onAbsorb(velocity, direction); + } + + @Override + public void onInvalidate() { + invalidate(); + } + + @Override + public void onPull(final int offset, final int direction) { + mEdgeView.onPull(offset, direction); + } + } + + class FullPicture implements Picture { + private int mRotation; + private final Size mSize = new Size(); + + @Override + public void draw(final GLCanvas canvas, final Rect r) { + drawTileView(canvas, r); + + // We want to have the following transitions: + // (1) Move camera preview out of its place: switch to film mode + // (2) Move camera preview into its place: switch to page mode + // The extra mWasCenter check makes sure (1) does not apply if in + // page mode, we move _to_ the camera preview from another picture. + + // Holdings except touch-down prevent the transitions. + if ((mHolding & ~HOLD_TOUCH_DOWN) != 0) return; + + if (mWantPictureCenterCallbacks && mPositionController.isCenter()) { + mListener.onPictureCenter(); + } + } + + @Override + public void forceSize() { + updateSize(); + mPositionController.forceImageSize(mSize); + } + + @Override + public Size getSize() { + return mSize; + } + + @Override + public void reload() { + // mImageWidth and mImageHeight will get updated + mTileView.notifyModelInvalidated(); + setScreenNail(mModel.getScreenNail()); + updateSize(); + } + + @Override + public void setScreenNail(final ScreenNail s) { + mTileView.setScreenNail(s); + } + + private void drawTileView(final GLCanvas canvas, final Rect r) { + final float imageScale = mPositionController.getImageScale(); + final int viewW = getWidth(); + final int viewH = getHeight(); + final float cx = r.exactCenterX(); + final float cy = r.exactCenterY(); + + canvas.save(GLCanvas.SAVE_FLAG_MATRIX | GLCanvas.SAVE_FLAG_ALPHA); + // Draw the tile view. + setTileViewPosition(cx, cy, viewW, viewH, imageScale); + renderChild(canvas, mTileView); + + // Draw the play video icon and the message. + canvas.translate((int) (cx + 0.5f), (int) (cy + 0.5f)); + + canvas.restore(); + } + + // Set the position of the tile view + private void setTileViewPosition(final float cx, final float cy, final int viewW, final int viewH, + final float scale) { + // Find out the bitmap coordinates of the center of the view + final int imageW = mPositionController.getImageWidth(); + final int imageH = mPositionController.getImageHeight(); + final int centerX = (int) (imageW / 2f + (viewW / 2f - cx) / scale + 0.5f); + final int centerY = (int) (imageH / 2f + (viewH / 2f - cy) / scale + 0.5f); + + final int inverseX = imageW - centerX; + final int inverseY = imageH - centerY; + int x, y; + switch (mRotation) { + case 0: + x = centerX; + y = centerY; + break; + case 90: + x = centerY; + y = inverseX; + break; + case 180: + x = inverseX; + y = inverseY; + break; + case 270: + x = inverseY; + y = centerX; + break; + default: + throw new RuntimeException(String.valueOf(mRotation)); + } + mTileView.setPosition(x, y, scale, mRotation); + } + + private void updateSize() { + mRotation = mModel.getImageRotation(); + + final int w = mTileView.mImageWidth; + final int h = mTileView.mImageHeight; + mSize.width = getRotated(mRotation, w, h); + mSize.height = getRotated(mRotation, h, w); + } + } } diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java b/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java index 8be0ff012..215d3c599 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java +++ b/twidere/src/main/java/org/mariotaku/gallery3d/ui/TileImageView.java @@ -23,7 +23,7 @@ import android.support.v4.util.LongSparseArray; import android.util.FloatMath; import android.util.Log; -import org.mariotaku.gallery3d.ImageViewerGLActivity; +import org.mariotaku.gallery3d.ImageViewerGLActivityOld; import org.mariotaku.gallery3d.util.ApiHelper; import org.mariotaku.gallery3d.util.BitmapPool; import org.mariotaku.gallery3d.util.DecodeUtils; @@ -36,723 +36,724 @@ import org.mariotaku.gallery3d.util.ThreadPool.JobContext; import java.util.concurrent.atomic.AtomicBoolean; public class TileImageView extends GLView { - public static final int SIZE_UNKNOWN = -1; - - private static final String TAG = "TileImageView"; - - // TILE_SIZE must be 2^N - 2. We put one pixel border in each side of the - // texture to avoid seams between tiles. - private static int TILE_SIZE; - private static final int TILE_BORDER = 1; - private static int BITMAP_SIZE; - private static final int UPLOAD_LIMIT = 1; - - private static BitmapPool sTilePool; - - /* - * This is the tile state in the CPU side. Life of a Tile: ACTIVATED - * (initial state) --> IN_QUEUE - by queueForDecode() --> RECYCLED - by - * recycleTile() IN_QUEUE --> DECODING - by decodeTile() --> RECYCLED - by - * recycleTile) DECODING --> RECYCLING - by recycleTile() --> DECODED - by - * decodeTile() --> DECODE_FAIL - by decodeTile() RECYCLING --> RECYCLED - - * by decodeTile() DECODED --> ACTIVATED - (after the decoded bitmap is - * uploaded) DECODED --> RECYCLED - by recycleTile() DECODE_FAIL -> RECYCLED - * - by recycleTile() RECYCLED --> ACTIVATED - by obtainTile() - */ - private static final int STATE_ACTIVATED = 0x01; - private static final int STATE_IN_QUEUE = 0x02; - private static final int STATE_DECODING = 0x04; - private static final int STATE_DECODED = 0x08; - private static final int STATE_DECODE_FAIL = 0x10; - private static final int STATE_RECYCLING = 0x20; - private static final int STATE_RECYCLED = 0x40; - - private PhotoView.ITileImageAdapter mModel; - private ScreenNail mScreenNail; - protected int mLevelCount; // cache the value of mScaledBitmaps.length - - // The mLevel variable indicates which level of bitmap we should use. - // Level 0 means the original full-sized bitmap, and a larger value means - // a smaller scaled bitmap (The width and height of each scaled bitmap is - // half size of the previous one). If the value is in [0, mLevelCount), we - // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value - // is mLevelCount, and that means we use mScreenNail for display. - private int mLevel = 0; - - // The offsets of the (left, top) of the upper-left tile to the (left, top) - // of the view. - private int mOffsetX; - private int mOffsetY; - - private int mUploadQuota; - private boolean mRenderComplete; - - private final RectF mSourceRect = new RectF(); - private final RectF mTargetRect = new RectF(); - - private final LongSparseArray mActiveTiles = new LongSparseArray(); - - // The following three queue is guarded by TileImageView.this - private final TileQueue mRecycledQueue = new TileQueue(); - private final TileQueue mUploadQueue = new TileQueue(); - private final TileQueue mDecodeQueue = new TileQueue(); - - // The width and height of the full-sized bitmap - protected int mImageWidth = SIZE_UNKNOWN; - protected int mImageHeight = SIZE_UNKNOWN; - - protected int mCenterX; - protected int mCenterY; - protected float mScale; - protected int mRotation; - - // Temp variables to avoid memory allocation - private final Rect mTileRange = new Rect(); - private final Rect mActiveRange[] = { new Rect(), new Rect() }; - - private final TileUploader mTileUploader = new TileUploader(); - private boolean mIsTextureFreed; - private Future mTileDecoder; - private final ThreadPool mThreadPool; - private boolean mBackgroundTileUploaded; - - public TileImageView(final ImageViewerGLActivity context) { - mThreadPool = context.getThreadPool(); - mTileDecoder = mThreadPool.submit(new TileDecoder()); - if (TILE_SIZE == 0) { - if (GalleryUtils.isHighResolution(context)) { - TILE_SIZE = 510; - } else { - TILE_SIZE = 254; - } - BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2; - sTilePool = ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER ? new BitmapPool(BITMAP_SIZE, - BITMAP_SIZE, 128) : null; - } - } - - public void freeTextures() { - mIsTextureFreed = true; - - if (mTileDecoder != null) { - mTileDecoder.cancel(); - mTileDecoder.get(); - mTileDecoder = null; - } - - final int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile texture = mActiveTiles.valueAt(i); - texture.recycle(); - } - mActiveTiles.clear(); - mTileRange.set(0, 0, 0, 0); - - synchronized (this) { - mUploadQueue.clean(); - mDecodeQueue.clean(); - Tile tile = mRecycledQueue.pop(); - while (tile != null) { - tile.recycle(); - tile = mRecycledQueue.pop(); - } - } - setScreenNail(null); - if (sTilePool != null) { - sTilePool.clear(); - } - } - - public void notifyModelInvalidated() { - invalidateTiles(); - if (mModel == null) { - mScreenNail = null; - mImageWidth = 0; - mImageHeight = 0; - mLevelCount = 0; - } else { - setScreenNail(mModel.getScreenNail()); - mImageWidth = mModel.getImageWidth(); - mImageHeight = mModel.getImageHeight(); - mLevelCount = mModel.getLevelCount(); - } - layoutTiles(mCenterX, mCenterY, mScale, mRotation); - invalidate(); - } - - public void prepareTextures() { - if (mTileDecoder == null) { - mTileDecoder = mThreadPool.submit(new TileDecoder()); - } - if (mIsTextureFreed) { - layoutTiles(mCenterX, mCenterY, mScale, mRotation); - mIsTextureFreed = false; - setScreenNail(mModel == null ? null : mModel.getScreenNail()); - } - } - - public void setModel(final PhotoView.ITileImageAdapter model) { - mModel = model; - if (model != null) { - notifyModelInvalidated(); - } - } - - public boolean setPosition(final int centerX, final int centerY, final float scale, final int rotation) { - if (mCenterX == centerX && mCenterY == centerY && mScale == scale && mRotation == rotation) return false; - mCenterX = centerX; - mCenterY = centerY; - mScale = scale; - mRotation = rotation; - layoutTiles(centerX, centerY, scale, rotation); - invalidate(); - return true; - } - - public void setScreenNail(final ScreenNail s) { - mScreenNail = s; - } - - @Override - protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { - super.onLayout(changeSize, left, top, right, bottom); - if (changeSize) { - layoutTiles(mCenterX, mCenterY, mScale, mRotation); - } - } - - @Override - protected void render(final GLCanvas canvas) { - mUploadQuota = UPLOAD_LIMIT; - mRenderComplete = true; - - final int level = mLevel; - final int rotation = mRotation; - int flags = 0; - if (rotation != 0) { - flags |= GLCanvas.SAVE_FLAG_MATRIX; - } - - if (flags != 0) { - canvas.save(flags); - if (rotation != 0) { - final int centerX = getWidth() / 2, centerY = getHeight() / 2; - canvas.translate(centerX, centerY); - canvas.rotate(rotation, 0, 0, 1); - canvas.translate(-centerX, -centerY); - } - } - try { - if (level != mLevelCount && !isScreenNailAnimating()) { - if (mScreenNail != null) { - mScreenNail.noDraw(); - } - - final int size = TILE_SIZE << level; - final float length = size * mScale; - final Rect r = mTileRange; - - for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) { - final float y = mOffsetY + i * length; - for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) { - final float x = mOffsetX + j * length; - drawTile(canvas, tx, ty, level, x, y, length); - } - } - } else if (mScreenNail != null) { - mScreenNail.draw(canvas, mOffsetX, mOffsetY, Math.round(mImageWidth * mScale), - Math.round(mImageHeight * mScale)); - if (isScreenNailAnimating()) { - invalidate(); - } - } - } finally { - if (flags != 0) { - canvas.restore(); - } - } - - if (mRenderComplete) { - if (!mBackgroundTileUploaded) { - uploadBackgroundTiles(canvas); - } - } else { - invalidate(); - } - } - - private void activateTile(final int x, final int y, final int level) { - final long key = makeTileKey(x, y, level); - Tile tile = mActiveTiles.get(key); - if (tile != null) { - if (tile.mTileState == STATE_IN_QUEUE) { - tile.mTileState = STATE_ACTIVATED; - } - return; - } - tile = obtainTile(x, y, level); - mActiveTiles.put(key, tile); - } - - private boolean decodeTile(final Tile tile) { - synchronized (this) { - if (tile.mTileState != STATE_IN_QUEUE) return false; - tile.mTileState = STATE_DECODING; - } - final boolean decodeComplete = tile.decode(); - synchronized (this) { - if (tile.mTileState == STATE_RECYCLING) { - tile.mTileState = STATE_RECYCLED; - if (tile.mDecodedTile != null) { - if (sTilePool != null) { - sTilePool.recycle(tile.mDecodedTile); - } - tile.mDecodedTile = null; - } - mRecycledQueue.push(tile); - return false; - } - tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; - return decodeComplete; - } - } - - // Draw the tile to a square at canvas that locates at (x, y) and - // has a side length of length. - private void drawTile(final GLCanvas canvas, final int tx, final int ty, final int level, final float x, - final float y, final float length) { - final RectF source = mSourceRect; - final RectF target = mTargetRect; - target.set(x, y, x + length, y + length); - source.set(0, 0, TILE_SIZE, TILE_SIZE); - - final Tile tile = getTile(tx, ty, level); - if (tile != null) { - if (!tile.isContentValid()) { - if (tile.mTileState == STATE_DECODED) { - if (mUploadQuota > 0) { - --mUploadQuota; - tile.updateContent(canvas); - } else { - mRenderComplete = false; - } - } else if (tile.mTileState != STATE_DECODE_FAIL) { - mRenderComplete = false; - queueForDecode(tile); - } - } - if (drawTile(tile, canvas, source, target)) return; - } - if (mScreenNail != null) { - final int size = TILE_SIZE << level; - final float scaleX = (float) mScreenNail.getWidth() / mImageWidth; - final float scaleY = (float) mScreenNail.getHeight() / mImageHeight; - source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, (ty + size) * scaleY); - mScreenNail.draw(canvas, source, target); - } - } - - // If the bitmap is scaled by the given factor "scale", return the - // rectangle containing visible range. The left-top coordinate returned is - // aligned to the tile boundary. - // - // (cX, cY) is the point on the original bitmap which will be put in the - // center of the ImageViewer. - private void getRange(final Rect out, final int cX, final int cY, final int level, final float scale, - final int rotation) { - - final double radians = Math.toRadians(-rotation); - final double w = getWidth(); - final double h = getHeight(); - - final double cos = Math.cos(radians); - final double sin = Math.sin(radians); - final int width = (int) Math.ceil(Math.max(Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h))); - final int height = (int) Math.ceil(Math.max(Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h))); - - int left = (int) FloatMath.floor(cX - width / (2f * scale)); - int top = (int) FloatMath.floor(cY - height / (2f * scale)); - int right = (int) FloatMath.ceil(left + width / scale); - int bottom = (int) FloatMath.ceil(top + height / scale); - - // align the rectangle to tile boundary - final int size = TILE_SIZE << level; - left = Math.max(0, size * (left / size)); - top = Math.max(0, size * (top / size)); - right = Math.min(mImageWidth, right); - bottom = Math.min(mImageHeight, bottom); - - out.set(left, top, right, bottom); - } - - private void getRange(final Rect out, final int cX, final int cY, final int level, final int rotation) { - getRange(out, cX, cY, level, 1f / (1 << level + 1), rotation); - } - - private Tile getTile(final int x, final int y, final int level) { - return mActiveTiles.get(makeTileKey(x, y, level)); - } - - private synchronized void invalidateTiles() { - mDecodeQueue.clean(); - mUploadQueue.clean(); - - // TODO disable decoder - final int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile tile = mActiveTiles.valueAt(i); - recycleTile(tile); - } - mActiveTiles.clear(); - } - - private boolean isScreenNailAnimating() { - return false; - } - - // Prepare the tiles we want to use for display. - // - // 1. Decide the tile level we want to use for display. - // 2. Decide the tile levels we want to keep as texture (in addition to - // the one we use for display). - // 3. Recycle unused tiles. - // 4. Activate the tiles we want. - private void layoutTiles(final int centerX, final int centerY, final float scale, final int rotation) { - // The width and height of this view. - final int width = getWidth(); - final int height = getHeight(); - - // The tile levels we want to keep as texture is in the range - // [fromLevel, endLevel). - int fromLevel; - int endLevel; - - // We want to use a texture larger than or equal to the display size. - mLevel = GalleryUtils.clamp(GalleryUtils.floorLog2(1f / scale), 0, mLevelCount); - - // We want to keep one more tile level as texture in addition to what - // we use for display. So it can be faster when the scale moves to the - // next level. We choose a level closer to the current scale. - if (mLevel != mLevelCount) { - final Rect range = mTileRange; - getRange(range, centerX, centerY, mLevel, scale, rotation); - mOffsetX = Math.round(width / 2f + (range.left - centerX) * scale); - mOffsetY = Math.round(height / 2f + (range.top - centerY) * scale); - fromLevel = scale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel; - } else { - // Activate the tiles of the smallest two levels. - fromLevel = mLevel - 2; - mOffsetX = Math.round(width / 2f - centerX * scale); - mOffsetY = Math.round(height / 2f - centerY * scale); - } - - fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2)); - endLevel = Math.min(fromLevel + 2, mLevelCount); - - final Rect range[] = mActiveRange; - for (int i = fromLevel; i < endLevel; ++i) { - getRange(range[i - fromLevel], centerX, centerY, i, rotation); - } - - // If rotation is transient, don't update the tile. - if (rotation % 90 != 0) return; - - synchronized (this) { - mDecodeQueue.clean(); - mUploadQueue.clean(); - mBackgroundTileUploaded = false; - - // Recycle unused tiles: if the level of the active tile is outside - // the - // range [fromLevel, endLevel) or not in the visible range. - int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile tile = mActiveTiles.valueAt(i); - final int level = tile.mTileLevel; - if (level < fromLevel || level >= endLevel || !range[level - fromLevel].contains(tile.mX, tile.mY)) { - mActiveTiles.removeAt(i); - i--; - n--; - recycleTile(tile); - } - } - } - - for (int i = fromLevel; i < endLevel; ++i) { - final int size = TILE_SIZE << i; - final Rect r = range[i - fromLevel]; - for (int y = r.top, bottom = r.bottom; y < bottom; y += size) { - for (int x = r.left, right = r.right; x < right; x += size) { - activateTile(x, y, i); - } - } - } - invalidate(); - } - - private synchronized Tile obtainTile(final int x, final int y, final int level) { - final Tile tile = mRecycledQueue.pop(); - if (tile != null) { - tile.mTileState = STATE_ACTIVATED; - tile.update(x, y, level); - return tile; - } - return new Tile(x, y, level); - } - - private synchronized void queueForDecode(final Tile tile) { - if (tile.mTileState == STATE_ACTIVATED) { - tile.mTileState = STATE_IN_QUEUE; - if (mDecodeQueue.push(tile)) { - notifyAll(); - } - } - } - - private void queueForUpload(final Tile tile) { - synchronized (this) { - mUploadQueue.push(tile); - } - if (mTileUploader.mActive.compareAndSet(false, true)) { - getGLRoot().addOnGLIdleListener(mTileUploader); - } - } - - private synchronized void recycleTile(final Tile tile) { - if (tile.mTileState == STATE_DECODING) { - tile.mTileState = STATE_RECYCLING; - return; - } - tile.mTileState = STATE_RECYCLED; - if (tile.mDecodedTile != null) { - if (sTilePool != null) { - sTilePool.recycle(tile.mDecodedTile); - } - tile.mDecodedTile = null; - } - mRecycledQueue.push(tile); - } - - private void uploadBackgroundTiles(final GLCanvas canvas) { - mBackgroundTileUploaded = true; - final int n = mActiveTiles.size(); - for (int i = 0; i < n; i++) { - final Tile tile = mActiveTiles.valueAt(i); - if (!tile.isContentValid()) { - queueForDecode(tile); - } - } - } - - private static boolean drawTile(Tile tile, final GLCanvas canvas, final RectF source, final RectF target) { - while (true) { - if (tile.isContentValid()) { - // offset source rectangle for the texture border. - source.offset(TILE_BORDER, TILE_BORDER); - canvas.drawTexture(tile, source, target); - return true; - } - - // Parent can be divided to four quads and tile is one of the four. - final Tile parent = tile.getParentTile(); - if (parent == null) return false; - if (tile.mX == parent.mX) { - source.left /= 2f; - source.right /= 2f; - } else { - source.left = (TILE_SIZE + source.left) / 2f; - source.right = (TILE_SIZE + source.right) / 2f; - } - if (tile.mY == parent.mY) { - source.top /= 2f; - source.bottom /= 2f; - } else { - source.top = (TILE_SIZE + source.top) / 2f; - source.bottom = (TILE_SIZE + source.bottom) / 2f; - } - tile = parent; - } - } - - private static long makeTileKey(final int x, final int y, final int level) { - long result = x; - result = result << 16 | y; - result = result << 16 | level; - return result; - } - - private class Tile extends UploadedTexture { - public int mX; - public int mY; - public int mTileLevel; - public Tile mNext; - public Bitmap mDecodedTile; - public volatile int mTileState = STATE_ACTIVATED; - - public Tile(final int x, final int y, final int level) { - mX = x; - mY = y; - mTileLevel = level; - } - - public Tile getParentTile() { - if (mTileLevel + 1 == mLevelCount) return null; - final int size = TILE_SIZE << mTileLevel + 1; - final int x = size * (mX / size); - final int y = size * (mY / size); - return getTile(x, y, mTileLevel + 1); - } - - @Override - public int getTextureHeight() { - return TILE_SIZE + TILE_BORDER * 2; - } - - // We override getTextureWidth() and getTextureHeight() here, so the - // texture can be re-used for different tiles regardless of the actual - // size of the tile (which may be small because it is a tile at the - // boundary). - @Override - public int getTextureWidth() { - return TILE_SIZE + TILE_BORDER * 2; - } - - @Override - public String toString() { - return String.format("tile(%s, %s, %s / %s)", mX / TILE_SIZE, mY / TILE_SIZE, mLevel, mLevelCount); - } - - public void update(final int x, final int y, final int level) { - mX = x; - mY = y; - mTileLevel = level; - invalidateContent(); - } - - @Override - protected void onFreeBitmap(final Bitmap bitmap) { - if (sTilePool != null) { - sTilePool.recycle(bitmap); - } - } - - @Override - protected Bitmap onGetBitmap() { - GalleryUtils.assertTrue(mTileState == STATE_DECODED); - - // We need to override the width and height, so that we won't - // draw beyond the boundaries. - final int rightEdge = (mImageWidth - mX >> mTileLevel) + TILE_BORDER; - final int bottomEdge = (mImageHeight - mY >> mTileLevel) + TILE_BORDER; - setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge)); - - final Bitmap bitmap = mDecodedTile; - mDecodedTile = null; - mTileState = STATE_ACTIVATED; - return bitmap; - } - - boolean decode() { - // Get a tile from the original image. The tile is down-scaled - // by (1 << mTilelevel) from a region in the original image. - try { - mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(mTileLevel, mX, mY, TILE_SIZE, - TILE_BORDER, sTilePool)); - } catch (final Throwable t) { - Log.w(TAG, "fail to decode tile", t); - } - return mDecodedTile != null; - } - } - - private class TileDecoder implements ThreadPool.Job { - - private final CancelListener mNotifier = new CancelListener() { - @Override - public void onCancel() { - synchronized (TileImageView.this) { - TileImageView.this.notifyAll(); - } - } - }; - - @Override - public Void run(final JobContext jc) { - jc.setMode(ThreadPool.MODE_NONE); - jc.setCancelListener(mNotifier); - while (!jc.isCancelled()) { - Tile tile = null; - synchronized (TileImageView.this) { - tile = mDecodeQueue.pop(); - if (tile == null && !jc.isCancelled()) { - GalleryUtils.waitWithoutInterrupt(TileImageView.this); - } - } - if (tile == null) { - continue; - } - if (decodeTile(tile)) { - queueForUpload(tile); - } - } - return null; - } - } - - private static class TileQueue { - private Tile mHead; - - public void clean() { - mHead = null; - } - - public Tile pop() { - final Tile tile = mHead; - if (tile != null) { - mHead = tile.mNext; - } - return tile; - } - - public boolean push(final Tile tile) { - final boolean wasEmpty = mHead == null; - tile.mNext = mHead; - mHead = tile; - return wasEmpty; - } - } - - private class TileUploader implements GLRoot.OnGLIdleListener { - AtomicBoolean mActive = new AtomicBoolean(false); - - @Override - public boolean onGLIdle(final GLCanvas canvas, final boolean renderRequested) { - // Skips uploading if there is a pending rendering request. - // Returns true to keep uploading in next rendering loop. - if (renderRequested) return true; - int quota = UPLOAD_LIMIT; - Tile tile = null; - while (quota > 0) { - synchronized (TileImageView.this) { - tile = mUploadQueue.pop(); - } - if (tile == null) { - break; - } - if (!tile.isContentValid()) { - final boolean hasBeenLoaded = tile.isLoaded(); - if (tile.mTileState != STATE_DECODED) return false; - tile.updateContent(canvas); - if (!hasBeenLoaded) { - tile.draw(canvas, 0, 0); - } - --quota; - } - } - if (tile == null) { - mActive.set(false); - } - return tile != null; - } - } + public static final int SIZE_UNKNOWN = -1; + + private static final String TAG = "TileImageView"; + + // TILE_SIZE must be 2^N - 2. We put one pixel border in each side of the + // texture to avoid seams between tiles. + private static int TILE_SIZE; + private static final int TILE_BORDER = 1; + private static int BITMAP_SIZE; + private static final int UPLOAD_LIMIT = 1; + + private static BitmapPool sTilePool; + + /* + * This is the tile state in the CPU side. Life of a Tile: ACTIVATED + * (initial state) --> IN_QUEUE - by queueForDecode() --> RECYCLED - by + * recycleTile() IN_QUEUE --> DECODING - by decodeTile() --> RECYCLED - by + * recycleTile) DECODING --> RECYCLING - by recycleTile() --> DECODED - by + * decodeTile() --> DECODE_FAIL - by decodeTile() RECYCLING --> RECYCLED - + * by decodeTile() DECODED --> ACTIVATED - (after the decoded bitmap is + * uploaded) DECODED --> RECYCLED - by recycleTile() DECODE_FAIL -> RECYCLED + * - by recycleTile() RECYCLED --> ACTIVATED - by obtainTile() + */ + private static final int STATE_ACTIVATED = 0x01; + private static final int STATE_IN_QUEUE = 0x02; + private static final int STATE_DECODING = 0x04; + private static final int STATE_DECODED = 0x08; + private static final int STATE_DECODE_FAIL = 0x10; + private static final int STATE_RECYCLING = 0x20; + private static final int STATE_RECYCLED = 0x40; + + private PhotoView.ITileImageAdapter mModel; + private ScreenNail mScreenNail; + protected int mLevelCount; // cache the value of mScaledBitmaps.length + + // The mLevel variable indicates which level of bitmap we should use. + // Level 0 means the original full-sized bitmap, and a larger value means + // a smaller scaled bitmap (The width and height of each scaled bitmap is + // half size of the previous one). If the value is in [0, mLevelCount), we + // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value + // is mLevelCount, and that means we use mScreenNail for display. + private int mLevel = 0; + + // The offsets of the (left, top) of the upper-left tile to the (left, top) + // of the view. + private int mOffsetX; + private int mOffsetY; + + private int mUploadQuota; + private boolean mRenderComplete; + + private final RectF mSourceRect = new RectF(); + private final RectF mTargetRect = new RectF(); + + private final LongSparseArray mActiveTiles = new LongSparseArray(); + + // The following three queue is guarded by TileImageView.this + private final TileQueue mRecycledQueue = new TileQueue(); + private final TileQueue mUploadQueue = new TileQueue(); + private final TileQueue mDecodeQueue = new TileQueue(); + + // The width and height of the full-sized bitmap + protected int mImageWidth = SIZE_UNKNOWN; + protected int mImageHeight = SIZE_UNKNOWN; + + protected int mCenterX; + protected int mCenterY; + protected float mScale; + protected int mRotation; + + // Temp variables to avoid memory allocation + private final Rect mTileRange = new Rect(); + private final Rect mActiveRange[] = {new Rect(), new Rect()}; + + private final TileUploader mTileUploader = new TileUploader(); + private boolean mIsTextureFreed; + private Future mTileDecoder; + private final ThreadPool mThreadPool; + private boolean mBackgroundTileUploaded; + + public TileImageView(final ImageViewerGLActivityOld context) { + mThreadPool = context.getThreadPool(); + mTileDecoder = mThreadPool.submit(new TileDecoder()); + if (TILE_SIZE == 0) { + if (GalleryUtils.isHighResolution(context)) { + TILE_SIZE = 510; + } else { + TILE_SIZE = 254; + } + BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2; + sTilePool = ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER ? new BitmapPool(BITMAP_SIZE, + BITMAP_SIZE, 128) : null; + } + } + + public void freeTextures() { + mIsTextureFreed = true; + + if (mTileDecoder != null) { + mTileDecoder.cancel(); + mTileDecoder.get(); + mTileDecoder = null; + } + + final int n = mActiveTiles.size(); + for (int i = 0; i < n; i++) { + final Tile texture = mActiveTiles.valueAt(i); + texture.recycle(); + } + mActiveTiles.clear(); + mTileRange.set(0, 0, 0, 0); + + synchronized (this) { + mUploadQueue.clean(); + mDecodeQueue.clean(); + Tile tile = mRecycledQueue.pop(); + while (tile != null) { + tile.recycle(); + tile = mRecycledQueue.pop(); + } + } + setScreenNail(null); + if (sTilePool != null) { + sTilePool.clear(); + } + } + + public void notifyModelInvalidated() { + invalidateTiles(); + if (mModel == null) { + mScreenNail = null; + mImageWidth = 0; + mImageHeight = 0; + mLevelCount = 0; + } else { + setScreenNail(mModel.getScreenNail()); + mImageWidth = mModel.getImageWidth(); + mImageHeight = mModel.getImageHeight(); + mLevelCount = mModel.getLevelCount(); + } + layoutTiles(mCenterX, mCenterY, mScale, mRotation); + invalidate(); + } + + public void prepareTextures() { + if (mTileDecoder == null) { + mTileDecoder = mThreadPool.submit(new TileDecoder()); + } + if (mIsTextureFreed) { + layoutTiles(mCenterX, mCenterY, mScale, mRotation); + mIsTextureFreed = false; + setScreenNail(mModel == null ? null : mModel.getScreenNail()); + } + } + + public void setModel(final PhotoView.ITileImageAdapter model) { + mModel = model; + if (model != null) { + notifyModelInvalidated(); + } + } + + public boolean setPosition(final int centerX, final int centerY, final float scale, final int rotation) { + if (mCenterX == centerX && mCenterY == centerY && mScale == scale && mRotation == rotation) + return false; + mCenterX = centerX; + mCenterY = centerY; + mScale = scale; + mRotation = rotation; + layoutTiles(centerX, centerY, scale, rotation); + invalidate(); + return true; + } + + public void setScreenNail(final ScreenNail s) { + mScreenNail = s; + } + + @Override + protected void onLayout(final boolean changeSize, final int left, final int top, final int right, final int bottom) { + super.onLayout(changeSize, left, top, right, bottom); + if (changeSize) { + layoutTiles(mCenterX, mCenterY, mScale, mRotation); + } + } + + @Override + protected void render(final GLCanvas canvas) { + mUploadQuota = UPLOAD_LIMIT; + mRenderComplete = true; + + final int level = mLevel; + final int rotation = mRotation; + int flags = 0; + if (rotation != 0) { + flags |= GLCanvas.SAVE_FLAG_MATRIX; + } + + if (flags != 0) { + canvas.save(flags); + if (rotation != 0) { + final int centerX = getWidth() / 2, centerY = getHeight() / 2; + canvas.translate(centerX, centerY); + canvas.rotate(rotation, 0, 0, 1); + canvas.translate(-centerX, -centerY); + } + } + try { + if (level != mLevelCount && !isScreenNailAnimating()) { + if (mScreenNail != null) { + mScreenNail.noDraw(); + } + + final int size = TILE_SIZE << level; + final float length = size * mScale; + final Rect r = mTileRange; + + for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) { + final float y = mOffsetY + i * length; + for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) { + final float x = mOffsetX + j * length; + drawTile(canvas, tx, ty, level, x, y, length); + } + } + } else if (mScreenNail != null) { + mScreenNail.draw(canvas, mOffsetX, mOffsetY, Math.round(mImageWidth * mScale), + Math.round(mImageHeight * mScale)); + if (isScreenNailAnimating()) { + invalidate(); + } + } + } finally { + if (flags != 0) { + canvas.restore(); + } + } + + if (mRenderComplete) { + if (!mBackgroundTileUploaded) { + uploadBackgroundTiles(canvas); + } + } else { + invalidate(); + } + } + + private void activateTile(final int x, final int y, final int level) { + final long key = makeTileKey(x, y, level); + Tile tile = mActiveTiles.get(key); + if (tile != null) { + if (tile.mTileState == STATE_IN_QUEUE) { + tile.mTileState = STATE_ACTIVATED; + } + return; + } + tile = obtainTile(x, y, level); + mActiveTiles.put(key, tile); + } + + private boolean decodeTile(final Tile tile) { + synchronized (this) { + if (tile.mTileState != STATE_IN_QUEUE) return false; + tile.mTileState = STATE_DECODING; + } + final boolean decodeComplete = tile.decode(); + synchronized (this) { + if (tile.mTileState == STATE_RECYCLING) { + tile.mTileState = STATE_RECYCLED; + if (tile.mDecodedTile != null) { + if (sTilePool != null) { + sTilePool.recycle(tile.mDecodedTile); + } + tile.mDecodedTile = null; + } + mRecycledQueue.push(tile); + return false; + } + tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; + return decodeComplete; + } + } + + // Draw the tile to a square at canvas that locates at (x, y) and + // has a side length of length. + private void drawTile(final GLCanvas canvas, final int tx, final int ty, final int level, final float x, + final float y, final float length) { + final RectF source = mSourceRect; + final RectF target = mTargetRect; + target.set(x, y, x + length, y + length); + source.set(0, 0, TILE_SIZE, TILE_SIZE); + + final Tile tile = getTile(tx, ty, level); + if (tile != null) { + if (!tile.isContentValid()) { + if (tile.mTileState == STATE_DECODED) { + if (mUploadQuota > 0) { + --mUploadQuota; + tile.updateContent(canvas); + } else { + mRenderComplete = false; + } + } else if (tile.mTileState != STATE_DECODE_FAIL) { + mRenderComplete = false; + queueForDecode(tile); + } + } + if (drawTile(tile, canvas, source, target)) return; + } + if (mScreenNail != null) { + final int size = TILE_SIZE << level; + final float scaleX = (float) mScreenNail.getWidth() / mImageWidth; + final float scaleY = (float) mScreenNail.getHeight() / mImageHeight; + source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, (ty + size) * scaleY); + mScreenNail.draw(canvas, source, target); + } + } + + // If the bitmap is scaled by the given factor "scale", return the + // rectangle containing visible range. The left-top coordinate returned is + // aligned to the tile boundary. + // + // (cX, cY) is the point on the original bitmap which will be put in the + // center of the ImageViewer. + private void getRange(final Rect out, final int cX, final int cY, final int level, final float scale, + final int rotation) { + + final double radians = Math.toRadians(-rotation); + final double w = getWidth(); + final double h = getHeight(); + + final double cos = Math.cos(radians); + final double sin = Math.sin(radians); + final int width = (int) Math.ceil(Math.max(Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h))); + final int height = (int) Math.ceil(Math.max(Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h))); + + int left = (int) FloatMath.floor(cX - width / (2f * scale)); + int top = (int) FloatMath.floor(cY - height / (2f * scale)); + int right = (int) FloatMath.ceil(left + width / scale); + int bottom = (int) FloatMath.ceil(top + height / scale); + + // align the rectangle to tile boundary + final int size = TILE_SIZE << level; + left = Math.max(0, size * (left / size)); + top = Math.max(0, size * (top / size)); + right = Math.min(mImageWidth, right); + bottom = Math.min(mImageHeight, bottom); + + out.set(left, top, right, bottom); + } + + private void getRange(final Rect out, final int cX, final int cY, final int level, final int rotation) { + getRange(out, cX, cY, level, 1f / (1 << level + 1), rotation); + } + + private Tile getTile(final int x, final int y, final int level) { + return mActiveTiles.get(makeTileKey(x, y, level)); + } + + private synchronized void invalidateTiles() { + mDecodeQueue.clean(); + mUploadQueue.clean(); + + // TODO disable decoder + final int n = mActiveTiles.size(); + for (int i = 0; i < n; i++) { + final Tile tile = mActiveTiles.valueAt(i); + recycleTile(tile); + } + mActiveTiles.clear(); + } + + private boolean isScreenNailAnimating() { + return false; + } + + // Prepare the tiles we want to use for display. + // + // 1. Decide the tile level we want to use for display. + // 2. Decide the tile levels we want to keep as texture (in addition to + // the one we use for display). + // 3. Recycle unused tiles. + // 4. Activate the tiles we want. + private void layoutTiles(final int centerX, final int centerY, final float scale, final int rotation) { + // The width and height of this view. + final int width = getWidth(); + final int height = getHeight(); + + // The tile levels we want to keep as texture is in the range + // [fromLevel, endLevel). + int fromLevel; + int endLevel; + + // We want to use a texture larger than or equal to the display size. + mLevel = GalleryUtils.clamp(GalleryUtils.floorLog2(1f / scale), 0, mLevelCount); + + // We want to keep one more tile level as texture in addition to what + // we use for display. So it can be faster when the scale moves to the + // next level. We choose a level closer to the current scale. + if (mLevel != mLevelCount) { + final Rect range = mTileRange; + getRange(range, centerX, centerY, mLevel, scale, rotation); + mOffsetX = Math.round(width / 2f + (range.left - centerX) * scale); + mOffsetY = Math.round(height / 2f + (range.top - centerY) * scale); + fromLevel = scale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel; + } else { + // Activate the tiles of the smallest two levels. + fromLevel = mLevel - 2; + mOffsetX = Math.round(width / 2f - centerX * scale); + mOffsetY = Math.round(height / 2f - centerY * scale); + } + + fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2)); + endLevel = Math.min(fromLevel + 2, mLevelCount); + + final Rect range[] = mActiveRange; + for (int i = fromLevel; i < endLevel; ++i) { + getRange(range[i - fromLevel], centerX, centerY, i, rotation); + } + + // If rotation is transient, don't update the tile. + if (rotation % 90 != 0) return; + + synchronized (this) { + mDecodeQueue.clean(); + mUploadQueue.clean(); + mBackgroundTileUploaded = false; + + // Recycle unused tiles: if the level of the active tile is outside + // the + // range [fromLevel, endLevel) or not in the visible range. + int n = mActiveTiles.size(); + for (int i = 0; i < n; i++) { + final Tile tile = mActiveTiles.valueAt(i); + final int level = tile.mTileLevel; + if (level < fromLevel || level >= endLevel || !range[level - fromLevel].contains(tile.mX, tile.mY)) { + mActiveTiles.removeAt(i); + i--; + n--; + recycleTile(tile); + } + } + } + + for (int i = fromLevel; i < endLevel; ++i) { + final int size = TILE_SIZE << i; + final Rect r = range[i - fromLevel]; + for (int y = r.top, bottom = r.bottom; y < bottom; y += size) { + for (int x = r.left, right = r.right; x < right; x += size) { + activateTile(x, y, i); + } + } + } + invalidate(); + } + + private synchronized Tile obtainTile(final int x, final int y, final int level) { + final Tile tile = mRecycledQueue.pop(); + if (tile != null) { + tile.mTileState = STATE_ACTIVATED; + tile.update(x, y, level); + return tile; + } + return new Tile(x, y, level); + } + + private synchronized void queueForDecode(final Tile tile) { + if (tile.mTileState == STATE_ACTIVATED) { + tile.mTileState = STATE_IN_QUEUE; + if (mDecodeQueue.push(tile)) { + notifyAll(); + } + } + } + + private void queueForUpload(final Tile tile) { + synchronized (this) { + mUploadQueue.push(tile); + } + if (mTileUploader.mActive.compareAndSet(false, true)) { + getGLRoot().addOnGLIdleListener(mTileUploader); + } + } + + private synchronized void recycleTile(final Tile tile) { + if (tile.mTileState == STATE_DECODING) { + tile.mTileState = STATE_RECYCLING; + return; + } + tile.mTileState = STATE_RECYCLED; + if (tile.mDecodedTile != null) { + if (sTilePool != null) { + sTilePool.recycle(tile.mDecodedTile); + } + tile.mDecodedTile = null; + } + mRecycledQueue.push(tile); + } + + private void uploadBackgroundTiles(final GLCanvas canvas) { + mBackgroundTileUploaded = true; + final int n = mActiveTiles.size(); + for (int i = 0; i < n; i++) { + final Tile tile = mActiveTiles.valueAt(i); + if (!tile.isContentValid()) { + queueForDecode(tile); + } + } + } + + private static boolean drawTile(Tile tile, final GLCanvas canvas, final RectF source, final RectF target) { + while (true) { + if (tile.isContentValid()) { + // offset source rectangle for the texture border. + source.offset(TILE_BORDER, TILE_BORDER); + canvas.drawTexture(tile, source, target); + return true; + } + + // Parent can be divided to four quads and tile is one of the four. + final Tile parent = tile.getParentTile(); + if (parent == null) return false; + if (tile.mX == parent.mX) { + source.left /= 2f; + source.right /= 2f; + } else { + source.left = (TILE_SIZE + source.left) / 2f; + source.right = (TILE_SIZE + source.right) / 2f; + } + if (tile.mY == parent.mY) { + source.top /= 2f; + source.bottom /= 2f; + } else { + source.top = (TILE_SIZE + source.top) / 2f; + source.bottom = (TILE_SIZE + source.bottom) / 2f; + } + tile = parent; + } + } + + private static long makeTileKey(final int x, final int y, final int level) { + long result = x; + result = result << 16 | y; + result = result << 16 | level; + return result; + } + + private class Tile extends UploadedTexture { + public int mX; + public int mY; + public int mTileLevel; + public Tile mNext; + public Bitmap mDecodedTile; + public volatile int mTileState = STATE_ACTIVATED; + + public Tile(final int x, final int y, final int level) { + mX = x; + mY = y; + mTileLevel = level; + } + + public Tile getParentTile() { + if (mTileLevel + 1 == mLevelCount) return null; + final int size = TILE_SIZE << mTileLevel + 1; + final int x = size * (mX / size); + final int y = size * (mY / size); + return getTile(x, y, mTileLevel + 1); + } + + @Override + public int getTextureHeight() { + return TILE_SIZE + TILE_BORDER * 2; + } + + // We override getTextureWidth() and getTextureHeight() here, so the + // texture can be re-used for different tiles regardless of the actual + // size of the tile (which may be small because it is a tile at the + // boundary). + @Override + public int getTextureWidth() { + return TILE_SIZE + TILE_BORDER * 2; + } + + @Override + public String toString() { + return String.format("tile(%s, %s, %s / %s)", mX / TILE_SIZE, mY / TILE_SIZE, mLevel, mLevelCount); + } + + public void update(final int x, final int y, final int level) { + mX = x; + mY = y; + mTileLevel = level; + invalidateContent(); + } + + @Override + protected void onFreeBitmap(final Bitmap bitmap) { + if (sTilePool != null) { + sTilePool.recycle(bitmap); + } + } + + @Override + protected Bitmap onGetBitmap() { + GalleryUtils.assertTrue(mTileState == STATE_DECODED); + + // We need to override the width and height, so that we won't + // draw beyond the boundaries. + final int rightEdge = (mImageWidth - mX >> mTileLevel) + TILE_BORDER; + final int bottomEdge = (mImageHeight - mY >> mTileLevel) + TILE_BORDER; + setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge)); + + final Bitmap bitmap = mDecodedTile; + mDecodedTile = null; + mTileState = STATE_ACTIVATED; + return bitmap; + } + + boolean decode() { + // Get a tile from the original image. The tile is down-scaled + // by (1 << mTilelevel) from a region in the original image. + try { + mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(mTileLevel, mX, mY, TILE_SIZE, + TILE_BORDER, sTilePool)); + } catch (final Throwable t) { + Log.w(TAG, "fail to decode tile", t); + } + return mDecodedTile != null; + } + } + + private class TileDecoder implements ThreadPool.Job { + + private final CancelListener mNotifier = new CancelListener() { + @Override + public void onCancel() { + synchronized (TileImageView.this) { + TileImageView.this.notifyAll(); + } + } + }; + + @Override + public Void run(final JobContext jc) { + jc.setMode(ThreadPool.MODE_NONE); + jc.setCancelListener(mNotifier); + while (!jc.isCancelled()) { + Tile tile = null; + synchronized (TileImageView.this) { + tile = mDecodeQueue.pop(); + if (tile == null && !jc.isCancelled()) { + GalleryUtils.waitWithoutInterrupt(TileImageView.this); + } + } + if (tile == null) { + continue; + } + if (decodeTile(tile)) { + queueForUpload(tile); + } + } + return null; + } + } + + private static class TileQueue { + private Tile mHead; + + public void clean() { + mHead = null; + } + + public Tile pop() { + final Tile tile = mHead; + if (tile != null) { + mHead = tile.mNext; + } + return tile; + } + + public boolean push(final Tile tile) { + final boolean wasEmpty = mHead == null; + tile.mNext = mHead; + mHead = tile; + return wasEmpty; + } + } + + private class TileUploader implements GLRoot.OnGLIdleListener { + AtomicBoolean mActive = new AtomicBoolean(false); + + @Override + public boolean onGLIdle(final GLCanvas canvas, final boolean renderRequested) { + // Skips uploading if there is a pending rendering request. + // Returns true to keep uploading in next rendering loop. + if (renderRequested) return true; + int quota = UPLOAD_LIMIT; + Tile tile = null; + while (quota > 0) { + synchronized (TileImageView.this) { + tile = mUploadQueue.pop(); + } + if (tile == null) { + break; + } + if (!tile.isContentValid()) { + final boolean hasBeenLoaded = tile.isLoaded(); + if (tile.mTileState != STATE_DECODED) return false; + tile.updateContent(canvas); + if (!hasBeenLoaded) { + tile.draw(canvas, 0, 0); + } + --quota; + } + } + if (tile == null) { + mActive.set(false); + } + return tile != null; + } + } } diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java b/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java index ec4c3dbf7..eba45eccd 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java +++ b/twidere/src/main/java/org/mariotaku/gallery3d/util/GalleryUtils.java @@ -110,30 +110,7 @@ public class GalleryUtils { return color >>> 24 == 0xFF; } - // Returns the next power of two. - // Returns the input if it is already power of 2. - // Throws IllegalArgumentException if the input is <= 0 or - // the answer overflows. - public static int nextPowerOf2(int n) { - if (n <= 0 || n > 1 << 30) throw new IllegalArgumentException("n is invalid: " + n); - n -= 1; - n |= n >> 16; - n |= n >> 8; - n |= n >> 4; - n |= n >> 2; - n |= n >> 1; - return n + 1; - } - - // Returns the previous power of two. - // Returns the input if it is already power of 2. - // Throws IllegalArgumentException if the input is <= 0 - public static int prevPowerOf2(final int n) { - if (n <= 0) throw new IllegalArgumentException(); - return Integer.highestOneBit(n); - } - - public static void waitWithoutInterrupt(final Object object) { + public static void waitWithoutInterrupt(final Object object) { try { object.wait(); } catch (final InterruptedException e) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/SettingsActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/SettingsActivity.java index f6ad8e302..15d4b9eae 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/SettingsActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/SettingsActivity.java @@ -53,7 +53,7 @@ import org.mariotaku.twidere.activity.support.DataImportActivity; import org.mariotaku.twidere.graphic.EmptyDrawable; import org.mariotaku.twidere.util.CompareUtils; import org.mariotaku.twidere.util.ThemeUtils; -import org.mariotaku.twidere.view.holder.ListViewHolder; +import org.mariotaku.twidere.view.holder.ViewListHolder; import java.util.ArrayList; import java.util.List; @@ -427,7 +427,7 @@ public class SettingsActivity extends BasePreferenceActivity { } - private static class HeaderViewHolder extends ListViewHolder { + private static class HeaderViewHolder extends ViewListHolder { private final TextView title, summary; private final ImageView icon; private final LinearLayout content; diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportActivity.java index 3b8f39c1c..0d7bb312b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportActivity.java @@ -38,7 +38,7 @@ import org.mariotaku.twidere.view.MainFrameLayout.FitSystemWindowsCallback; import java.util.ArrayList; @SuppressLint("Registered") -public class BaseSupportActivity extends BaseSupportThemedActivity implements Constants, +public class BaseSupportActivity extends ThemedFragmentActivity implements Constants, FitSystemWindowsCallback, SystemWindowsInsetsCallback, IControlBarActivity { private boolean mInstanceStateSaved, mIsVisible, mIsOnTop; diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportDialogActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportDialogActivity.java index c22d7de4a..c922e0950 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportDialogActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportDialogActivity.java @@ -28,7 +28,7 @@ import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.util.ThemeUtils; @SuppressLint("Registered") -public class BaseSupportDialogActivity extends BaseSupportThemedActivity implements Constants, IThemedActivity { +public class BaseSupportDialogActivity extends ThemedFragmentActivity implements Constants, IThemedActivity { private boolean mInstanceStateSaved; diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/GlobalSearchBoxActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/GlobalSearchBoxActivity.java new file mode 100644 index 000000000..5d306b69d --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/GlobalSearchBoxActivity.java @@ -0,0 +1,85 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.twidere.activity.support; + +import android.content.Intent; +import android.os.Bundle; +import android.view.Gravity; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Spinner; + +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.adapter.AccountsSpinnerAdapter; +import org.mariotaku.twidere.model.ParcelableAccount; +import org.mariotaku.twidere.util.ThemeUtils; + +import java.util.List; + +/** + * Created by mariotaku on 15/1/6. + */ +public class GlobalSearchBoxActivity extends BaseSupportActivity { + + private Spinner mAccountSpinner; + + @Override + public int getThemeResourceId() { + return ThemeUtils.getGlobalSearchThemeResource(this); + } + + @Override + public void onContentChanged() { + super.onContentChanged(); + mAccountSpinner = (Spinner) findViewById(R.id.account_spinner); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_global_search_box); + final List accounts = ParcelableAccount.getAccountsList(this, false); + final AccountsSpinnerAdapter accountsSpinnerAdapter = new AccountsSpinnerAdapter(this, R.layout.spinner_item_account_icon); + accountsSpinnerAdapter.setDropDownViewResource(R.layout.list_item_user); + accountsSpinnerAdapter.addAll(accounts); + mAccountSpinner.setAdapter(accountsSpinnerAdapter); + if (savedInstanceState == null) { + final Intent intent = getIntent(); + final int index = accountsSpinnerAdapter.findItemPosition(intent.getLongExtra(EXTRA_ACCOUNT_ID, -1)); + if (index != -1) { + mAccountSpinner.setSelection(index); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + updateWindowAttributes(); + } + + private void updateWindowAttributes() { + final Window window = getWindow(); + final WindowManager.LayoutParams attributes = window.getAttributes(); + attributes.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + window.setAttributes(attributes); + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ImagePickerActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ImagePickerActivity.java index 7d844edae..ec95af140 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ImagePickerActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ImagePickerActivity.java @@ -35,7 +35,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -public class ImagePickerActivity extends BaseSupportThemedActivity { +public class ImagePickerActivity extends ThemedFragmentActivity { public static final int REQUEST_PICK_IMAGE = 101; public static final int REQUEST_TAKE_PHOTO = 102; diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java new file mode 100644 index 000000000..51dd740d6 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/MediaViewerActivity.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.twidere.activity.support; + +import android.app.ActionBar; +import android.app.ActionBar.OnMenuVisibilityListener; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.View.OnLayoutChangeListener; +import android.widget.ImageView; +import android.widget.ProgressBar; + +import org.mariotaku.menucomponent.widget.MenuBar; +import org.mariotaku.menucomponent.widget.MenuBar.MenuBarListener; +import org.mariotaku.tileimageview.widget.TileImageView; +import org.mariotaku.twidere.Constants; +import org.mariotaku.twidere.R; +import org.mariotaku.twidere.loader.support.TileImageLoader; +import org.mariotaku.twidere.util.SaveImageTask; +import org.mariotaku.twidere.util.ThemeUtils; +import org.mariotaku.twidere.util.Utils; + +import java.io.File; + +public final class MediaViewerActivity extends BaseSupportActivity implements Constants, + TileImageLoader.DownloadListener, LoaderManager.LoaderCallbacks, + OnMenuVisibilityListener, MenuBarListener { + + + private ActionBar mActionBar; + + private ProgressBar mProgress; + private ImageView mImageView; + private MenuBar mMenuBar; + + private long mContentLength; + + private File mImageFile; + private boolean mLoaderInitialized; + + @Override + public int getThemeResourceId() { + return ThemeUtils.getViewerThemeResource(this); + } + + + @Override + public void onContentChanged() { + super.onContentChanged(); + mImageView = (ImageView) findViewById(R.id.image_viewer); + mProgress = (ProgressBar) findViewById(R.id.progress); + mMenuBar = (MenuBar) findViewById(R.id.menu_bar); + } + + @Override + public Loader onCreateLoader(final int id, final Bundle args) { + mProgress.setVisibility(View.VISIBLE); + mProgress.setIndeterminate(true); + invalidateOptionsMenu(); + final Uri uri = args.getParcelable(EXTRA_URI); + final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1); + return new TileImageLoader(this, this, accountId, uri); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.menu_image_viewer_action_bar, menu); + return true; + } + + + @Override + public void onDownloadError(final Throwable t) { + mContentLength = 0; + } + + @Override + public void onDownloadFinished() { + mContentLength = 0; + } + + @Override + public void onDownloadStart(final long total) { + mContentLength = total; + mProgress.setIndeterminate(total <= 0); + mProgress.setMax(total > 0 ? (int) (total / 1024) : 0); + } + + + @Override + public void onLoaderReset(final Loader loader) { + + } + + @Override + public void onLoadFinished(final Loader loader, final TileImageLoader.Result data) { + if (data.hasData()) { + mImageView.setVisibility(View.VISIBLE); +// mImageView.setBitmapRegionDecoder(data.decoder, data.bitmap); +// mImageView.setScale(1); + mImageView.setImageBitmap(data.bitmap); + mImageFile = data.file; + } else { + mImageView.setVisibility(View.GONE); + mImageFile = null; + Utils.showErrorMessage(this, null, data.exception, true); + } + mProgress.setVisibility(View.GONE); + mProgress.setProgress(0); + invalidateOptionsMenu(); + updateShareIntent(); + } + + @Override + public boolean onMenuItemClick(final MenuItem item) { + switch (item.getItemId()) { + case MENU_SAVE: { + if (mImageFile != null) { + new SaveImageTask(this, mImageFile).execute(); + } + break; + } + case MENU_OPEN_IN_BROWSER: { + final Intent intent = getIntent(); + intent.setExtrasClassLoader(getClassLoader()); + final Uri uri = intent.getData(); + final Uri orig = intent.getParcelableExtra(EXTRA_URI_ORIG); + final Uri uriPreferred = orig != null ? orig : uri; + if (uriPreferred == null) return false; + final String scheme = uriPreferred.getScheme(); + if ("http".equals(scheme) || "https".equals(scheme)) { + final Intent open_intent = new Intent(Intent.ACTION_VIEW, uriPreferred); + open_intent.addCategory(Intent.CATEGORY_BROWSABLE); + try { + startActivity(open_intent); + } catch (final ActivityNotFoundException e) { + // Ignore. + } + } + break; + } + default: { + final Intent intent = item.getIntent(); + if (intent != null) { + try { + startActivity(intent); + } catch (final ActivityNotFoundException e) { + // Ignore. + } + return true; + } + return false; + } + } + return true; + } + + @Override + public void onMenuVisibilityChanged(final boolean isVisible) { + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case MENU_HOME: { + onBackPressed(); + break; + } + case MENU_REFRESH: { + loadImage(); + break; + } + } + return true; + } + + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + final LoaderManager lm = getSupportLoaderManager(); + Utils.setMenuItemAvailability(menu, MENU_REFRESH, !lm.hasRunningLoaders()); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public void onProgressUpdate(final long downloaded) { + if (mContentLength == 0) { + mProgress.setIndeterminate(true); + return; + } + mProgress.setIndeterminate(false); + mProgress.setProgress((int) (downloaded / 1024)); + } + + + public void showProgress() { + mProgress.setVisibility(View.VISIBLE); + mProgress.setIndeterminate(true); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.image_viewer); + mActionBar = getActionBar(); + mActionBar.setDisplayHomeAsUpEnabled(true); + mActionBar.addOnMenuVisibilityListener(this); + if (savedInstanceState == null) { + loadImage(); + } + +// mImageView.setScaleToFit(false); + mImageView.addOnLayoutChangeListener(new TileImageViewLayoutListener()); + + mMenuBar.setMenuBarListener(this); + mMenuBar.inflate(R.menu.menu_image_viewer); + mMenuBar.setIsBottomBar(true); + mMenuBar.show(); + } + + + private static class TileImageViewLayoutListener implements OnLayoutChangeListener { + @Override + public void onLayoutChange(final View v, final int left, final int top, final int right, final int bottom, + final int oldLeft, final int oldTop, final int oldRight, final int oldBottom) { + if (!(v instanceof TileImageView)) return; + final TileImageView tileView = (TileImageView) v; + final int baseWidth = tileView.getBaseWidth(), baseHeight = tileView.getBaseHeight(); + final double scaleMin = getMinScale(left, top, right, bottom, baseWidth, baseHeight); + tileView.setScaleLimits(scaleMin, Math.max(scaleMin, 2.0)); + final double oldScaleMin = getMinScale(oldLeft, oldTop, oldRight, oldBottom, baseWidth, baseHeight); + final double oldScale = tileView.getScale(); + tileView.setScaleLimits(scaleMin, Math.max(scaleMin, 2.0)); + if (oldScale == oldScaleMin) { + tileView.setScale(scaleMin); + } + } + + private static double getMinScale(final int left, final int top, final int right, final int bottom, + final int baseWidth, final int baseHeight) { + final double viewWidth = right - left, viewHeight = bottom - top; + if (viewWidth <= 0 || viewHeight <= 0) return 0; + final double widthScale = Math.min(1, baseWidth / viewWidth), heightScale = Math.min(1, baseHeight + / viewHeight); + return Math.min(widthScale, heightScale); + } + + } + + @Override + protected void onDestroy() { + mActionBar.removeOnMenuVisibilityListener(this); + super.onDestroy(); + + } + + @Override + protected void onNewIntent(final Intent intent) { + setIntent(intent); + loadImage(); + } + + @Override + protected void onPause() { + super.onPause(); + + } + + @Override + protected void onResume() { + super.onResume(); + + } + + + private void loadImage() { + getSupportLoaderManager().destroyLoader(0); + final Intent intent = getIntent(); + final Uri uri = intent.getData(); + final long accountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1); + if (uri == null) { + finish(); + return; + } + final Bundle args = new Bundle(); + args.putParcelable(EXTRA_URI, uri); + args.putLong(EXTRA_ACCOUNT_ID, accountId); + if (!mLoaderInitialized) { + getSupportLoaderManager().initLoader(0, args, this); + mLoaderInitialized = true; + } else { + getSupportLoaderManager().restartLoader(0, args, this); + } + } + + + void updateShareIntent() { + final MenuItem item = mMenuBar.getMenu().findItem(MENU_SHARE); + if (item == null || !item.hasSubMenu()) return; + final SubMenu subMenu = item.getSubMenu(); + subMenu.clear(); + final Intent intent = getIntent(); + final Uri uri = intent.getData(); + final Intent shareIntent = new Intent(Intent.ACTION_SEND); + if (mImageFile != null && mImageFile.exists()) { + shareIntent.setType("image/*"); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mImageFile)); + } else { + shareIntent.setType("text/plain"); + shareIntent.putExtra(Intent.EXTRA_TEXT, uri.toString()); + } + Utils.addIntentToMenu(this, subMenu, shareIntent); + } + + @Override + public void onPreShowMenu(Menu menu) { + + } + + @Override + public void onPostShowMenu(Menu menu) { + + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedFragmentActivity.java similarity index 89% rename from twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java rename to twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedFragmentActivity.java index ddd3bf312..5248d267c 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/BaseSupportThemedActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ThemedFragmentActivity.java @@ -36,12 +36,16 @@ import org.mariotaku.twidere.activity.iface.IThemedActivity; import org.mariotaku.twidere.util.StrictModeUtils; import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.Utils; +import org.mariotaku.twidere.view.ShapedImageView; +import org.mariotaku.twidere.view.ShapedImageView.ShapeStyle; import static org.mariotaku.twidere.util.Utils.restartActivity; -public abstract class BaseSupportThemedActivity extends FragmentActivity implements Constants, IThemedActivity { +public abstract class ThemedFragmentActivity extends FragmentActivity implements Constants, IThemedActivity { private int mCurrentThemeResource, mCurrentThemeColor, mCurrentThemeBackgroundAlpha; + @ShapeStyle + private int mProfileImageStyle; @Override public Resources getDefaultResources() { @@ -112,6 +116,10 @@ public abstract class BaseSupportThemedActivity extends FragmentActivity impleme @Override public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) { final View view = ThemeUtils.createView(name, context, attrs, mCurrentThemeColor); + if (view instanceof ShapedImageView) { + final ShapedImageView shapedImageView = (ShapedImageView) view; + shapedImageView.setStyle(mProfileImageStyle); + } if (view != null) return view; return super.onCreateView(name, context, attrs); } @@ -129,6 +137,7 @@ public abstract class BaseSupportThemedActivity extends FragmentActivity impleme mCurrentThemeResource = getThemeResourceId(); mCurrentThemeColor = getThemeColor(); mCurrentThemeBackgroundAlpha = getThemeBackgroundAlpha(); + mProfileImageStyle = Utils.getProfileImageStyle(this); ThemeUtils.notifyStatusBarColorChanged(this, mCurrentThemeResource, mCurrentThemeColor, mCurrentThemeBackgroundAlpha); setTheme(mCurrentThemeResource); diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java index 037e094f1..dd127571d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/UserListSelectorActivity.java @@ -19,11 +19,6 @@ package org.mariotaku.twidere.activity.support; -import static android.text.TextUtils.isEmpty; -import static org.mariotaku.twidere.util.ParseUtils.parseString; -import static org.mariotaku.twidere.util.Utils.getAccountScreenName; -import static org.mariotaku.twidere.util.Utils.getTwitterInstance; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -49,6 +44,9 @@ import org.mariotaku.twidere.model.ParcelableUserList; import org.mariotaku.twidere.model.SingleResponse; import org.mariotaku.twidere.task.TwidereAsyncTask; +import java.util.ArrayList; +import java.util.List; + import twitter4j.ResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; @@ -56,284 +54,286 @@ import twitter4j.User; import twitter4j.UserList; import twitter4j.http.HttpResponseCode; -import java.util.ArrayList; -import java.util.List; +import static android.text.TextUtils.isEmpty; +import static org.mariotaku.twidere.util.ParseUtils.parseString; +import static org.mariotaku.twidere.util.Utils.getAccountScreenName; +import static org.mariotaku.twidere.util.Utils.getTwitterInstance; public class UserListSelectorActivity extends BaseSupportDialogActivity implements OnClickListener, OnItemClickListener { - private AutoCompleteTextView mEditScreenName; - private ListView mUserListsListView, mUsersListView; - private SimpleParcelableUserListsAdapter mUserListsAdapter; - private SimpleParcelableUsersAdapter mUsersAdapter; - private View mUsersListContainer, mUserListsContainer, mCreateUserListContainer; + private AutoCompleteTextView mEditScreenName; + private ListView mUserListsListView, mUsersListView; + private SimpleParcelableUserListsAdapter mUserListsAdapter; + private SimpleParcelableUsersAdapter mUsersAdapter; + private View mUsersListContainer, mUserListsContainer, mCreateUserListContainer; - private String mScreenName; + private String mScreenName; - private final BroadcastReceiver mStatusReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mStatusReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - final String action = intent.getAction(); - if (BROADCAST_USER_LIST_CREATED.equals(action)) { - getUserLists(mScreenName); - } - } - }; + @Override + public void onReceive(final Context context, final Intent intent) { + final String action = intent.getAction(); + if (BROADCAST_USER_LIST_CREATED.equals(action)) { + getUserLists(mScreenName); + } + } + }; - @Override - public void onClick(final View v) { - switch (v.getId()) { - case R.id.screen_name_confirm: { - final String screen_name = parseString(mEditScreenName.getText()); - if (isEmpty(screen_name)) return; - searchUser(screen_name); - break; - } - case R.id.create_list: { - final DialogFragment f = new CreateUserListDialogFragment(); - final Bundle args = new Bundle(); - args.putLong(EXTRA_ACCOUNT_ID, getAccountId()); - f.setArguments(args); - f.show(getSupportFragmentManager(), null); - break; - } - } - } + @Override + public void onClick(final View v) { + switch (v.getId()) { + case R.id.screen_name_confirm: { + final String screen_name = parseString(mEditScreenName.getText()); + if (isEmpty(screen_name)) return; + searchUser(screen_name); + break; + } + case R.id.create_list: { + final DialogFragment f = new CreateUserListDialogFragment(); + final Bundle args = new Bundle(); + args.putLong(EXTRA_ACCOUNT_ID, getAccountId()); + f.setArguments(args); + f.show(getSupportFragmentManager(), null); + break; + } + } + } - @Override - public void onContentChanged() { - super.onContentChanged(); - mUsersListContainer = findViewById(R.id.users_list_container); - mUserListsContainer = findViewById(R.id.user_lists_container); - mEditScreenName = (AutoCompleteTextView) findViewById(R.id.edit_screen_name); - mUserListsListView = (ListView) findViewById(R.id.user_lists_list); - mUsersListView = (ListView) findViewById(R.id.users_list); - mCreateUserListContainer = findViewById(R.id.create_list_container); - } + @Override + public void onContentChanged() { + super.onContentChanged(); + mUsersListContainer = findViewById(R.id.users_list_container); + mUserListsContainer = findViewById(R.id.user_lists_container); + mEditScreenName = (AutoCompleteTextView) findViewById(R.id.edit_screen_name); + mUserListsListView = (ListView) findViewById(R.id.user_lists_list); + mUsersListView = (ListView) findViewById(R.id.users_list); + mCreateUserListContainer = findViewById(R.id.create_list_container); + } - @Override - public void onItemClick(final AdapterView view, final View child, final int position, final long id) { - final int view_id = view.getId(); - final ListView list = (ListView) view; - if (view_id == R.id.users_list) { - final ParcelableUser user = mUsersAdapter.getItem(position - list.getHeaderViewsCount()); - if (user == null) return; - if (isSelectingUser()) { - final Intent data = new Intent(); - data.setExtrasClassLoader(getClassLoader()); - data.putExtra(EXTRA_USER, user); - setResult(RESULT_OK, data); - finish(); - } else { - getUserLists(user.screen_name); - } - } else if (view_id == R.id.user_lists_list) { - final Intent data = new Intent(); - data.putExtra(EXTRA_USER_LIST, mUserListsAdapter.getItem(position - list.getHeaderViewsCount())); - setResult(RESULT_OK, data); - finish(); - } - } + @Override + public void onItemClick(final AdapterView view, final View child, final int position, final long id) { + final int view_id = view.getId(); + final ListView list = (ListView) view; + if (view_id == R.id.users_list) { + final ParcelableUser user = mUsersAdapter.getItem(position - list.getHeaderViewsCount()); + if (user == null) return; + if (isSelectingUser()) { + final Intent data = new Intent(); + data.setExtrasClassLoader(getClassLoader()); + data.putExtra(EXTRA_USER, user); + setResult(RESULT_OK, data); + finish(); + } else { + getUserLists(user.screen_name); + } + } else if (view_id == R.id.user_lists_list) { + final Intent data = new Intent(); + data.putExtra(EXTRA_USER_LIST, mUserListsAdapter.getItem(position - list.getHeaderViewsCount())); + setResult(RESULT_OK, data); + finish(); + } + } - public void setUsersData(final List data) { - mUsersAdapter.setData(data, true); - mUsersListContainer.setVisibility(View.VISIBLE); - mUserListsContainer.setVisibility(View.GONE); - } + public void setUsersData(final List data) { + mUsersAdapter.setData(data, true); + mUsersListContainer.setVisibility(View.VISIBLE); + mUserListsContainer.setVisibility(View.GONE); + } - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Intent intent = getIntent(); - if (!intent.hasExtra(EXTRA_ACCOUNT_ID)) { - finish(); - return; - } - setContentView(R.layout.activity_user_list_selector); - if (savedInstanceState == null) { - mScreenName = intent.getStringExtra(EXTRA_SCREEN_NAME); - } else { - mScreenName = savedInstanceState.getString(EXTRA_SCREEN_NAME); - } + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Intent intent = getIntent(); + if (!intent.hasExtra(EXTRA_ACCOUNT_ID)) { + finish(); + return; + } + setContentView(R.layout.activity_user_list_selector); + if (savedInstanceState == null) { + mScreenName = intent.getStringExtra(EXTRA_SCREEN_NAME); + } else { + mScreenName = savedInstanceState.getString(EXTRA_SCREEN_NAME); + } - final boolean selecting_user = isSelectingUser(); - setTitle(selecting_user ? R.string.select_user : R.string.select_user_list); - if (!isEmpty(mScreenName)) { - if (selecting_user) { - searchUser(mScreenName); - } else { - getUserLists(mScreenName); - } - } - mEditScreenName.setAdapter(new UserHashtagAutoCompleteAdapter(this)); - mEditScreenName.setText(mScreenName); - mUserListsListView.setAdapter(mUserListsAdapter = new SimpleParcelableUserListsAdapter(this)); - mUsersListView.setAdapter(mUsersAdapter = new SimpleParcelableUsersAdapter(this)); - mUserListsListView.setOnItemClickListener(this); - mUsersListView.setOnItemClickListener(this); - if (selecting_user) { - mUsersListContainer.setVisibility(View.VISIBLE); - mUserListsContainer.setVisibility(View.GONE); - } else { - mUsersListContainer.setVisibility(isEmpty(mScreenName) ? View.VISIBLE : View.GONE); - mUserListsContainer.setVisibility(isEmpty(mScreenName) ? View.GONE : View.VISIBLE); - } - } + final boolean selecting_user = isSelectingUser(); + setTitle(selecting_user ? R.string.select_user : R.string.select_user_list); + if (!isEmpty(mScreenName)) { + if (selecting_user) { + searchUser(mScreenName); + } else { + getUserLists(mScreenName); + } + } + mEditScreenName.setAdapter(new UserHashtagAutoCompleteAdapter(this)); + mEditScreenName.setText(mScreenName); + mUserListsListView.setAdapter(mUserListsAdapter = new SimpleParcelableUserListsAdapter(this)); + mUsersListView.setAdapter(mUsersAdapter = new SimpleParcelableUsersAdapter(this)); + mUserListsListView.setOnItemClickListener(this); + mUsersListView.setOnItemClickListener(this); + if (selecting_user) { + mUsersListContainer.setVisibility(View.VISIBLE); + mUserListsContainer.setVisibility(View.GONE); + } else { + mUsersListContainer.setVisibility(isEmpty(mScreenName) ? View.VISIBLE : View.GONE); + mUserListsContainer.setVisibility(isEmpty(mScreenName) ? View.GONE : View.VISIBLE); + } + } - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(EXTRA_SCREEN_NAME, mScreenName); - } + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(EXTRA_SCREEN_NAME, mScreenName); + } - @Override - protected void onStart() { - super.onStart(); - final IntentFilter filter = new IntentFilter(BROADCAST_USER_LIST_CREATED); - registerReceiver(mStatusReceiver, filter); - } + @Override + protected void onStart() { + super.onStart(); + final IntentFilter filter = new IntentFilter(BROADCAST_USER_LIST_CREATED); + registerReceiver(mStatusReceiver, filter); + } - @Override - protected void onStop() { - unregisterReceiver(mStatusReceiver); - super.onStop(); - } + @Override + protected void onStop() { + unregisterReceiver(mStatusReceiver); + super.onStop(); + } - private long getAccountId() { - return getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1); - } + private long getAccountId() { + return getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1); + } - private void getUserLists(final String screen_name) { - if (screen_name == null) return; - mScreenName = screen_name; - final GetUserListsTask task = new GetUserListsTask(this, getAccountId(), screen_name); - task.executeTask(); - } + private void getUserLists(final String screen_name) { + if (screen_name == null) return; + mScreenName = screen_name; + final GetUserListsTask task = new GetUserListsTask(this, getAccountId(), screen_name); + task.executeTask(); + } - private boolean isSelectingUser() { - return INTENT_ACTION_SELECT_USER.equals(getIntent().getAction()); - } + private boolean isSelectingUser() { + return INTENT_ACTION_SELECT_USER.equals(getIntent().getAction()); + } - private void searchUser(final String name) { - final SearchUsersTask task = new SearchUsersTask(this, getAccountId(), name); - task.executeTask(); - } + private void searchUser(final String name) { + final SearchUsersTask task = new SearchUsersTask(this, getAccountId(), name); + task.executeTask(); + } - private void setUserListsData(final List data, final boolean is_my_account) { - mUserListsAdapter.setData(data, true); - mUsersListContainer.setVisibility(View.GONE); - mUserListsContainer.setVisibility(View.VISIBLE); - mCreateUserListContainer.setVisibility(is_my_account ? View.VISIBLE : View.GONE); - } + private void setUserListsData(final List data, final boolean is_my_account) { + mUserListsAdapter.setData(data, true); + mUsersListContainer.setVisibility(View.GONE); + mUserListsContainer.setVisibility(View.VISIBLE); + mCreateUserListContainer.setVisibility(is_my_account ? View.VISIBLE : View.GONE); + } - private static class GetUserListsTask extends TwidereAsyncTask>> { + private static class GetUserListsTask extends TwidereAsyncTask>> { - private static final String FRAGMENT_TAG_GET_USER_LISTS = "get_user_lists"; - private final UserListSelectorActivity mActivity; - private final long mAccountId; - private final String mScreenName; + private static final String FRAGMENT_TAG_GET_USER_LISTS = "get_user_lists"; + private final UserListSelectorActivity mActivity; + private final long mAccountId; + private final String mScreenName; - GetUserListsTask(final UserListSelectorActivity activity, final long account_id, final String screen_name) { - mActivity = activity; - mAccountId = account_id; - mScreenName = screen_name; - } + GetUserListsTask(final UserListSelectorActivity activity, final long account_id, final String screen_name) { + mActivity = activity; + mAccountId = account_id; + mScreenName = screen_name; + } - @Override - protected SingleResponse> doInBackground(final Void... params) { - final Twitter twitter = getTwitterInstance(mActivity, mAccountId, false); - if (twitter == null) return SingleResponse.getInstance(); - try { - final ResponseList lists = twitter.getUserLists(mScreenName); - final List data = new ArrayList(); - boolean is_my_account = mScreenName.equalsIgnoreCase(getAccountScreenName(mActivity, mAccountId)); - for (final UserList item : lists) { - final User user = item.getUser(); - if (user != null && mScreenName.equalsIgnoreCase(user.getScreenName())) { - if (!is_my_account && user.getId() == mAccountId) { - is_my_account = true; - } - data.add(new ParcelableUserList(item, mAccountId)); - } - } - final SingleResponse> result = SingleResponse.getInstance(data); - result.getExtras().putBoolean(EXTRA_IS_MY_ACCOUNT, is_my_account); - return result; - } catch (final TwitterException e) { - e.printStackTrace(); - return SingleResponse.getInstance(e); - } - } + @Override + protected SingleResponse> doInBackground(final Void... params) { + final Twitter twitter = getTwitterInstance(mActivity, mAccountId, false); + if (twitter == null) return SingleResponse.getInstance(); + try { + final ResponseList lists = twitter.getUserLists(mScreenName, true); + final List data = new ArrayList(); + boolean is_my_account = mScreenName.equalsIgnoreCase(getAccountScreenName(mActivity, mAccountId)); + for (final UserList item : lists) { + final User user = item.getUser(); + if (user != null && mScreenName.equalsIgnoreCase(user.getScreenName())) { + if (!is_my_account && user.getId() == mAccountId) { + is_my_account = true; + } + data.add(new ParcelableUserList(item, mAccountId)); + } + } + final SingleResponse> result = SingleResponse.getInstance(data); + result.getExtras().putBoolean(EXTRA_IS_MY_ACCOUNT, is_my_account); + return result; + } catch (final TwitterException e) { + e.printStackTrace(); + return SingleResponse.getInstance(e); + } + } - @Override - protected void onPostExecute(final SingleResponse> result) { - final Fragment f = mActivity.getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_GET_USER_LISTS); - if (f instanceof DialogFragment) { - ((DialogFragment) f).dismiss(); - } - if (result.getData() != null) { - mActivity.setUserListsData(result.getData(), result.getExtras().getBoolean(EXTRA_IS_MY_ACCOUNT)); - } else if (result.getException() instanceof TwitterException) { - final TwitterException te = (TwitterException) result.getException(); - if (te.getStatusCode() == HttpResponseCode.NOT_FOUND) { - mActivity.searchUser(mScreenName); - } - } - } + @Override + protected void onPostExecute(final SingleResponse> result) { + final Fragment f = mActivity.getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_GET_USER_LISTS); + if (f instanceof DialogFragment) { + ((DialogFragment) f).dismiss(); + } + if (result.getData() != null) { + mActivity.setUserListsData(result.getData(), result.getExtras().getBoolean(EXTRA_IS_MY_ACCOUNT)); + } else if (result.getException() instanceof TwitterException) { + final TwitterException te = (TwitterException) result.getException(); + if (te.getStatusCode() == HttpResponseCode.NOT_FOUND) { + mActivity.searchUser(mScreenName); + } + } + } - @Override - protected void onPreExecute() { - SupportProgressDialogFragment.show(mActivity, FRAGMENT_TAG_GET_USER_LISTS).setCancelable(false); - } + @Override + protected void onPreExecute() { + SupportProgressDialogFragment.show(mActivity, FRAGMENT_TAG_GET_USER_LISTS).setCancelable(false); + } - } + } - private static class SearchUsersTask extends TwidereAsyncTask>> { + private static class SearchUsersTask extends TwidereAsyncTask>> { - private static final String FRAGMENT_TAG_SEARCH_USERS = "search_users"; - private final UserListSelectorActivity mActivity; - private final long mAccountId; - private final String mName; + private static final String FRAGMENT_TAG_SEARCH_USERS = "search_users"; + private final UserListSelectorActivity mActivity; + private final long mAccountId; + private final String mName; - SearchUsersTask(final UserListSelectorActivity activity, final long account_id, final String name) { - mActivity = activity; - mAccountId = account_id; - mName = name; - } + SearchUsersTask(final UserListSelectorActivity activity, final long account_id, final String name) { + mActivity = activity; + mAccountId = account_id; + mName = name; + } - @Override - protected SingleResponse> doInBackground(final Void... params) { - final Twitter twitter = getTwitterInstance(mActivity, mAccountId, false); - if (twitter == null) return SingleResponse.getInstance(); - try { - final ResponseList lists = twitter.searchUsers(mName, 1); - final List data = new ArrayList(); - for (final User item : lists) { - data.add(new ParcelableUser(item, mAccountId)); - } - return SingleResponse.getInstance(data); - } catch (final TwitterException e) { - e.printStackTrace(); - return SingleResponse.getInstance(e); - } - } + @Override + protected SingleResponse> doInBackground(final Void... params) { + final Twitter twitter = getTwitterInstance(mActivity, mAccountId, false); + if (twitter == null) return SingleResponse.getInstance(); + try { + final ResponseList lists = twitter.searchUsers(mName, 1); + final List data = new ArrayList<>(); + for (final User item : lists) { + data.add(new ParcelableUser(item, mAccountId)); + } + return SingleResponse.getInstance(data); + } catch (final TwitterException e) { + e.printStackTrace(); + return SingleResponse.getInstance(e); + } + } - @Override - protected void onPostExecute(final SingleResponse> result) { - final Fragment f = mActivity.getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_SEARCH_USERS); - if (f instanceof DialogFragment) { - ((DialogFragment) f).dismiss(); - } - if (result.getData() != null) { - mActivity.setUsersData(result.getData()); - } - } + @Override + protected void onPostExecute(final SingleResponse> result) { + final Fragment f = mActivity.getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG_SEARCH_USERS); + if (f instanceof DialogFragment) { + ((DialogFragment) f).dismiss(); + } + if (result.getData() != null) { + mActivity.setUsersData(result.getData()); + } + } - @Override - protected void onPreExecute() { - SupportProgressDialogFragment.show(mActivity, FRAGMENT_TAG_SEARCH_USERS).setCancelable(false); - } + @Override + protected void onPreExecute() { + SupportProgressDialogFragment.show(mActivity, FRAGMENT_TAG_SEARCH_USERS).setCancelable(false); + } - } + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java index 2c7631945..b3a0c7cf8 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsActivitiesAdapter.java @@ -20,11 +20,14 @@ package org.mariotaku.twidere.adapter; import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.util.Pair; +import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView.Adapter; import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.TextView; @@ -32,6 +35,7 @@ import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.adapter.iface.IActivitiesAdapter; import org.mariotaku.twidere.app.TwidereApplication; +import org.mariotaku.twidere.fragment.support.UserFragment; import org.mariotaku.twidere.model.ParcelableActivity; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.util.AsyncTwitterWrapper; @@ -44,12 +48,13 @@ import org.mariotaku.twidere.view.holder.ActivityTitleSummaryViewHolder; import org.mariotaku.twidere.view.holder.GapViewHolder; import org.mariotaku.twidere.view.holder.LoadIndicatorViewHolder; import org.mariotaku.twidere.view.holder.StatusViewHolder; +import org.mariotaku.twidere.view.holder.StatusViewHolder.StatusClickListener; /** * Created by mariotaku on 15/1/3. */ public abstract class AbsActivitiesAdapter extends Adapter implements Constants, - IActivitiesAdapter, OnClickListener { + IActivitiesAdapter, StatusClickListener { private static final int ITEM_VIEW_TYPE_STUB = 0; private static final int ITEM_VIEW_TYPE_GAP = 1; @@ -65,9 +70,10 @@ public abstract class AbsActivitiesAdapter extends Adapter imp private final int mCardBackgroundColor; private final int mTextSize; private final int mProfileImageStyle, mMediaPreviewStyle; + private final boolean mCompactCards; private boolean mLoadMoreIndicatorEnabled; - protected AbsActivitiesAdapter(final Context context) { + protected AbsActivitiesAdapter(final Context context, boolean compact) { mContext = context; final TwidereApplication app = TwidereApplication.getInstance(context); mCardBackgroundColor = ThemeUtils.getCardBackgroundColor(context); @@ -78,6 +84,7 @@ public abstract class AbsActivitiesAdapter extends Adapter imp final SharedPreferencesWrapper preferences = SharedPreferencesWrapper.getInstance(context, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mTextSize = preferences.getInt(KEY_TEXT_SIZE, context.getResources().getInteger(R.integer.default_text_size)); + mCompactCards = compact; mProfileImageStyle = Utils.getProfileImageStyle(preferences.getString(KEY_PROFILE_IMAGE_STYLE, null)); mMediaPreviewStyle = Utils.getMediaPreviewStyle(preferences.getString(KEY_MEDIA_PREVIEW_STYLE, null)); } @@ -86,19 +93,39 @@ public abstract class AbsActivitiesAdapter extends Adapter imp public abstract int getActivityCount(); - @Override - public void onClick(View v) { - - } @Override public void onStatusClick(StatusViewHolder holder, int position) { - + final ParcelableActivity activity = getActivity(position); + final ParcelableStatus status; + if (activity.action == ParcelableActivity.ACTION_MENTION) { + status = activity.target_object_statuses[0]; + } else { + status = activity.target_statuses[0]; + } + Utils.openStatus(getContext(), status, null); } @Override public void onUserProfileClick(StatusViewHolder holder, int position) { - + final Context context = getContext(); + final ParcelableActivity activity = getActivity(position); + final ParcelableStatus status; + if (activity.action == ParcelableActivity.ACTION_MENTION) { + status = activity.target_object_statuses[0]; + } else { + status = activity.target_statuses[0]; + } + final View profileImageView = holder.getProfileImageView(); + final View profileTypeView = holder.getProfileTypeView(); + if (context instanceof FragmentActivity) { + final Bundle options = Utils.makeSceneTransitionOption((FragmentActivity) context, + new Pair<>(profileImageView, UserFragment.TRANSITION_NAME_PROFILE_IMAGE), + new Pair<>(profileTypeView, UserFragment.TRANSITION_NAME_PROFILE_TYPE)); + Utils.openUserProfile(context, status.account_id, status.user_id, status.user_screen_name, options); + } else { + Utils.openUserProfile(context, status.account_id, status.user_id, status.user_screen_name, null); + } } public abstract Data getData(); @@ -144,18 +171,42 @@ public abstract class AbsActivitiesAdapter extends Adapter imp return mLoadMoreIndicatorEnabled; } + @Override + public void onItemMenuClick(ViewHolder holder, int position) { + + } + + @Override + public void onItemActionClick(ViewHolder holder, int id, int position) { + + } + @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType) { case ITEM_VIEW_TYPE_STATUS: { - final View view = mInflater.inflate(R.layout.card_item_status_compat, parent, false); + final View view; + if (mCompactCards) { + view = mInflater.inflate(R.layout.card_item_status_compact, parent, false); + } else { + view = mInflater.inflate(R.layout.card_item_status, parent, false); + final CardView cardView = (CardView) view.findViewById(R.id.card); + cardView.setCardBackgroundColor(mCardBackgroundColor); + } final StatusViewHolder holder = new StatusViewHolder(view); holder.setTextSize(getTextSize()); - holder.setOnClickListeners(this); + holder.setStatusClickListener(this); return holder; } case ITEM_VIEW_TYPE_TITLE_SUMMARY: { - final View view = mInflater.inflate(R.layout.list_item_activity_title_summary, parent, false); + final View view; + if (mCompactCards) { + view = mInflater.inflate(R.layout.card_item_activity_summary_compact, parent, false); + } else { + view = mInflater.inflate(R.layout.card_item_activity_summary, parent, false); + final CardView cardView = (CardView) view.findViewById(R.id.card); + cardView.setCardBackgroundColor(mCardBackgroundColor); + } final ActivityTitleSummaryViewHolder holder = new ActivityTitleSummaryViewHolder(this, view); holder.setTextSize(getTextSize()); return holder; @@ -207,7 +258,7 @@ public abstract class AbsActivitiesAdapter extends Adapter imp @Override public int getItemViewType(int position) { - if (position == getItemCount() - 1) { + if (position == getActivityCount()) { return ITEM_VIEW_TYPE_LOAD_INDICATOR; } else if (isGapItem(position)) { return ITEM_VIEW_TYPE_GAP; diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java index 0de496484..22278546c 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/AbsStatusesAdapter.java @@ -61,7 +61,7 @@ public abstract class AbsStatusesAdapter extends Adapter implemen SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mTextSize = preferences.getInt(KEY_TEXT_SIZE, context.getResources().getInteger(R.integer.default_text_size)); if (compact) { - mCardLayoutResource = R.layout.card_item_status_compat; + mCardLayoutResource = R.layout.card_item_status_compact; } else { mCardLayoutResource = R.layout.card_item_status; } @@ -173,7 +173,7 @@ public abstract class AbsStatusesAdapter extends Adapter implemen @Override public int getItemViewType(int position) { - if (position == getItemCount() - 1) { + if (position == getStatusCount()) { return ITEM_VIEW_TYPE_LOAD_INDICATOR; } else if (isGapItem(position)) { return ITEM_VIEW_TYPE_GAP; diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java index d24e49718..7d2866a47 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/ParcelableActivitiesAdapter.java @@ -34,8 +34,8 @@ public class ParcelableActivitiesAdapter extends AbsActivitiesAdapter mData; - public ParcelableActivitiesAdapter(Context context) { - super(context); + public ParcelableActivitiesAdapter(Context context, boolean compact) { + super(context,compact); } @Override @@ -57,7 +57,7 @@ public class ParcelableActivitiesAdapter extends AbsActivitiesAdapter implements IBaseCardAdapter, +public class ParcelableUserListsListAdapter extends BaseArrayAdapter implements IBaseCardAdapter, OnClickListener { private final Context mContext; @@ -52,11 +51,11 @@ public class ParcelableUserListsAdapter extends BaseArrayAdapter imp public View getView(final int position, final View convertView, final ViewGroup parent) { final View view = super.getView(position, convertView, parent); final Object tag = view.getTag(); - final UserListViewHolder holder; - if (tag instanceof UserListViewHolder) { - holder = (UserListViewHolder) tag; + final UserViewListHolder holder; + if (tag instanceof UserViewListHolder) { + holder = (UserViewListHolder) tag; } else { - holder = new UserListViewHolder(view); + holder = new UserViewListHolder(view); // holder.content.setOnOverflowIconClickListener(this); view.setTag(holder); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/decorator/DividerItemDecoration.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/decorator/DividerItemDecoration.java index ce92addc7..1c0af43a7 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/decorator/DividerItemDecoration.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/decorator/DividerItemDecoration.java @@ -42,6 +42,7 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { private int mOrientation; private Rect mPadding; + private int mDecorationStart = -1, mDecorationEnd = -1, mDecorationEndOffset; public DividerItemDecoration(Context context, int orientation) { mPadding = new Rect(); @@ -51,6 +52,21 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { setOrientation(orientation); } + + public void setDecorationStart(int start) { + mDecorationStart = start; + } + + public void setDecorationEnd(int end) { + mDecorationEnd = end; + mDecorationEndOffset = -1; + } + + public void setDecorationEndOffset(int endOffset) { + mDecorationEndOffset = endOffset; + mDecorationEnd = -1; + } + public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); @@ -78,6 +94,9 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); + final int childPos = parent.getChildPosition(child); + final int start = getDecorationStart(), end = getDecorationEnd(parent); + if (start >= 0 && end >= 0 && (childPos < start || childPos > end)) continue; final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin + @@ -96,6 +115,9 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); + final int childPos = parent.getChildPosition(child); + final int start = getDecorationStart(), end = getDecorationEnd(parent); + if (start >= 0 && end >= 0 && (childPos < start || childPos > end)) continue; final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getRight() + params.rightMargin + @@ -109,8 +131,9 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { - final Adapter adapter = parent.getAdapter(); - if (adapter != null && parent.getChildPosition(view) == adapter.getItemCount() - 1) { + final int childPos = parent.getChildPosition(view); + final int start = getDecorationStart(), end = getDecorationEnd(parent); + if (start >= 0 && end >= 0 && childPos < start && childPos > end) { outRect.setEmpty(); return; } @@ -120,4 +143,17 @@ public class DividerItemDecoration extends RecyclerView.ItemDecoration { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } + + private int getDecorationEnd(RecyclerView parent) { + if (mDecorationEnd != -1) return mDecorationEnd; + if (mDecorationEndOffset != -1) { + final Adapter adapter = parent.getAdapter(); + return adapter.getItemCount() - 1 - mDecorationEndOffset; + } + return -1; + } + + private int getDecorationStart() { + return mDecorationStart; + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/ICardSupportedAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/ContentCardClickListener.java similarity index 96% rename from twidere/src/main/java/org/mariotaku/twidere/adapter/iface/ICardSupportedAdapter.java rename to twidere/src/main/java/org/mariotaku/twidere/adapter/iface/ContentCardClickListener.java index 9a003054f..589ec1cee 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/ICardSupportedAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/ContentCardClickListener.java @@ -24,7 +24,7 @@ import android.support.v7.widget.RecyclerView.ViewHolder; /** * Created by mariotaku on 14/12/3. */ -public interface ICardSupportedAdapter { +public interface ContentCardClickListener { void onItemActionClick(ViewHolder holder, int id, int position); void onItemMenuClick(ViewHolder holder, int position); diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java index 1bb8ffb7c..f0b6c348a 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IActivitiesAdapter.java @@ -20,22 +20,16 @@ package org.mariotaku.twidere.adapter.iface; import org.mariotaku.twidere.model.ParcelableActivity; -import org.mariotaku.twidere.view.holder.StatusViewHolder; /** * Created by mariotaku on 14/11/18. */ public interface IActivitiesAdapter extends IContentCardAdapter { - ParcelableActivity getActivity(int position); int getActivityCount(); - void onStatusClick(StatusViewHolder holder, int position); - - void onUserProfileClick(StatusViewHolder holder, int position); - void setData(Data data); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IContentCardAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IContentCardAdapter.java index e69a4a496..d153cbfb7 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IContentCardAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IContentCardAdapter.java @@ -20,7 +20,6 @@ package org.mariotaku.twidere.adapter.iface; import android.content.Context; -import android.support.v7.widget.RecyclerView.ViewHolder; import org.mariotaku.twidere.util.AsyncTwitterWrapper; import org.mariotaku.twidere.util.ImageLoaderWrapper; @@ -29,7 +28,7 @@ import org.mariotaku.twidere.util.ImageLoadingHandler; /** * Created by mariotaku on 15/1/3. */ -public interface IContentCardAdapter extends IGapSupportedAdapter, ICardSupportedAdapter { +public interface IContentCardAdapter extends IGapSupportedAdapter, ContentCardClickListener { ImageLoaderWrapper getImageLoader(); Context getContext(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.java index 738d143d0..311652390 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/iface/IStatusesAdapter.java @@ -2,11 +2,12 @@ package org.mariotaku.twidere.adapter.iface; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.view.holder.StatusViewHolder; +import org.mariotaku.twidere.view.holder.StatusViewHolder.StatusClickListener; /** * Created by mariotaku on 14/11/18. */ -public interface IStatusesAdapter extends IContentCardAdapter { +public interface IStatusesAdapter extends IContentCardAdapter, StatusClickListener { ParcelableStatus getStatus(int position); @@ -14,7 +15,4 @@ public interface IStatusesAdapter extends IContentCardAdapter { void setData(Data data); - void onUserProfileClick(StatusViewHolder holder, int position); - - void onStatusClick(StatusViewHolder holder, int position); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseWebViewFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseWebViewFragment.java index 46690390d..7aba261d6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseWebViewFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/BaseWebViewFragment.java @@ -23,6 +23,7 @@ import android.annotation.SuppressLint; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; +import android.webkit.WebViewClient; import android.webkit.WebViewFragment; import org.mariotaku.twidere.Constants; @@ -36,10 +37,15 @@ public class BaseWebViewFragment extends WebViewFragment implements Constants { public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final WebView view = getWebView(); - view.setWebViewClient(new DefaultWebViewClient(getActivity())); + view.setWebViewClient(createWebViewClient()); final WebSettings settings = view.getSettings(); settings.setBuiltInZoomControls(true); settings.setJavaScriptEnabled(true); WebSettingsAccessor.setAllowUniversalAccessFromFileURLs(settings, true); } + + + protected WebViewClient createWebViewClient() { + return new DefaultWebViewClient(getActivity()); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsActivitiesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsActivitiesFragment.java index 82e834984..e55ce3047 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsActivitiesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsActivitiesFragment.java @@ -163,7 +163,9 @@ public abstract class AbsActivitiesFragment extends BaseSupportFragment im final LinearLayoutManager layoutManager = new LinearLayoutManager(context); layoutManager.setOrientation(LinearLayoutManager.VERTICAL); mRecyclerView.setLayoutManager(layoutManager); - mRecyclerView.addItemDecoration(new DividerItemDecoration(context, layoutManager.getOrientation())); + if (compact) { + mRecyclerView.addItemDecoration(new DividerItemDecoration(context, layoutManager.getOrientation())); + } mRecyclerView.setAdapter(mAdapter); mRecyclerView.setOnScrollListener(mOnScrollListener); getLoaderManager().initLoader(0, getArguments(), this); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java index 6db0f03d7..818c9ba01 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AbsStatusesFragment.java @@ -57,16 +57,20 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl private AbsStatusesAdapter mAdapter; private SimpleDrawerCallback mDrawerCallback; private OnScrollListener mOnScrollListener = new OnScrollListener() { + + private int mScrollState; + @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); + mScrollState = newState; } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); if (isRefreshing()) return; - if (mAdapter.hasLoadMoreIndicator() + if (mAdapter.hasLoadMoreIndicator() && mScrollState != RecyclerView.SCROLL_STATE_IDLE && layoutManager.findLastVisibleItemPosition() == mAdapter.getItemCount() - 1) { onLoadMoreStatuses(); } @@ -174,9 +178,12 @@ public abstract class AbsStatusesFragment extends BaseSupportFragment impl public void onLoadFinished(Loader loader, Data data) { setRefreshing(false); mAdapter.setData(data); + mAdapter.setLoadMoreIndicatorEnabled(hasMoreData(data)); setListShown(true); } + protected abstract boolean hasMoreData(Data data); + @Override public void onLoaderReset(Loader loader) { } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java index e0b8e222c..51cdd7a4e 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/AccountsDashboardFragment.java @@ -65,6 +65,7 @@ import org.mariotaku.twidere.activity.iface.IThemedActivity; import org.mariotaku.twidere.activity.support.AccountsManagerActivity; import org.mariotaku.twidere.activity.support.ComposeActivity; import org.mariotaku.twidere.activity.support.DraftsActivity; +import org.mariotaku.twidere.activity.support.GlobalSearchBoxActivity; import org.mariotaku.twidere.activity.support.HomeActivity; import org.mariotaku.twidere.activity.support.UserProfileEditorActivity; import org.mariotaku.twidere.adapter.ArrayAdapter; @@ -187,12 +188,15 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement final OptionItem option = (OptionItem) item; switch (option.id) { case MENU_SEARCH: { - final FragmentActivity a = getActivity(); - if (a instanceof HomeActivity) { - ((HomeActivity) a).openSearchView(account); - } else { - getActivity().onSearchRequested(); - } +// final FragmentActivity a = getActivity(); +// if (a instanceof HomeActivity) { +// ((HomeActivity) a).openSearchView(account); +// } else { +// getActivity().onSearchRequested(); +// } + final Intent intent = new Intent(getActivity(), GlobalSearchBoxActivity.class); + intent.putExtra(EXTRA_ACCOUNT_ID, account.account_id); + startActivity(intent); closeAccountsDrawer(); break; } @@ -294,7 +298,9 @@ public class AccountsDashboardFragment extends BaseSupportListFragment implement super.onActivityCreated(savedInstanceState); mPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mResolver = getContentResolver(); - final Context context = getView().getContext(); + final View view = getView(); + if (view == null) throw new AssertionError(); + final Context context = view.getContext(); mImageLoader = TwidereApplication.getInstance(context).getImageLoaderWrapper(); final LayoutInflater inflater = LayoutInflater.from(context); final ListView listView = getListView(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportWebViewFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportWebViewFragment.java index 51ab1b3e7..5bf3c7fbe 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportWebViewFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseSupportWebViewFragment.java @@ -23,6 +23,7 @@ import android.annotation.SuppressLint; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; +import android.webkit.WebViewClient; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.util.accessor.WebSettingsAccessor; @@ -35,11 +36,15 @@ public class BaseSupportWebViewFragment extends SupportWebViewFragment implement public void onActivityCreated(final Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final WebView view = getWebView(); - view.setWebViewClient(new DefaultWebViewClient(getActivity())); + view.setWebViewClient(createWebViewClient()); final WebSettings settings = view.getSettings(); settings.setBuiltInZoomControls(true); settings.setJavaScriptEnabled(true); WebSettingsAccessor.setAllowUniversalAccessFromFileURLs(settings, true); } + + protected WebViewClient createWebViewClient() { + return new DefaultWebViewClient(getActivity()); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java index 13f6217ea..99857f766 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/BaseUserListsListFragment.java @@ -33,7 +33,7 @@ import android.view.View; import android.widget.AbsListView; import android.widget.ListView; -import org.mariotaku.twidere.adapter.ParcelableUserListsAdapter; +import org.mariotaku.twidere.adapter.ParcelableUserListsListAdapter; import org.mariotaku.twidere.adapter.iface.IBaseCardAdapter.MenuButtonClickListener; import org.mariotaku.twidere.loader.support.BaseUserListsLoader; import org.mariotaku.twidere.model.ParcelableUserList; @@ -49,7 +49,7 @@ import static org.mariotaku.twidere.util.Utils.openUserListDetails; abstract class BaseUserListsListFragment extends BasePullToRefreshListFragment implements LoaderCallbacks>, OnMenuItemClickListener, MenuButtonClickListener { - private ParcelableUserListsAdapter mAdapter; + private ParcelableUserListsListAdapter mAdapter; private SharedPreferences mPreferences; private ListView mListView; @@ -77,7 +77,7 @@ abstract class BaseUserListsListFragment extends BasePullToRefreshListFragment i } @Override - public ParcelableUserListsAdapter getListAdapter() { + public ParcelableUserListsListAdapter getListAdapter() { return mAdapter; } @@ -116,7 +116,7 @@ abstract class BaseUserListsListFragment extends BasePullToRefreshListFragment i mUserId = args.getLong(EXTRA_USER_ID, -1); mScreenName = args.getString(EXTRA_SCREEN_NAME); } - mAdapter = new ParcelableUserListsAdapter(getActivity()); + mAdapter = new ParcelableUserListsListAdapter(getActivity()); mListView = getListView(); mListView.setDivider(null); mListView.setSelector(android.R.color.transparent); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CardBrowserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CardBrowserFragment.java new file mode 100644 index 000000000..826ff61e0 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CardBrowserFragment.java @@ -0,0 +1,45 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.twidere.fragment.support; + +import android.os.Bundle; +import android.webkit.WebSettings; +import android.webkit.WebView; + +/** + * Created by mariotaku on 15/1/6. + */ +public class CardBrowserFragment extends SupportBrowserFragment { + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final WebView view = getWebView(); + final WebSettings settings = view.getSettings(); + settings.setBuiltInZoomControls(false); + } + + public static CardBrowserFragment show(String uri) { + final Bundle args = new Bundle(); + args.putString(EXTRA_URI, uri); + final CardBrowserFragment fragment = new CardBrowserFragment(); + fragment.setArguments(args); + return fragment; + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorStatusesFragment.java index d81e3d27c..dd57705f5 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/CursorStatusesFragment.java @@ -88,6 +88,11 @@ public abstract class CursorStatusesFragment extends AbsStatusesFragment return true; } + @Override + protected boolean hasMoreData(Cursor cursor) { + return true; + } + @Override protected long[] getAccountIds() { final Bundle args = getArguments(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesConversationFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesConversationFragment.java index 2ca4a3f36..147954aad 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesConversationFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/DirectMessagesConversationFragment.java @@ -33,6 +33,7 @@ import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; +import android.support.v4.util.Pair; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -48,7 +49,6 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.BaseAdapter; import android.widget.EditText; import android.widget.ImageView; import android.widget.ImageView.ScaleType; @@ -125,6 +125,8 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl private EditText mUserQuery; private View mUsersSearchProgress; private View mQueryButton; + private View mUsersSearchEmpty; + private TextView mUsersSearchEmptyText; private PopupMenu mPopupMenu; @@ -160,6 +162,7 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl mUsersSearchList.setVisibility(View.VISIBLE); mUsersSearchProgress.setVisibility(View.GONE); mUsersSearchAdapter.setData(data, true); + updateEmptyText(); } @Override @@ -238,6 +241,7 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl mUsersSearchAdapter = new SimpleParcelableUsersAdapter(activity); mUsersSearchList.setAdapter(mUsersSearchAdapter); + mUsersSearchList.setEmptyView(mUsersSearchEmpty); mUsersSearchList.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { @@ -361,14 +365,6 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl sendDirectMessage(); break; } -// case R.id.recipient_selector: { -// if (mAccountId <= 0) return; -// final Intent intent = new Intent(INTENT_ACTION_SELECT_USER); -// intent.setClass(getActivity(), UserListSelectorActivity.class); -// intent.putExtra(EXTRA_ACCOUNT_ID, mAccountId); -// startActivityForResult(intent, REQUEST_SELECT_USER); -// break; -// } case R.id.add_image: { final Intent intent = new Intent(getActivity(), ImagePickerActivity.class); startActivityForResult(intent, REQUEST_PICK_IMAGE); @@ -377,8 +373,10 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl case R.id.item_profile_image: { final ParcelableUser recipient = mRecipient; if (recipient == null) return; + final Bundle options = Utils.makeSceneTransitionOption(getActivity(), + new Pair<>(view, UserFragment.TRANSITION_NAME_PROFILE_IMAGE)); Utils.openUserProfile(getActivity(), recipient.account_id, recipient.id, - recipient.screen_name, null); + recipient.screen_name, options); break; } case R.id.query_button: { @@ -554,6 +552,7 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl final Bus bus = TwidereApplication.getInstance(getActivity()).getMessageBus(); bus.register(this); updateTextCount(); + updateEmptyText(); } @Override @@ -582,11 +581,22 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl mSendButton.setEnabled(mValidator.isValidTweet(s.toString())); } + private void updateEmptyText() { + final boolean noQuery = mUserQuery.length() <= 0; + if (noQuery) { + mUsersSearchEmptyText.setText(R.string.type_name_to_search); + } else { + mUsersSearchEmptyText.setText(R.string.no_user_found); + } + } + @Override public void onViewCreated(final View view, final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mUsersSearchProgress = view.findViewById(R.id.users_search_progress); mUsersSearchList = (ListView) view.findViewById(R.id.users_search_list); + mUsersSearchEmpty = view.findViewById(R.id.users_search_empty); + mUsersSearchEmptyText = (TextView) view.findViewById(R.id.users_search_empty_text); mMessagesListView = (ListView) view.findViewById(android.R.id.list); final View inputSendContainer = view.findViewById(R.id.input_send_container); mConversationContainer = view.findViewById(R.id.conversation_container); @@ -709,86 +719,6 @@ public class DirectMessagesConversationFragment extends BaseSupportFragment impl mTextCountView.setTextCount(count); } - private static class UsersSearchAdapter extends BaseAdapter { - - private final LayoutInflater mInflater; - private Object mUsers; - private int mScreenNameIdx; - private long mAccountId; - - public UsersSearchAdapter(Context context) { - mInflater = LayoutInflater.from(context); - } - - public void setUsers(List users) { - mUsers = users; - notifyDataSetChanged(); - } - - public void setUsers(Cursor users) { - mUsers = users; - notifyDataSetChanged(); - } - - @Override - public int getCount() { - if (mUsers instanceof Cursor) { - final Cursor c = (Cursor) mUsers; - mScreenNameIdx = c.getColumnIndex(CachedUsers.SCREEN_NAME); - return c.getCount(); - } else if (mUsers instanceof List) { - return ((List) mUsers).size(); - } - return 0; - } - - public void setAccountId(long accountId) { - mAccountId = accountId; - } - - @Override - public ParcelableUser getItem(int position) { - if (mUsers instanceof Cursor) { - final Cursor c = (Cursor) mUsers; - return new ParcelableUser(c, mAccountId); - } else if (mUsers instanceof List) { - return (ParcelableUser) ((List) mUsers).get(position); - } - throw new IllegalStateException(); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final View view; - if (convertView != null) { - view = convertView; - } else { - view = mInflater.inflate(R.layout.list_item_user, parent, false); - } - if (mUsers instanceof Cursor) { - final Cursor c = (Cursor) mUsers; - c.moveToPosition(position); - bindUser(view, c); - } else if (mUsers instanceof List) { - bindUser(view, getItem(position)); - } - return view; - } - - private void bindUser(View view, ParcelableUser user) { - - } - - private void bindUser(View view, Cursor cursor) { - - } - } - private static class MyUserSearchLoader extends UserSearchLoader { private final boolean mFromCache; diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableActivitiesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableActivitiesFragment.java index 35459499b..027291d19 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableActivitiesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableActivitiesFragment.java @@ -67,7 +67,7 @@ public abstract class ParcelableActivitiesFragment extends AbsActivitiesFragment @Override protected ParcelableActivitiesAdapter onCreateAdapter(final Context context, final boolean compact) { - return new ParcelableActivitiesAdapter(context); + return new ParcelableActivitiesAdapter(context,compact); } @Override diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableStatusesFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableStatusesFragment.java index f0eb2a955..ff268ff86 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableStatusesFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/ParcelableStatusesFragment.java @@ -45,6 +45,8 @@ import java.util.Set; public abstract class ParcelableStatusesFragment extends AbsStatusesFragment> { + private long mLastId; + public final void deleteStatus(final long statusId) { final List data = getAdapterData(); if (statusId <= 0 || data == null) return; @@ -88,6 +90,12 @@ public abstract class ParcelableStatusesFragment extends AbsStatusesFragment data) { + if (data == null || data.isEmpty()) return false; + return (mLastId != (mLastId = data.get(data.size() - 1).id)); + } + @Override protected long[] getAccountIds() { return new long[]{getAccountId()}; diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java index f092c8d51..d8feee437 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/RetweetQuoteDialogFragment.java @@ -24,6 +24,7 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.FragmentManager; @@ -50,17 +51,25 @@ public class RetweetQuoteDialogFragment extends BaseSupportDialogFragment implem @Override public void onClick(final DialogInterface dialog, final int which) { + final ParcelableStatus status = getStatus(); + if (status == null) return; switch (which) { - case DialogInterface.BUTTON_POSITIVE: - final ParcelableStatus status = getStatus(); + case DialogInterface.BUTTON_POSITIVE: { final AsyncTwitterWrapper twitter = getTwitterWrapper(); - if (status == null || twitter == null) return; + if (twitter == null) return; if (isMyRetweet(status)) { twitter.cancelRetweetAsync(status.account_id, status.id, status.my_retweet_id); } else { twitter.retweetStatusAsync(status.account_id, status.id); } break; + } + case DialogInterface.BUTTON_NEUTRAL: { + final Intent intent = new Intent(INTENT_ACTION_QUOTE); + intent.putExtra(EXTRA_STATUS, status); + startActivity(intent); + break; + } default: break; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java index 5681a83df..77420040d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java @@ -66,7 +66,7 @@ import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration; import org.mariotaku.twidere.adapter.iface.IStatusesAdapter; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.constant.IntentConstants; -import org.mariotaku.twidere.loader.ParcelableStatusLoader; +import org.mariotaku.twidere.loader.support.ParcelableStatusLoader; import org.mariotaku.twidere.loader.support.StatusRepliesLoader; import org.mariotaku.twidere.model.ListResponse; import org.mariotaku.twidere.model.ParcelableAccount; @@ -140,6 +140,8 @@ public class StatusFragment extends BaseSupportFragment private View mStatusContent; private View mProgressContainer; private View mErrorContainer; + private DividerItemDecoration mItemDecoration; + private LoaderCallbacks> mRepliesLoaderCallback = new LoaderCallbacks>() { @Override public Loader> onCreateLoader(int id, Bundle args) { @@ -207,8 +209,9 @@ public class StatusFragment extends BaseSupportFragment final Context context = view.getContext(); final boolean compact = Utils.isCompactCards(context); mLayoutManager = new StatusListLinearLayoutManager(context, mRecyclerView); + mItemDecoration = new DividerItemDecoration(context, mLayoutManager.getOrientation()); if (compact) { - mRecyclerView.addItemDecoration(new DividerItemDecoration(context, mLayoutManager.getOrientation())); + mRecyclerView.addItemDecoration(mItemDecoration); } mLayoutManager.setRecycleChildrenOnDetach(true); mRecyclerView.setLayoutManager(mLayoutManager); @@ -420,7 +423,7 @@ public class StatusFragment extends BaseSupportFragment mTextSize = preferences.getInt(KEY_TEXT_SIZE, res.getInteger(R.integer.default_text_size)); mIsCompact = compact; if (compact) { - mCardLayoutResource = R.layout.card_item_status_compat; + mCardLayoutResource = R.layout.card_item_status_compact; } else { mCardLayoutResource = R.layout.card_item_status; } @@ -531,8 +534,21 @@ public class StatusFragment extends BaseSupportFragment public void setDetailMediaExpanded(boolean expanded) { mDetailMediaExpanded = expanded; notifyDataSetChanged(); + updateItemDecoration(); } + private void updateItemDecoration() { + final DividerItemDecoration decoration = mFragment.getItemDecoration(); + decoration.setDecorationStart(0); + if (mReplies != null) { + decoration.setDecorationEnd(getItemCount() - 2); + } else { + decoration.setDecorationEnd(getItemCount() - 3); + } + mFragment.mRecyclerView.invalidateItemDecorations(); + } + + @Override public boolean isGapItem(int position) { return false; @@ -576,7 +592,7 @@ public class StatusFragment extends BaseSupportFragment if (mCachedHolder != null) return mCachedHolder; final View view; if (mIsCompact) { - view = mInflater.inflate(R.layout.header_status_common, parent, false); + view = mInflater.inflate(R.layout.header_status_compact, parent, false); } else { view = mInflater.inflate(R.layout.header_status, parent, false); } @@ -677,6 +693,7 @@ public class StatusFragment extends BaseSupportFragment public void setConversation(List conversation) { mConversation = conversation; notifyDataSetChanged(); + updateItemDecoration(); } public void setEventListener(StatusAdapterListener listener) { @@ -686,6 +703,7 @@ public class StatusFragment extends BaseSupportFragment public void setReplies(List replies) { mReplies = replies; notifyDataSetChanged(); + updateItemDecoration(); } public boolean setStatus(ParcelableStatus status) { @@ -697,6 +715,7 @@ public class StatusFragment extends BaseSupportFragment mStatusAccount = null; } notifyDataSetChanged(); + updateItemDecoration(); return !CompareUtils.objectEquals(old, status); } @@ -709,6 +728,10 @@ public class StatusFragment extends BaseSupportFragment } } + private DividerItemDecoration getItemDecoration() { + return mItemDecoration; + } + @Override public void onLoaderReset(final Loader> loader) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/SupportBrowserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/SupportBrowserFragment.java index d5839f577..b7afb97b7 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/SupportBrowserFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/SupportBrowserFragment.java @@ -35,12 +35,5 @@ public class SupportBrowserFragment extends BaseSupportWebViewFragment { view.loadUrl(ParseUtils.parseString(uri, "about:blank")); } - public static SupportBrowserFragment show(String uri) { - final Bundle args = new Bundle(); - args.putString(EXTRA_URI, uri); - final SupportBrowserFragment fragment = new SupportBrowserFragment(); - fragment.setArguments(args); - return fragment; - } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java index ba9b68c7f..179fc044a 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java @@ -137,7 +137,6 @@ import static org.mariotaku.twidere.util.Utils.addIntentToMenu; import static org.mariotaku.twidere.util.Utils.formatToLongTimeString; import static org.mariotaku.twidere.util.Utils.getAccountColor; import static org.mariotaku.twidere.util.Utils.getAccountScreenName; -import static org.mariotaku.twidere.util.UserColorNameUtils.getDisplayName; import static org.mariotaku.twidere.util.Utils.getErrorMessage; import static org.mariotaku.twidere.util.Utils.getLocalizedNumber; import static org.mariotaku.twidere.util.Utils.getOriginalTwitterProfileImage; @@ -322,6 +321,8 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener mFollowButton.setVisibility(View.VISIBLE); } else if (relationship != null) { final int drawableRes; + mFollowButton.setEnabled(!relationship.isSourceBlockedByTarget()); + getView().findViewById(R.id.pages_error).setVisibility(relationship.isSourceBlockedByTarget() ? View.VISIBLE : View.GONE); if (relationship.isSourceBlockingTarget()) { mFollowButton.setText(R.string.unblock); drawableRes = R.drawable.ic_follow_blocked; @@ -369,6 +370,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener } else { mFollowButton.setText(null); mFollowButton.setVisibility(View.GONE); + getView().findViewById(R.id.pages_error).setVisibility(View.GONE); // mFollowingYouIndicator.setVisibility(View.GONE); } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java index 1cac12c3d..b57bd16f3 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListFragment.java @@ -82,7 +82,6 @@ import twitter4j.UserList; import static android.text.TextUtils.isEmpty; import static org.mariotaku.twidere.util.Utils.addIntentToMenu; import static org.mariotaku.twidere.util.Utils.getAccountColor; -import static org.mariotaku.twidere.util.UserColorNameUtils.getDisplayName; import static org.mariotaku.twidere.util.Utils.getTwitterInstance; import static org.mariotaku.twidere.util.Utils.openUserProfile; import static org.mariotaku.twidere.util.Utils.setMenuItemAvailability; @@ -98,7 +97,6 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList private TextView mListNameView, mCreatedByView, mDescriptionView, mErrorMessageView; private View mErrorRetryContainer, mProgressContainer; private ColorLabelRelativeLayout mProfileContainer; - private View mDescriptionContainer; private Button mRetryButton; private HeaderDrawerLayout mHeaderDrawerLayout; private ViewPager mViewPager; @@ -186,7 +184,7 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList public void displayUserList(final ParcelableUserList userList) { if (userList == null || getActivity() == null) return; getLoaderManager().destroyLoader(0); - final boolean is_myself = userList.account_id == userList.user_id; + final boolean isMyself = userList.account_id == userList.user_id; mErrorRetryContainer.setVisibility(View.GONE); mProgressContainer.setVisibility(View.GONE); mUserList = userList; @@ -196,7 +194,7 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList userList.user_screen_name, false); mCreatedByView.setText(getString(R.string.created_by, display_name)); final String description = userList.description; - mDescriptionContainer.setVisibility(is_myself || !isEmpty(description) ? View.VISIBLE : View.GONE); + mDescriptionView.setVisibility(isMyself || !isEmpty(description) ? View.VISIBLE : View.GONE); mDescriptionView.setText(description); final TwidereLinkify linkify = new TwidereLinkify( new OnLinkClickHandler(getActivity(), getMultiSelectManager())); @@ -276,6 +274,7 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList mViewPager.setAdapter(mPagerAdapter); mPagerIndicator.setViewPager(mViewPager); + mPagerIndicator.setTabDisplayOption(TabPagerIndicator.LABEL); mTwitterWrapper = getApplication().getTwitterWrapper(); mProfileImageLoader = getApplication().getImageLoaderWrapper(); @@ -480,7 +479,6 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList mCreatedByView = (TextView) headerView.findViewById(R.id.created_by); mDescriptionView = (TextView) headerView.findViewById(R.id.description); mProfileImageView = (ImageView) headerView.findViewById(R.id.profile_image); - mDescriptionContainer = headerView.findViewById(R.id.description_container); mRetryButton = (Button) mErrorRetryContainer.findViewById(R.id.retry); mErrorMessageView = (TextView) mErrorRetryContainer.findViewById(R.id.error_message); mViewPager = (ViewPager) contentView.findViewById(R.id.view_pager); @@ -519,8 +517,8 @@ public class UserListFragment extends BaseSupportFragment implements OnClickList tabArgs.putString(EXTRA_LIST_NAME, args.getString(EXTRA_LIST_NAME)); } mPagerAdapter.addTab(UserListTimelineFragment.class, tabArgs, getString(R.string.statuses), null, 0); - mPagerAdapter.addTab(UserListMembersFragment.class, tabArgs, getString(R.string.list_members), null, 1); - mPagerAdapter.addTab(UserListSubscribersFragment.class, tabArgs, getString(R.string.list_subscribers), null, 2); + mPagerAdapter.addTab(UserListMembersFragment.class, tabArgs, getString(R.string.members), null, 1); + mPagerAdapter.addTab(UserListSubscribersFragment.class, tabArgs, getString(R.string.subscribers), null, 2); mPagerIndicator.notifyDataSetChanged(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembershipsListFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembershipsListFragment.java index 9d3403ca8..fc50ee6ee 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembershipsListFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListMembershipsListFragment.java @@ -28,10 +28,10 @@ import java.util.List; public class UserListMembershipsListFragment extends BaseUserListsListFragment { - @Override - public Loader> newLoaderInstance(final long account_id, final long user_id, - final String screen_name) { - return new UserListMembershipsLoader(getActivity(), account_id, user_id, screen_name, getCursor(), getData()); - } + @Override + public Loader> newLoaderInstance(final long accountId, final long userId, + final String screenName) { + return new UserListMembershipsLoader(getActivity(), accountId, userId, screenName, getCursor(), getData()); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java index 7f02d8987..234f26059 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsFragment.java @@ -65,6 +65,7 @@ public class UserListsFragment extends BaseSupportFragment implements RefreshScr mViewPager.setAdapter(mAdapter); mViewPager.setOffscreenPageLimit(2); mPagerIndicator.setViewPager(mViewPager); + mPagerIndicator.setTabDisplayOption(TabPagerIndicator.LABEL); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsListFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsListFragment.java index dcdddeaed..2a6d3c850 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsListFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserListsListFragment.java @@ -31,7 +31,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import org.mariotaku.twidere.R; -import org.mariotaku.twidere.adapter.ParcelableUserListsAdapter; +import org.mariotaku.twidere.adapter.ParcelableUserListsListAdapter; import org.mariotaku.twidere.loader.support.UserListsLoader; import org.mariotaku.twidere.model.ParcelableUserList; @@ -59,7 +59,7 @@ public class UserListsListFragment extends BaseUserListsListFragment { @Override public Loader> newLoaderInstance(final long accountId, final long userId, final String screenName) { - return new UserListsLoader(getActivity(), accountId, userId, screenName, getData()); + return new UserListsLoader(getActivity(), accountId, userId, screenName, true, getData()); } @Override @@ -113,7 +113,7 @@ public class UserListsListFragment extends BaseUserListsListFragment { } private void removeUserList(final long id) { - final ParcelableUserListsAdapter adapter = getListAdapter(); + final ParcelableUserListsListAdapter adapter = getListAdapter(); final int listsIdx = adapter.findItemPosition(id); if (listsIdx >= 0) { adapter.removeAt(listsIdx); diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/ParcelableStatusLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableStatusLoader.java similarity index 98% rename from twidere/src/main/java/org/mariotaku/twidere/loader/ParcelableStatusLoader.java rename to twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableStatusLoader.java index f9ab61372..976b22a70 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/ParcelableStatusLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableStatusLoader.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package org.mariotaku.twidere.loader; +package org.mariotaku.twidere.loader.support; import android.content.Context; import android.os.Bundle; diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/GLImageLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java similarity index 89% rename from twidere/src/main/java/org/mariotaku/gallery3d/GLImageLoader.java rename to twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java index 99f330c33..878ed4b8b 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/GLImageLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TileImageLoader.java @@ -1,7 +1,7 @@ /* - * Twidere - Twitter client for Android + * Twidere - Twitter client for Android * - * Copyright (C) 2012-2013 Mariotaku Lee + * Copyright (C) 2012-2015 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 @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package org.mariotaku.gallery3d; +package org.mariotaku.twidere.loader.support; import android.content.ContentResolver; import android.content.Context; @@ -33,11 +33,10 @@ import android.util.DisplayMetrics; import com.nostra13.universalimageloader.cache.disc.DiskCache; import com.nostra13.universalimageloader.core.download.ImageDownloader; +import com.nostra13.universalimageloader.utils.IoUtils; -import org.mariotaku.gallery3d.util.BitmapUtils; -import org.mariotaku.gallery3d.util.GalleryUtils; -import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.app.TwidereApplication; +import org.mariotaku.twidere.util.BitmapUtils; import org.mariotaku.twidere.util.Exif; import org.mariotaku.twidere.util.ImageValidator; import org.mariotaku.twidere.util.ParseUtils; @@ -49,7 +48,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -public class GLImageLoader extends AsyncTaskLoader implements Constants { +public class TileImageLoader extends AsyncTaskLoader { private final Uri mUri; private final Handler mHandler; @@ -60,7 +59,7 @@ public class GLImageLoader extends AsyncTaskLoader impleme private final float mFallbackSize; private final long mAccountId; - public GLImageLoader(final Context context, final DownloadListener listener, final long accountId, final Uri uri) { + public TileImageLoader(final Context context, final DownloadListener listener, final long accountId, final Uri uri) { super(context); mHandler = new Handler(); mAccountId = accountId; @@ -75,7 +74,7 @@ public class GLImageLoader extends AsyncTaskLoader impleme } @Override - public GLImageLoader.Result loadInBackground() { + public TileImageLoader.Result loadInBackground() { if (mUri == null) { return Result.nullInstance(); } @@ -105,8 +104,8 @@ public class GLImageLoader extends AsyncTaskLoader impleme dump(is, os); mHandler.post(new DownloadFinishRunnable(this, mListener)); } finally { - GalleryUtils.closeSilently(is); - GalleryUtils.closeSilently(os); + IoUtils.closeSilently(is); + IoUtils.closeSilently(os); } if (!ImageValidator.checkImageValidity(cacheFile)) { // The file is corrupted, so we remove it from @@ -137,7 +136,6 @@ public class GLImageLoader extends AsyncTaskLoader impleme final String path = file.getAbsolutePath(); final BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; - o.inPreferredConfig = Bitmap.Config.RGB_565; BitmapFactory.decodeFile(path, o); final int width = o.outWidth, height = o.outHeight; if (width <= 0 || height <= 0) return Result.getInstance(file, null); @@ -155,7 +153,6 @@ public class GLImageLoader extends AsyncTaskLoader impleme final int height = decoder.getHeight(); final BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = BitmapUtils.computeSampleSize(mFallbackSize / Math.max(width, height)); - options.inPreferredConfig = Bitmap.Config.RGB_565; final Bitmap bitmap = decoder.decodeRegion(new Rect(0, 0, width, height), options); return Result.getInstance(decoder, bitmap, Exif.getOrientation(file), file); } catch (final IOException e) { @@ -217,6 +214,10 @@ public class GLImageLoader extends AsyncTaskLoader impleme this.exception = exception; } + public boolean hasData() { + return bitmap != null || decoder != null; + } + public static Result getInstance(final Bitmap bitmap, final int orientation, final File file) { return new Result(null, bitmap, orientation, file, null); } @@ -237,11 +238,11 @@ public class GLImageLoader extends AsyncTaskLoader impleme private final static class DownloadErrorRunnable implements Runnable { - private final GLImageLoader loader; + private final TileImageLoader loader; private final DownloadListener listener; private final Throwable t; - DownloadErrorRunnable(final GLImageLoader loader, final DownloadListener listener, final Throwable t) { + DownloadErrorRunnable(final TileImageLoader loader, final DownloadListener listener, final Throwable t) { this.loader = loader; this.listener = listener; this.t = t; @@ -256,10 +257,10 @@ public class GLImageLoader extends AsyncTaskLoader impleme private final static class DownloadFinishRunnable implements Runnable { - private final GLImageLoader loader; + private final TileImageLoader loader; private final DownloadListener listener; - DownloadFinishRunnable(final GLImageLoader loader, final DownloadListener listener) { + DownloadFinishRunnable(final TileImageLoader loader, final DownloadListener listener) { this.loader = loader; this.listener = listener; } @@ -273,11 +274,11 @@ public class GLImageLoader extends AsyncTaskLoader impleme private final static class DownloadStartRunnable implements Runnable { - private final GLImageLoader loader; + private final TileImageLoader loader; private final DownloadListener listener; private final long total; - DownloadStartRunnable(final GLImageLoader loader, final DownloadListener listener, final long total) { + DownloadStartRunnable(final TileImageLoader loader, final DownloadListener listener, final long total) { this.loader = loader; this.listener = listener; this.total = total; diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListsLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListsLoader.java index 49d4a07c4..c8c36d8cf 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListsLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserListsLoader.java @@ -23,38 +23,38 @@ import android.content.Context; import org.mariotaku.twidere.model.ParcelableUserList; +import java.util.List; + import twitter4j.ResponseList; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.UserList; -import java.util.List; - public class UserListsLoader extends BaseUserListsLoader { - public static final String LOGTAG = UserListsLoader.class.getSimpleName(); + private final long mUserId; + private final String mScreenName; + private final boolean mReverse; - private final long mUserId; - private final String mScreenName; + public UserListsLoader(final Context context, final long accountId, final long userId, + final String screenName, final boolean reverse, final List data) { + super(context, accountId, 0, data); + mUserId = userId; + mScreenName = screenName; + mReverse = reverse; + } - public UserListsLoader(final Context context, final long accountId, final long userId, final String screenName, - final List data) { - super(context, accountId, 0, data); - mUserId = userId; - mScreenName = screenName; - } + @Override + public ResponseList getUserLists(final Twitter twitter) throws TwitterException { + if (twitter == null) return null; + if (mUserId > 0) + return twitter.getUserLists(mUserId, mReverse); + else if (mScreenName != null) return twitter.getUserLists(mScreenName, mReverse); + return null; + } - @Override - public ResponseList getUserLists(final Twitter twitter) throws TwitterException { - if (twitter == null) return null; - if (mUserId > 0) - return twitter.getUserLists(mUserId); - else if (mScreenName != null) return twitter.getUserLists(mScreenName); - return null; - } - - @Override - protected boolean isFollowing(final UserList list) { - return true; - } + @Override + protected boolean isFollowing(final UserList list) { + return true; + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/preference/CardPreviewPreference.java b/twidere/src/main/java/org/mariotaku/twidere/preference/CardPreviewPreference.java index 01cebb533..94999dffc 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/preference/CardPreviewPreference.java +++ b/twidere/src/main/java/org/mariotaku/twidere/preference/CardPreviewPreference.java @@ -90,7 +90,7 @@ public class CardPreviewPreference extends Preference implements Constants, OnSh @Override protected View onCreateView(final ViewGroup parent) { if (mPreferences != null && mPreferences.getBoolean(KEY_COMPACT_CARDS, false)) - return mInflater.inflate(R.layout.card_item_status_compat, parent, false); + return mInflater.inflate(R.layout.card_item_status_compact, parent, false); return mInflater.inflate(R.layout.card_item_status, parent, false); } diff --git a/twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/BitmapUtils.java similarity index 90% rename from twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapUtils.java rename to twidere/src/main/java/org/mariotaku/twidere/util/BitmapUtils.java index 1307a6251..f2f52a364 100644 --- a/twidere/src/main/java/org/mariotaku/gallery3d/util/BitmapUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/BitmapUtils.java @@ -14,19 +14,21 @@ * limitations under the License. */ -package org.mariotaku.gallery3d.util; +package org.mariotaku.twidere.util; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; +import org.mariotaku.twidere.util.MathUtils; + public class BitmapUtils { // Find the max x that 1 / x <= scale. public static int computeSampleSize(final float scale) { if (scale <= 0) return 1; final int initialSize = Math.max(1, (int) Math.ceil(1 / scale)); - return initialSize <= 8 ? GalleryUtils.nextPowerOf2(initialSize) : (initialSize + 7) / 8 * 8; + return initialSize <= 8 ? MathUtils.nextPowerOf2(initialSize) : (initialSize + 7) / 8 * 8; } // This computes a sample size which makes the longer side at least @@ -35,7 +37,7 @@ public class BitmapUtils { final int initialSize = Math.max(w / minSideLength, h / minSideLength); if (initialSize <= 1) return 1; - return initialSize <= 8 ? GalleryUtils.prevPowerOf2(initialSize) : initialSize / 8 * 8; + return initialSize <= 8 ? MathUtils.prevPowerOf2(initialSize) : initialSize / 8 * 8; } // Resize the bitmap if each side is >= targetSize * 2 diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/MathUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/MathUtils.java index ed7a8b19e..84507935c 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/MathUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/MathUtils.java @@ -30,4 +30,26 @@ public class MathUtils { return Math.max(Math.min(num, max), min); } + // Returns the next power of two. + // Returns the input if it is already power of 2. + // Throws IllegalArgumentException if the input is <= 0 or + // the answer overflows. + public static int nextPowerOf2(int n) { + if (n <= 0 || n > 1 << 30) throw new IllegalArgumentException("n is invalid: " + n); + n -= 1; + n |= n >> 16; + n |= n >> 8; + n |= n >> 4; + n |= n >> 2; + n |= n >> 1; + return n + 1; + } + + // Returns the previous power of two. + // Returns the input if it is already power of 2. + // Throws IllegalArgumentException if the input is <= 0 + public static int prevPowerOf2(final int n) { + if (n <= 0) throw new IllegalArgumentException(); + return Integer.highestOneBit(n); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java index 7a01763b2..5e9771b09 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ThemeUtils.java @@ -215,6 +215,15 @@ public class ThemeUtils implements Constants { return view; } + public static int getGlobalSearchThemeResource(final Context context) { + return getGlobalSearchThemeResource(getThemeNameOption(context)); + } + + public static int getGlobalSearchThemeResource(final String name) { + if (VALUE_THEME_NAME_DARK.equals(name)) return R.style.Theme_Twidere_Dark_GlobalSearch; + return R.style.Theme_Twidere_Light_GlobalSearch; + } + private static void applyColorTintForView(View view, int tintColor) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return; if (view instanceof IThemedView) { @@ -227,10 +236,10 @@ public class ThemeUtils implements Constants { ViewAccessor.setProgressBackgroundTintList(progressBar, tintList); ViewAccessor.setIndeterminateTintList(progressBar, tintList); } else if (view instanceof Switch) { - final ColorStateList tintList = ColorStateList.valueOf(tintColor); - final Switch switchView = (Switch) view; - DrawableCompat.setTintList(switchView.getThumbDrawable(), tintList); - DrawableCompat.setTintList(switchView.getTrackDrawable(), tintList); +// final ColorStateList tintList = ColorStateList.valueOf(tintColor); +// final Switch switchView = (Switch) view; +// DrawableCompat.setTintList(switchView.getThumbDrawable(), tintList); +// DrawableCompat.setTintList(switchView.getTrackDrawable(), tintList); } else if (view instanceof CompoundButton) { final ColorStateList tintList = ColorStateList.valueOf(tintColor); final CompoundButton compoundButton = (CompoundButton) view; diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/TwitterCardFragmentFactory.java b/twidere/src/main/java/org/mariotaku/twidere/util/TwitterCardFragmentFactory.java index 23934a244..3329f3152 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/TwitterCardFragmentFactory.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/TwitterCardFragmentFactory.java @@ -21,7 +21,7 @@ package org.mariotaku.twidere.util; import android.support.v4.app.Fragment; -import org.mariotaku.twidere.fragment.support.SupportBrowserFragment; +import org.mariotaku.twidere.fragment.support.CardBrowserFragment; import org.mariotaku.twidere.model.ParcelableStatus.ParcelableCardEntity; import org.mariotaku.twidere.model.ParcelableStatus.ParcelableCardEntity.ParcelableValueItem; @@ -42,6 +42,6 @@ public abstract class TwitterCardFragmentFactory { public static Fragment createGenericPlayerFragment(ParcelableCardEntity card) { final ParcelableValueItem player_url = ParcelableCardEntity.getValue(card, "player_url"); - return SupportBrowserFragment.show((String) player_url.value); + return CardBrowserFragment.show((String) player_url.value); } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 7a7da6ede..1285f6142 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -103,7 +103,6 @@ import android.widget.Toast; import org.apache.http.NameValuePair; import org.json.JSONException; -import org.mariotaku.gallery3d.ImageViewerGLActivity; import org.mariotaku.jsonserializer.JSONSerializer; import org.mariotaku.menucomponent.internal.menu.MenuUtils; import org.mariotaku.querybuilder.AllColumns; @@ -122,6 +121,7 @@ import org.mariotaku.twidere.BuildConfig; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.CameraCropActivity; +import org.mariotaku.twidere.activity.support.MediaViewerActivity; import org.mariotaku.twidere.adapter.iface.IBaseAdapter; import org.mariotaku.twidere.adapter.iface.IBaseCardAdapter; import org.mariotaku.twidere.app.TwidereApplication; @@ -190,6 +190,7 @@ import org.mariotaku.twidere.util.menu.TwidereMenuInfo; import org.mariotaku.twidere.util.net.TwidereHostResolverFactory; import org.mariotaku.twidere.util.net.TwidereHttpClientFactory; import org.mariotaku.twidere.view.ShapedImageView; +import org.mariotaku.twidere.view.ShapedImageView.ShapeStyle; import java.io.Closeable; import java.io.File; @@ -2036,6 +2037,14 @@ public final class Utils implements Constants, TwitterConstants { return url; } + @ShapeStyle + public static int getProfileImageStyle(Context context) { + final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + final String style = prefs.getString(KEY_PROFILE_IMAGE_STYLE, null); + return getProfileImageStyle(style); + } + + @ShapeStyle public static int getProfileImageStyle(String style) { if (VALUE_PROFILE_IMAGE_STYLE_SQUARE.equalsIgnoreCase(style)) { return ShapedImageView.SHAPE_RECTANGLE; @@ -2457,7 +2466,8 @@ public final class Utils implements Constants, TwitterConstants { cb.setGZIPEnabled(enableGzip); cb.setIgnoreSSLError(ignoreSslError); cb.setIncludeCards(true); - cb.setCardsPlatform("Android-5"); + cb.setCardsPlatform("Android-12"); +// cb.setModelVersion(7); if (enableProxy) { final String proxy_host = prefs.getString(KEY_PROXY_HOST, null); final int proxy_port = ParseUtils.parseInt(prefs.getString(KEY_PROXY_PORT, "-1")); @@ -2912,7 +2922,7 @@ public final class Utils implements Constants, TwitterConstants { final Intent intent = new Intent(INTENT_ACTION_VIEW_IMAGE); intent.setData(Uri.parse(uri)); intent.putExtra(EXTRA_ACCOUNT_ID, accountId); - intent.setClass(context, ImageViewerGLActivity.class); + intent.setClass(context, MediaViewerActivity.class); context.startActivity(intent); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java b/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java index 5d26d3f9d..d4753200c 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/ShapedImageView.java @@ -40,6 +40,7 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.os.Build; +import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; @@ -49,13 +50,24 @@ import android.widget.ImageView; import org.mariotaku.twidere.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * An ImageView class with a circle mask so that all images are drawn in a * circle instead of a square. */ public class ShapedImageView extends ImageView { + + @IntDef({SHAPE_CIRCLE, SHAPE_RECTANGLE}) + @Retention(RetentionPolicy.SOURCE) + public @interface ShapeStyle { + } + + @ShapeStyle public static final int SHAPE_CIRCLE = 0x1; + @ShapeStyle public static final int SHAPE_RECTANGLE = 0x2; private static final int SHADOW_START_COLOR = 0x37000000; @@ -120,7 +132,9 @@ public class ShapedImageView extends ImageView { } setBorderColor(a.getColor(R.styleable.ShapedImageView_sivBorderColor, Color.TRANSPARENT)); setBorderWidth(a.getDimensionPixelSize(R.styleable.ShapedImageView_sivBorderWidth, 0)); - setStyle(a.getInt(R.styleable.ShapedImageView_sivShape, SHAPE_RECTANGLE)); + @ShapeStyle + final int shapeStyle = a.getInt(R.styleable.ShapedImageView_sivShape, SHAPE_RECTANGLE); + setStyle(shapeStyle); setCornerRadius(a.getDimension(R.styleable.ShapedImageView_sivCornerRadius, 0)); setCornerRadiusRatio(a.getFraction(R.styleable.ShapedImageView_sivCornerRadiusRatio, 1, 1, -1)); @@ -203,11 +217,12 @@ public class ShapedImageView extends ImageView { } } + @ShapeStyle public int getStyle() { return mStyle; } - public void setStyle(int style) { + public void setStyle(@ShapeStyle final int style) { mStyle = style; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/TabPagerIndicator.java b/twidere/src/main/java/org/mariotaku/twidere/view/TabPagerIndicator.java index 542dad7db..1293a0ea1 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/TabPagerIndicator.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/TabPagerIndicator.java @@ -75,6 +75,8 @@ public class TabPagerIndicator extends RecyclerView implements PagerIndicator { final int dividerHorizontalPadding = a.getDimensionPixelSize(R.styleable.TabPagerIndicator_tabDividerHorizontalPadding, 0); mItemDecoration.setPadding(dividerHorizontalPadding, dividerVerticalPadding, dividerHorizontalPadding, dividerVerticalPadding); + mItemDecoration.setDecorationStart(0); + mItemDecoration.setDecorationEndOffset(1); a.recycle(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityListViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityListViewHolder.java deleted file mode 100644 index 92a7a09cb..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/ActivityListViewHolder.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 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 org.mariotaku.twidere.view.holder; - -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.mariotaku.twidere.R; - -public class ActivityListViewHolder extends StatusListViewHolder { - - public final ImageView activity_profile_image_1, activity_profile_image_2, activity_profile_image_3, - activity_profile_image_4, activity_profile_image_5; - public final ImageView[] activity_profile_images; - public final ViewGroup activity_profile_images_container; - public final TextView activity_profile_image_more_number; - public final View divider; - - public ActivityListViewHolder(final View view) { - super(view); - divider = findViewById(R.id.divider); - activity_profile_images_container = (ViewGroup) findViewById(R.id.activity_profile_image_container); - activity_profile_image_1 = (ImageView) findViewById(R.id.activity_profile_image_1); - activity_profile_image_2 = (ImageView) findViewById(R.id.activity_profile_image_2); - activity_profile_image_3 = (ImageView) findViewById(R.id.activity_profile_image_3); - activity_profile_image_4 = (ImageView) findViewById(R.id.activity_profile_image_4); - activity_profile_image_5 = (ImageView) findViewById(R.id.activity_profile_image_5); - activity_profile_image_more_number = (TextView) findViewById(R.id.activity_profile_image_more_number); - activity_profile_images = new ImageView[] { activity_profile_image_1, activity_profile_image_2, - activity_profile_image_3, activity_profile_image_4, activity_profile_image_5 }; - } - - @Override - public boolean setTextSize(final float text_size) { - if (super.setTextSize(text_size)) return false; - activity_profile_image_more_number.setTextSize(text_size); - return true; - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/CardViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/CardViewHolder.java index d33138c13..0074148db 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/CardViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/CardViewHolder.java @@ -24,7 +24,7 @@ import android.view.View; import org.mariotaku.twidere.R; import org.mariotaku.twidere.view.iface.IColorLabelView; -public class CardViewHolder extends ListViewHolder { +public class CardViewHolder extends ViewListHolder { public final IColorLabelView content; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageConversationViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageConversationViewHolder.java index 3d09263c0..82bc38c46 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageConversationViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageConversationViewHolder.java @@ -31,7 +31,7 @@ import android.widget.TextView; import org.mariotaku.messagebubbleview.library.MessageBubbleView; import org.mariotaku.twidere.R; -public class DirectMessageConversationViewHolder extends ListViewHolder { +public class DirectMessageConversationViewHolder extends ViewListHolder { public final TextView text, time; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageEntryViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageEntryViewHolder.java index 761422548..71a37d3ae 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageEntryViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/DirectMessageEntryViewHolder.java @@ -28,7 +28,7 @@ import org.mariotaku.twidere.R; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.view.ShortTimeView; -public class DirectMessageEntryViewHolder extends ListViewHolder { +public class DirectMessageEntryViewHolder extends ViewListHolder { public final ImageView profile_image; public final TextView name, screen_name, text; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/DraftViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/DraftViewHolder.java index fce938a8a..f007ce229 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/DraftViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/DraftViewHolder.java @@ -26,7 +26,7 @@ import android.widget.TextView; import org.mariotaku.twidere.R; import org.mariotaku.twidere.view.iface.IColorLabelView; -public class DraftViewHolder extends ListViewHolder { +public class DraftViewHolder extends ViewListHolder { public final IColorLabelView content; public final TextView text; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java index 20f6a55e7..bdde9a94d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java @@ -12,6 +12,7 @@ import android.widget.ImageView; import android.widget.TextView; import org.mariotaku.twidere.R; +import org.mariotaku.twidere.adapter.iface.ContentCardClickListener; import org.mariotaku.twidere.adapter.iface.IStatusesAdapter; import org.mariotaku.twidere.model.ParcelableMedia; import org.mariotaku.twidere.model.ParcelableStatus; @@ -48,6 +49,8 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick private final CardMediaContainer mediaPreviewContainer; private final TextView replyCountView, retweetCountView, favoriteCountView; + private StatusClickListener statusClickListener; + public StatusViewHolder(View itemView) { this(null, itemView); @@ -75,19 +78,27 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick } public void setOnClickListeners() { - setOnClickListeners(this); + setStatusClickListener(adapter); } - public void setOnClickListeners(OnClickListener listener) { - itemView.findViewById(R.id.item_content).setOnClickListener(listener); - itemView.findViewById(R.id.item_menu).setOnClickListener(listener); + public static interface StatusClickListener extends ContentCardClickListener { - itemView.setOnClickListener(listener); - profileImageView.setOnClickListener(listener); - mediaPreviewContainer.setOnClickListener(listener); - replyCountView.setOnClickListener(listener); - retweetCountView.setOnClickListener(listener); - favoriteCountView.setOnClickListener(listener); + void onUserProfileClick(StatusViewHolder holder, int position); + + void onStatusClick(StatusViewHolder holder, int position); + } + + public void setStatusClickListener(StatusClickListener listener) { + statusClickListener = listener; + itemView.findViewById(R.id.item_content).setOnClickListener(this); + itemView.findViewById(R.id.item_menu).setOnClickListener(this); + + itemView.setOnClickListener(this); + profileImageView.setOnClickListener(this); + mediaPreviewContainer.setOnClickListener(this); + replyCountView.setOnClickListener(this); + retweetCountView.setOnClickListener(this); + favoriteCountView.setOnClickListener(this); } public void setupViewOptions() { @@ -330,24 +341,25 @@ public class StatusViewHolder extends RecyclerView.ViewHolder implements OnClick @Override public void onClick(View v) { + if (statusClickListener == null) return; final int position = getPosition(); switch (v.getId()) { case R.id.item_content: { - adapter.onStatusClick(this, position); + statusClickListener.onStatusClick(this, position); break; } case R.id.item_menu: { - adapter.onItemMenuClick(this, position); + statusClickListener.onItemMenuClick(this, position); break; } case R.id.profile_image: { - adapter.onUserProfileClick(this, position); + statusClickListener.onUserProfileClick(this, position); break; } case R.id.reply_count: case R.id.retweet_count: case R.id.favorite_count: { - adapter.onItemActionClick(this, v.getId(), position); + statusClickListener.onItemActionClick(this, v.getId(), position); break; } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusListViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewListHolder.java similarity index 98% rename from twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusListViewHolder.java rename to twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewListHolder.java index d00ded6e1..e278b4c17 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusListViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewListHolder.java @@ -37,7 +37,7 @@ import static org.mariotaku.twidere.util.UserColorNameUtils.getDisplayName; import static org.mariotaku.twidere.util.Utils.getStatusTypeIconRes; import static org.mariotaku.twidere.util.Utils.getUserTypeIconRes; -public class StatusListViewHolder extends CardViewHolder { +public class StatusViewListHolder extends CardViewHolder { public final ImageView my_profile_image, profile_image; public final ImageView image_preview; @@ -57,7 +57,7 @@ public class StatusListViewHolder extends CardViewHolder { private boolean display_profile_image; private int card_highlight_option; - public StatusListViewHolder(final View view) { + public StatusViewListHolder(final View view) { super(view); final Context context = getContext(); profile_image = (ImageView) findViewById(R.id.profile_image); diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/TwoLineWithIconViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/TwoLineWithIconViewHolder.java index c35f63511..df05264ee 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/TwoLineWithIconViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/TwoLineWithIconViewHolder.java @@ -23,7 +23,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; -public class TwoLineWithIconViewHolder extends ListViewHolder { +public class TwoLineWithIconViewHolder extends ViewListHolder { public final ImageView icon; public final TextView text1, text2; diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListListViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewListHolder.java similarity index 51% rename from twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListListViewHolder.java rename to twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewListHolder.java index 9a64b7163..d048128b6 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListListViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewListHolder.java @@ -26,31 +26,33 @@ import android.widget.TextView; import org.mariotaku.twidere.R; import org.mariotaku.twidere.view.iface.IColorLabelView; -public class UserListListViewHolder extends ListViewHolder { +public class UserListViewListHolder extends ViewListHolder { public final IColorLabelView content; - public final ImageView profile_image; - public final TextView name, description, created_by, members_count, subscribers_count; - private float text_size; - public int position; + public final ImageView profile_image; + public final TextView name, description, created_by, members_count, subscribers_count; + private float text_size; + public int position; - public UserListListViewHolder(final View view) { - super(view); + public UserListViewListHolder(final View view) { + super(view); content = (IColorLabelView) view.findViewById(R.id.content); - profile_image = (ImageView) findViewById(R.id.profile_image); - name = (TextView) findViewById(R.id.name); - description = (TextView) findViewById(R.id.description); - created_by = (TextView) findViewById(R.id.created_by); - members_count = (TextView) findViewById(R.id.members_count); - subscribers_count = (TextView) findViewById(R.id.subscribers_count); - } + profile_image = (ImageView) findViewById(R.id.profile_image); + name = (TextView) findViewById(R.id.name); + description = (TextView) findViewById(R.id.description); + created_by = (TextView) findViewById(R.id.created_by); + members_count = (TextView) findViewById(R.id.members_count); + subscribers_count = (TextView) findViewById(R.id.subscribers_count); + } - public void setTextSize(final float text_size) { - if (this.text_size == text_size) return; - this.text_size = text_size; - description.setTextSize(text_size); - name.setTextSize(text_size * 1.05f); - created_by.setTextSize(text_size * 0.65f); - } + public void setTextSize(final float text_size) { + if (this.text_size == text_size) return; + this.text_size = text_size; + if (description != null) { + description.setTextSize(text_size); + } + name.setTextSize(text_size * 1.05f); + created_by.setTextSize(text_size * 0.65f); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserViewListHolder.java similarity index 96% rename from twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewHolder.java rename to twidere/src/main/java/org/mariotaku/twidere/view/holder/UserViewListHolder.java index d0c9eed94..26eafe34b 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserListViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/UserViewListHolder.java @@ -27,7 +27,7 @@ import android.widget.TextView; import org.mariotaku.twidere.R; import org.mariotaku.twidere.view.iface.IColorLabelView; -public class UserListViewHolder extends ListViewHolder { +public class UserViewListHolder extends ViewListHolder { public final IColorLabelView content; public final ImageView profile_image, profile_type; @@ -37,7 +37,7 @@ public class UserListViewHolder extends ListViewHolder { private float text_size; public int position; - public UserListViewHolder(final View view) { + public UserViewListHolder(final View view) { super(view); content = (IColorLabelView) view.findViewById(R.id.content); profile_image = (ImageView) findViewById(R.id.profile_image); diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/ListViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/ViewListHolder.java similarity index 93% rename from twidere/src/main/java/org/mariotaku/twidere/view/holder/ListViewHolder.java rename to twidere/src/main/java/org/mariotaku/twidere/view/holder/ViewListHolder.java index abe6fec60..bfbc2cff4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/ListViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/ViewListHolder.java @@ -24,11 +24,11 @@ import android.view.View; import org.mariotaku.twidere.Constants; -public class ListViewHolder implements Constants { +public class ViewListHolder implements Constants { public View view; - public ListViewHolder(final View view) { + public ViewListHolder(final View view) { if (view == null) throw new NullPointerException(); this.view = view; } diff --git a/twidere/src/main/java/twitter4j/Relationship.java b/twidere/src/main/java/twitter4j/Relationship.java index 0d67187d9..22e50da44 100644 --- a/twidere/src/main/java/twitter4j/Relationship.java +++ b/twidere/src/main/java/twitter4j/Relationship.java @@ -19,92 +19,94 @@ package twitter4j; /** * A data interface that has detailed information about a relationship between * two users - * + * * @author Perry Sakkaris - psakkaris at gmail.com * @see GET - * friendships/show | Twitter Developers + * friendships/show | Twitter Developers * @since Twitter4J 2.1.0 */ public interface Relationship extends TwitterResponse { - boolean canSourceDMTarget(); + boolean canSourceDMTarget(); - boolean canSourceMediaTagTarget(); + boolean canSourceMediaTagTarget(); - /** - * Returns the source user id - * - * @return the source user id - */ - long getSourceUserId(); + /** + * Returns the source user id + * + * @return the source user id + */ + long getSourceUserId(); - /** - * Returns the source user screen name - * - * @return returns the source user screen name - */ - String getSourceUserScreenName(); + /** + * Returns the source user screen name + * + * @return returns the source user screen name + */ + String getSourceUserScreenName(); - /** - * Returns the target user id - * - * @return target user id - */ - long getTargetUserId(); + /** + * Returns the target user id + * + * @return target user id + */ + long getTargetUserId(); - /** - * Returns the target user screen name - * - * @return the target user screen name - */ - String getTargetUserScreenName(); + /** + * Returns the target user screen name + * + * @return the target user screen name + */ + String getTargetUserScreenName(); - /** - * Returns if the source user is blocking the target user - * - * @return if the source is blocking the target - */ - boolean isSourceBlockingTarget(); + /** + * Returns if the source user is blocking the target user + * + * @return if the source is blocking the target + */ + boolean isSourceBlockingTarget(); - /** - * Checks if source user is being followed by target user - * - * @return true if source user is being followed by target user - */ - boolean isSourceFollowedByTarget(); + boolean isSourceBlockedByTarget(); - /** - * Checks if source user is following target user - * - * @return true if source user is following target user - */ - boolean isSourceFollowingTarget(); + /** + * Checks if source user is being followed by target user + * + * @return true if source user is being followed by target user + */ + boolean isSourceFollowedByTarget(); - boolean isSourceMarkedTargetAsSpam(); + /** + * Checks if source user is following target user + * + * @return true if source user is following target user + */ + boolean isSourceFollowingTarget(); - boolean isSourceMutingTarget(); + boolean isSourceMarkedTargetAsSpam(); - /** - * Checks if the source user has enabled notifications for updates of the - * target user - * - * @return true if source user enabled notifications for target user - */ - boolean isSourceNotificationsEnabled(); + boolean isSourceMutingTarget(); - /** - * Checks if target user is being followed by source user.
- * This method is equivalent to isSourceFollowingTarget(). - * - * @return true if target user is being followed by source user - */ - boolean isTargetFollowedBySource(); + /** + * Checks if the source user has enabled notifications for updates of the + * target user + * + * @return true if source user enabled notifications for target user + */ + boolean isSourceNotificationsEnabled(); - /** - * Checks if target user is following source user.
- * This method is equivalent to isSourceFollowedByTarget(). - * - * @return true if target user is following source user - */ - boolean isTargetFollowingSource(); + /** + * Checks if target user is being followed by source user.
+ * This method is equivalent to isSourceFollowingTarget(). + * + * @return true if target user is being followed by source user + */ + boolean isTargetFollowedBySource(); + + /** + * Checks if target user is following source user.
+ * This method is equivalent to isSourceFollowedByTarget(). + * + * @return true if target user is following source user + */ + boolean isTargetFollowingSource(); } diff --git a/twidere/src/main/java/twitter4j/SettingsUpdate.java b/twidere/src/main/java/twitter4j/SettingsUpdate.java new file mode 100644 index 000000000..d45a307d7 --- /dev/null +++ b/twidere/src/main/java/twitter4j/SettingsUpdate.java @@ -0,0 +1,84 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 twitter4j; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; + +import twitter4j.http.HttpParameter; + +/** + * Created by mariotaku on 15/1/6. + */ +public class SettingsUpdate implements Serializable { + + private final HashMap settingsMap = new HashMap<>(); + + public void set(String key, boolean value) { + settingsMap.put(key, new HttpParameter(key, value)); + } + + public void set(String key, int value) { + settingsMap.put(key, new HttpParameter(key, value)); + } + + public void set(String key, String value) { + settingsMap.put(key, new HttpParameter(key, value)); + } + + public void setTrendLocationWoeid(int woeid) { + set("trend_location_woeid", woeid); + } + + public void setSleepTimeEnabled(boolean enabled) { + set("sleep_time_enabled", enabled); + } + + public void setStartSleepTime(int startSleepTime) { + set("start_sleep_time", startSleepTime); + } + + public void setEndSleepTime(int endSleepTime) { + set("end_sleep_time", endSleepTime); + } + + public void setTimezone(String timezone) { + set("time_zone", timezone); + } + + public void setProtected(boolean userProtected) { + set("protected", userProtected); + } + + public void setLang(String lang) { + set("lang", lang); + } + + public void setScreenName(String screenName) { + set("screen_name", screenName); + } + + + void addToHttpParameterList(List parameterList) { + parameterList.addAll(settingsMap.values()); + } + +} diff --git a/twidere/src/main/java/twitter4j/Twitter.java b/twidere/src/main/java/twitter4j/Twitter.java index e475f7b7d..247fd57ab 100644 --- a/twidere/src/main/java/twitter4j/Twitter.java +++ b/twidere/src/main/java/twitter4j/Twitter.java @@ -23,16 +23,17 @@ import twitter4j.api.HelpResources; import twitter4j.api.ListsResources; import twitter4j.api.MediaResources; import twitter4j.api.PlacesGeoResources; +import twitter4j.api.PrivateActivityResources; +import twitter4j.api.PrivateDirectMessagesResources; +import twitter4j.api.PrivateFriendsFollowersResources; +import twitter4j.api.PrivateTimelinesResources; +import twitter4j.api.PrivateTweetResources; import twitter4j.api.SavedSearchesResources; import twitter4j.api.SearchResource; import twitter4j.api.SpamReportingResources; import twitter4j.api.TimelinesResources; import twitter4j.api.TrendsResources; import twitter4j.api.TweetResources; -import twitter4j.api.UndocumentedActivityResources; -import twitter4j.api.UndocumentedFriendsFollowersResources; -import twitter4j.api.UndocumentedTimelinesResources; -import twitter4j.api.UndocumentedTweetResources; import twitter4j.api.UsersResources; import twitter4j.auth.OAuthSupport; @@ -41,8 +42,8 @@ import twitter4j.auth.OAuthSupport; * @since Twitter4J 2.2.0 */ public interface Twitter extends OAuthSupport, TwitterConstants, TwitterBase, SearchResource, TimelinesResources, - TweetResources, UsersResources, ListsResources, DirectMessagesResources, FriendsFollowersResources, - FavoritesResources, SpamReportingResources, SavedSearchesResources, TrendsResources, PlacesGeoResources, - HelpResources, UndocumentedActivityResources, UndocumentedTweetResources, UndocumentedTimelinesResources, - UndocumentedFriendsFollowersResources, MediaResources { + TweetResources, UsersResources, ListsResources, DirectMessagesResources, FriendsFollowersResources, + FavoritesResources, SpamReportingResources, SavedSearchesResources, TrendsResources, PlacesGeoResources, + HelpResources, PrivateActivityResources, PrivateTweetResources, PrivateTimelinesResources, + PrivateFriendsFollowersResources, PrivateDirectMessagesResources, MediaResources { } diff --git a/twidere/src/main/java/twitter4j/TwitterConstants.java b/twidere/src/main/java/twitter4j/TwitterConstants.java index 7d1bd00bb..204b7a3a1 100644 --- a/twidere/src/main/java/twitter4j/TwitterConstants.java +++ b/twidere/src/main/java/twitter4j/TwitterConstants.java @@ -59,6 +59,8 @@ public interface TwitterConstants { public static final String ENDPOINT_DIRECT_MESSAGES_SENT = "direct_messages/sent.json"; public static final String ENDPOINT_DIRECT_MESSAGES_SHOW = "direct_messages/show.json"; + public static final String TEMPLATE_DM_CONVERSATION_DELETE = "dm/conversation/%d-%d/delete.json"; + public static final String ENDPOINT_FAVORITES_LIST = "favorites/list.json"; public static final String ENDPOINT_FAVORITES_CREATE = "favorites/create.json"; public static final String ENDPOINT_FAVORITES_DESTROY = "favorites/destroy.json"; diff --git a/twidere/src/main/java/twitter4j/TwitterImpl.java b/twidere/src/main/java/twitter4j/TwitterImpl.java index 06d27932a..0e1d0f419 100644 --- a/twidere/src/main/java/twitter4j/TwitterImpl.java +++ b/twidere/src/main/java/twitter4j/TwitterImpl.java @@ -299,6 +299,15 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { INCLUDE_ENTITIES)); } + @Override + public void destroyDirectMessagesConversation(long userId) throws TwitterException { + final String url = conf.getRestBaseURL() + + String.format(Locale.ROOT, TEMPLATE_DM_CONVERSATION_DELETE, id, userId); + final String signUrl = conf.getSigningRestBaseURL() + + String.format(Locale.ROOT, TEMPLATE_DM_CONVERSATION_DELETE, id, userId); + post(url, signUrl); + } + @Override public Status destroyFavorite(final long id) throws TwitterException { ensureAuthorizationEnabled(); @@ -1069,15 +1078,19 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public ResponseList getUserLists(final long listOwnerUserId) throws TwitterException { - return factory.createUserListList(get(conf.getRestBaseURL() + ENDPOINT_LISTS_LIST, conf.getSigningRestBaseURL() - + ENDPOINT_LISTS_LIST, new HttpParameter("user_id", listOwnerUserId))); + public ResponseList getUserLists(final long userId, final boolean reverse) throws TwitterException { + final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_LIST; + final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_LISTS_LIST; + return factory.createUserListList(get(url, signUrl, + new HttpParameter("user_id", userId), new HttpParameter("reverse", reverse))); } @Override - public ResponseList getUserLists(final String listOwnerScreenName) throws TwitterException { - return factory.createUserListList(get(conf.getRestBaseURL() + ENDPOINT_LISTS_LIST, conf.getSigningRestBaseURL() - + ENDPOINT_LISTS_LIST, new HttpParameter("screen_name", listOwnerScreenName))); + public ResponseList getUserLists(final String screenName, final boolean reverse) throws TwitterException { + final String url = conf.getRestBaseURL() + ENDPOINT_LISTS_LIST; + final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_LISTS_LIST; + return factory.createUserListList(get(url, signUrl, + new HttpParameter("screen_name", screenName), new HttpParameter("reverse", reverse))); } @Override @@ -1497,23 +1510,14 @@ final class TwitterImpl extends TwitterBaseImpl implements Twitter { } @Override - public AccountSettings updateAccountSettings(final Integer trend_locationWoeid, final Boolean sleep_timeEnabled, - final String start_sleepTime, final String end_sleepTime, final String time_zone, final String lang) - throws TwitterException { - + public AccountSettings updateAccountSettings(final SettingsUpdate settingsUpdate) throws TwitterException { ensureAuthorizationEnabled(); - - final List params = new ArrayList(6); - addParameterToList(params, "trend_location_woeid", trend_locationWoeid); - addParameterToList(params, "sleep_time_enabled", sleep_timeEnabled); - addParameterToList(params, "start_sleep_time", start_sleepTime); - addParameterToList(params, "end_sleep_time", end_sleepTime); - addParameterToList(params, "time_zone", time_zone); - addParameterToList(params, "lang", lang); + final List params = new ArrayList<>(); + settingsUpdate.addToHttpParameterList(params); params.add(INCLUDE_ENTITIES); - return factory.createAccountSettings(post(conf.getRestBaseURL() + ENDPOINT_ACCOUNT_SETTINGS, - conf.getSigningRestBaseURL() + ENDPOINT_ACCOUNT_SETTINGS, - params.toArray(new HttpParameter[params.size()]))); + final String url = conf.getRestBaseURL() + ENDPOINT_ACCOUNT_SETTINGS; + final String signUrl = conf.getSigningRestBaseURL() + ENDPOINT_ACCOUNT_SETTINGS; + return factory.createAccountSettings(post(url, signUrl, params.toArray(new HttpParameter[params.size()]))); } @Override diff --git a/twidere/src/main/java/twitter4j/api/ListsResources.java b/twidere/src/main/java/twitter4j/api/ListsResources.java index ec96d5123..d4ab33ba8 100644 --- a/twidere/src/main/java/twitter4j/api/ListsResources.java +++ b/twidere/src/main/java/twitter4j/api/ListsResources.java @@ -309,14 +309,14 @@ public interface ListsResources { * returned.
* This method calls http://api.twitter.com/1.1/lists.json * - * @param listOwnerUserId The id of the list owner + * @param userId The id of the list owner * @return the list of lists * @throws TwitterException when Twitter service or network is unavailable * @see GET lists | * Twitter Developers * @since Twitter4J 2.2.3 */ - ResponseList getUserLists(long listOwnerUserId) throws TwitterException; + ResponseList getUserLists(long userId, boolean reverse) throws TwitterException; /** * List the lists of the specified user. Private lists will be included if @@ -324,14 +324,14 @@ public interface ListsResources { * returned.
* This method calls http://api.twitter.com/1.1/lists.json * - * @param listOwnerScreenName The screen name of the list owner + * @param screenName The screen name of the user * @return the list of lists * @throws TwitterException when Twitter service or network is unavailable * @see GET lists | * Twitter Developers * @since Twitter4J 2.1.0 */ - ResponseList getUserLists(String listOwnerScreenName) throws TwitterException; + ResponseList getUserLists(String screenName, boolean reverse) throws TwitterException; /** * Show tweet timeline for members of the specified list.
diff --git a/twidere/src/main/java/twitter4j/api/UndocumentedActivityResources.java b/twidere/src/main/java/twitter4j/api/PrivateActivityResources.java similarity index 86% rename from twidere/src/main/java/twitter4j/api/UndocumentedActivityResources.java rename to twidere/src/main/java/twitter4j/api/PrivateActivityResources.java index 65b665569..09e96c6f5 100644 --- a/twidere/src/main/java/twitter4j/api/UndocumentedActivityResources.java +++ b/twidere/src/main/java/twitter4j/api/PrivateActivityResources.java @@ -5,7 +5,7 @@ import twitter4j.Paging; import twitter4j.ResponseList; import twitter4j.TwitterException; -public interface UndocumentedActivityResources extends UndocumentedResources { +public interface PrivateActivityResources extends PrivateResources { public ResponseList getActivitiesAboutMe() throws TwitterException; public ResponseList getActivitiesAboutMe(Paging paging) throws TwitterException; diff --git a/twidere/src/main/java/twitter4j/api/PrivateDirectMessagesResources.java b/twidere/src/main/java/twitter4j/api/PrivateDirectMessagesResources.java new file mode 100644 index 000000000..f092e4598 --- /dev/null +++ b/twidere/src/main/java/twitter4j/api/PrivateDirectMessagesResources.java @@ -0,0 +1,31 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 twitter4j.api; + +import twitter4j.TwitterException; + +/** + * Created by mariotaku on 15/1/6. + */ +public interface PrivateDirectMessagesResources extends PrivateResources { + + void destroyDirectMessagesConversation(long userId) throws TwitterException; + +} diff --git a/twidere/src/main/java/twitter4j/api/UndocumentedFriendsFollowersResources.java b/twidere/src/main/java/twitter4j/api/PrivateFriendsFollowersResources.java similarity index 80% rename from twidere/src/main/java/twitter4j/api/UndocumentedFriendsFollowersResources.java rename to twidere/src/main/java/twitter4j/api/PrivateFriendsFollowersResources.java index a94719875..7d2bed674 100644 --- a/twidere/src/main/java/twitter4j/api/UndocumentedFriendsFollowersResources.java +++ b/twidere/src/main/java/twitter4j/api/PrivateFriendsFollowersResources.java @@ -3,7 +3,7 @@ package twitter4j.api; import twitter4j.TwitterException; import twitter4j.User; -public interface UndocumentedFriendsFollowersResources extends UndocumentedResources { +public interface PrivateFriendsFollowersResources extends PrivateResources { public User acceptFriendship(long userId) throws TwitterException; diff --git a/twidere/src/main/java/twitter4j/api/PrivateResources.java b/twidere/src/main/java/twitter4j/api/PrivateResources.java new file mode 100644 index 000000000..6dcedd727 --- /dev/null +++ b/twidere/src/main/java/twitter4j/api/PrivateResources.java @@ -0,0 +1,5 @@ +package twitter4j.api; + +public interface PrivateResources { + +} diff --git a/twidere/src/main/java/twitter4j/api/UndocumentedTimelinesResources.java b/twidere/src/main/java/twitter4j/api/PrivateTimelinesResources.java similarity index 88% rename from twidere/src/main/java/twitter4j/api/UndocumentedTimelinesResources.java rename to twidere/src/main/java/twitter4j/api/PrivateTimelinesResources.java index 4f3c3d183..89ceca743 100644 --- a/twidere/src/main/java/twitter4j/api/UndocumentedTimelinesResources.java +++ b/twidere/src/main/java/twitter4j/api/PrivateTimelinesResources.java @@ -5,7 +5,7 @@ import twitter4j.ResponseList; import twitter4j.Status; import twitter4j.TwitterException; -public interface UndocumentedTimelinesResources extends UndocumentedResources { +public interface PrivateTimelinesResources extends PrivateResources { ResponseList getMediaTimeline() throws TwitterException; diff --git a/twidere/src/main/java/twitter4j/api/UndocumentedTweetResources.java b/twidere/src/main/java/twitter4j/api/PrivateTweetResources.java similarity index 90% rename from twidere/src/main/java/twitter4j/api/UndocumentedTweetResources.java rename to twidere/src/main/java/twitter4j/api/PrivateTweetResources.java index b3ac5cc59..1092cc4ce 100644 --- a/twidere/src/main/java/twitter4j/api/UndocumentedTweetResources.java +++ b/twidere/src/main/java/twitter4j/api/PrivateTweetResources.java @@ -7,7 +7,7 @@ import twitter4j.StatusActivitySummary; import twitter4j.TranslationResult; import twitter4j.TwitterException; -public interface UndocumentedTweetResources extends UndocumentedResources { +public interface PrivateTweetResources extends PrivateResources { StatusActivitySummary getStatusActivitySummary(long statusId) throws TwitterException; diff --git a/twidere/src/main/java/twitter4j/api/UndocumentedResources.java b/twidere/src/main/java/twitter4j/api/UndocumentedResources.java deleted file mode 100644 index e62194f22..000000000 --- a/twidere/src/main/java/twitter4j/api/UndocumentedResources.java +++ /dev/null @@ -1,5 +0,0 @@ -package twitter4j.api; - -public interface UndocumentedResources { - -} diff --git a/twidere/src/main/java/twitter4j/api/UsersResources.java b/twidere/src/main/java/twitter4j/api/UsersResources.java index 207c35e0e..f98051553 100644 --- a/twidere/src/main/java/twitter4j/api/UsersResources.java +++ b/twidere/src/main/java/twitter4j/api/UsersResources.java @@ -16,573 +16,562 @@ package twitter4j.api; +import java.io.File; +import java.io.InputStream; + import twitter4j.AccountSettings; import twitter4j.Category; import twitter4j.CursorPaging; import twitter4j.IDs; import twitter4j.PagableResponseList; import twitter4j.ResponseList; +import twitter4j.SettingsUpdate; import twitter4j.TwitterException; import twitter4j.User; -import java.io.File; -import java.io.InputStream; - /** * @author Joern Huxhorn - jhuxhorn at googlemail.com */ public interface UsersResources { - /** - * Blocks the user specified in the ID parameter as the authenticating user. - * Returns the blocked user in the requested format when successful.
- * This method calls http://api.twitter.com/1.1/blocks/create/[id].json - * - * @param userId the ID of the user to block - * @return the blocked user - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * blocks/create | Twitter Developers - * @since Twitter4J 2.1.0 - */ - User createBlock(long userId) throws TwitterException; + /** + * Blocks the user specified in the ID parameter as the authenticating user. + * Returns the blocked user in the requested format when successful.
+ * This method calls http://api.twitter.com/1.1/blocks/create/[id].json + * + * @param userId the ID of the user to block + * @return the blocked user + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * blocks/create | Twitter Developers + * @since Twitter4J 2.1.0 + */ + User createBlock(long userId) throws TwitterException; - /** - * Blocks the user specified in the ID parameter as the authenticating user. - * Returns the blocked user in the requested format when successful.
- * This method calls http://api.twitter.com/1.1/blocks/create/[id].json - * - * @param screenName the screen_name of the user to block - * @return the blocked user - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * blocks/create | Twitter Developers - * @since Twitter4J 2.0.1 - */ - User createBlock(String screenName) throws TwitterException; + /** + * Blocks the user specified in the ID parameter as the authenticating user. + * Returns the blocked user in the requested format when successful.
+ * This method calls http://api.twitter.com/1.1/blocks/create/[id].json + * + * @param screenName the screen_name of the user to block + * @return the blocked user + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * blocks/create | Twitter Developers + * @since Twitter4J 2.0.1 + */ + User createBlock(String screenName) throws TwitterException; - User createMute(long userId) throws TwitterException; + User createMute(long userId) throws TwitterException; - User createMute(String screenName) throws TwitterException; + User createMute(String screenName) throws TwitterException; - /** - * Un-blocks the user specified in the ID parameter as the authenticating - * user. Returns the un-blocked user in the requested format when - * successful.
- * This method calls http://api.twitter.com/1.1/blocks/destroy/[id].json - * - * @param userId the ID of the user to block - * @return the unblocked user - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * blocks/destroy | Twitter Developers - * @since Twitter4J 2.0.1 - */ - User destroyBlock(long userId) throws TwitterException; + /** + * Un-blocks the user specified in the ID parameter as the authenticating + * user. Returns the un-blocked user in the requested format when + * successful.
+ * This method calls http://api.twitter.com/1.1/blocks/destroy/[id].json + * + * @param userId the ID of the user to block + * @return the unblocked user + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * blocks/destroy | Twitter Developers + * @since Twitter4J 2.0.1 + */ + User destroyBlock(long userId) throws TwitterException; - /** - * Un-blocks the user specified in the ID parameter as the authenticating - * user. Returns the un-blocked user in the requested format when - * successful.
- * This method calls http://api.twitter.com/1.1/blocks/destroy/[id].json - * - * @param screenName the screen name of the user to block - * @return the unblocked user - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * blocks/destroy | Twitter Developers - * @since Twitter4J 2.0.1 - */ - User destroyBlock(String screenName) throws TwitterException; + /** + * Un-blocks the user specified in the ID parameter as the authenticating + * user. Returns the un-blocked user in the requested format when + * successful.
+ * This method calls http://api.twitter.com/1.1/blocks/destroy/[id].json + * + * @param screenName the screen name of the user to block + * @return the unblocked user + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * blocks/destroy | Twitter Developers + * @since Twitter4J 2.0.1 + */ + User destroyBlock(String screenName) throws TwitterException; - User destroyMute(long userId) throws TwitterException; + User destroyMute(long userId) throws TwitterException; - User destroyMute(String screenName) throws TwitterException; + User destroyMute(String screenName) throws TwitterException; - /** - * Returns the current trend, geo, language, timezone and sleep time - * information for the authenticating user.
- * This method has not been finalized and the interface is subject to change - * in incompatible ways.
- * This method calls http://api.twitter.com/1.1/account/settings.json - * - * @return the current trend, geo and sleep time information for the - * authenticating user. - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * account/settings | Twitter Developers - * @since Twitter4J 2.1.9 - */ - AccountSettings getAccountSettings() throws TwitterException; + /** + * Returns the current trend, geo, language, timezone and sleep time + * information for the authenticating user.
+ * This method has not been finalized and the interface is subject to change + * in incompatible ways.
+ * This method calls http://api.twitter.com/1.1/account/settings.json + * + * @return the current trend, geo and sleep time information for the + * authenticating user. + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * account/settings | Twitter Developers + * @since Twitter4J 2.1.9 + */ + AccountSettings getAccountSettings() throws TwitterException; - /** - * Returns an array of numeric user ids the authenticating user is blocking.
- * This method calls http://api.twitter.com/1.1/blocks/blocks/ids - * - * @return Returns an array of numeric user ids the authenticating user is - * blocking. - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * blocks/ids | Twitter Developers - * @since Twitter4J 2.0.4 - */ - IDs getBlocksIDs() throws TwitterException; + /** + * Returns an array of numeric user ids the authenticating user is blocking.
+ * This method calls http://api.twitter.com/1.1/blocks/blocks/ids + * + * @return Returns an array of numeric user ids the authenticating user is + * blocking. + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * blocks/ids | Twitter Developers + * @since Twitter4J 2.0.4 + */ + IDs getBlocksIDs() throws TwitterException; - IDs getBlocksIDs(CursorPaging paging) throws TwitterException; + IDs getBlocksIDs(CursorPaging paging) throws TwitterException; - /** - * Returns a list of user objects that the authenticating user is blocking.
- * This method calls http://api.twitter.com/1.1/blocks/blocking.json - * - * @return a list of user objects that the authenticating user - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * blocks/blocking | Twitter Developers - * @since Twitter4J 2.0.4 - */ - PagableResponseList getBlocksList() throws TwitterException; + /** + * Returns a list of user objects that the authenticating user is blocking.
+ * This method calls http://api.twitter.com/1.1/blocks/blocking.json + * + * @return a list of user objects that the authenticating user + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * blocks/blocking | Twitter Developers + * @since Twitter4J 2.0.4 + */ + PagableResponseList getBlocksList() throws TwitterException; - PagableResponseList getBlocksList(CursorPaging paging) throws TwitterException; + PagableResponseList getBlocksList(CursorPaging paging) throws TwitterException; - /** - * Access the users in a given category of the Twitter suggested user list - * and return their most recent status if they are not a protected user.
- * This method has not been finalized and the interface is subject to change - * in incompatible ways.
- * This method calls - * http://api.twitter.com/1.1/users/suggestions/:slug/members.json - * - * @param categorySlug slug - * @return list of suggested users - * @throws TwitterException when Twitter service or network is unavailable - * @see #newtwitter - * and the API - Twitter API Announcements | Google Group - * @since Twitter4J 2.1.9 - */ - ResponseList getMemberSuggestions(String categorySlug) throws TwitterException; + /** + * Access the users in a given category of the Twitter suggested user list + * and return their most recent status if they are not a protected user.
+ * This method has not been finalized and the interface is subject to change + * in incompatible ways.
+ * This method calls + * http://api.twitter.com/1.1/users/suggestions/:slug/members.json + * + * @param categorySlug slug + * @return list of suggested users + * @throws TwitterException when Twitter service or network is unavailable + * @see #newtwitter + * and the API - Twitter API Announcements | Google Group + * @since Twitter4J 2.1.9 + */ + ResponseList getMemberSuggestions(String categorySlug) throws TwitterException; - IDs getMutesUsersIDs() throws TwitterException; + IDs getMutesUsersIDs() throws TwitterException; - IDs getMutesUsersIDs(CursorPaging paging) throws TwitterException; + IDs getMutesUsersIDs(CursorPaging paging) throws TwitterException; - PagableResponseList getMutesUsersList() throws TwitterException; + PagableResponseList getMutesUsersList() throws TwitterException; - PagableResponseList getMutesUsersList(CursorPaging paging) throws TwitterException; + PagableResponseList getMutesUsersList(CursorPaging paging) throws TwitterException; - /** - * Access to Twitter's suggested user list. This returns the list of - * suggested user categories. The category can be used in the - * users/suggestions/category endpoint to get the users in that category.
- * This method calls http://api.twitter.com/1.1/users/suggestions/:slug.json - * - * @return list of suggested user categories. - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/suggestions/:slug | Twitter Developers - * @since Twitter4J 2.1.1 - */ - ResponseList getSuggestedUserCategories() throws TwitterException; + /** + * Access to Twitter's suggested user list. This returns the list of + * suggested user categories. The category can be used in the + * users/suggestions/category endpoint to get the users in that category.
+ * This method calls http://api.twitter.com/1.1/users/suggestions/:slug.json + * + * @return list of suggested user categories. + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/suggestions/:slug | Twitter Developers + * @since Twitter4J 2.1.1 + */ + ResponseList getSuggestedUserCategories() throws TwitterException; - /** - * Access the users in a given category of the Twitter suggested user list.
- * It is recommended that end clients cache this data for no more than one - * hour.
- * This method calls http://api.twitter.com/1.1/users/suggestions/:slug.json - * - * @param categorySlug slug - * @return list of suggested users - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/suggestions/slug | Twitter Developers - * @since Twitter4J 2.1.1 - */ - ResponseList getUserSuggestions(String categorySlug) throws TwitterException; + /** + * Access the users in a given category of the Twitter suggested user list.
+ * It is recommended that end clients cache this data for no more than one + * hour.
+ * This method calls http://api.twitter.com/1.1/users/suggestions/:slug.json + * + * @param categorySlug slug + * @return list of suggested users + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/suggestions/slug | Twitter Developers + * @since Twitter4J 2.1.1 + */ + ResponseList getUserSuggestions(String categorySlug) throws TwitterException; - /** - * Return up to 100 users worth of extended information, specified by either - * ID, screen name, or combination of the two. The author's most recent - * status (if the authenticating user has permission) will be returned - * inline.
- * This method calls http://api.twitter.com/1.1/users/lookup.json - * - * @param ids Specifies the screen names of the users to return. - * @return users - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/lookup | Twitter Developers - * @since Twitter4J 2.1.1 - */ - ResponseList lookupUsers(long[] ids) throws TwitterException; + /** + * Return up to 100 users worth of extended information, specified by either + * ID, screen name, or combination of the two. The author's most recent + * status (if the authenticating user has permission) will be returned + * inline.
+ * This method calls http://api.twitter.com/1.1/users/lookup.json + * + * @param ids Specifies the screen names of the users to return. + * @return users + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/lookup | Twitter Developers + * @since Twitter4J 2.1.1 + */ + ResponseList lookupUsers(long[] ids) throws TwitterException; - /** - * Return up to 100 users worth of extended information, specified by either - * ID, screen name, or combination of the two. The author's most recent - * status (if the authenticating user has permission) will be returned - * inline.
- * This method calls http://api.twitter.com/1.1/users/lookup.json - * - * @param screenNames Specifies the screen names of the users to return. - * @return users - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/lookup | Twitter Developers - * @since Twitter4J 2.1.1 - */ - ResponseList lookupUsers(String[] screenNames) throws TwitterException; + /** + * Return up to 100 users worth of extended information, specified by either + * ID, screen name, or combination of the two. The author's most recent + * status (if the authenticating user has permission) will be returned + * inline.
+ * This method calls http://api.twitter.com/1.1/users/lookup.json + * + * @param screenNames Specifies the screen names of the users to return. + * @return users + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/lookup | Twitter Developers + * @since Twitter4J 2.1.1 + */ + ResponseList lookupUsers(String[] screenNames) throws TwitterException; - void removeProfileBannerImage() throws TwitterException; + void removeProfileBannerImage() throws TwitterException; - /** - * Run a search for users similar to the Find People button on Twitter.com; - * the same results returned by people search on Twitter.com will be - * returned by using this API.
- * Usage note: It is only possible to retrieve the first 1000 matches from - * this API.
- * This method calls http://api.twitter.com/1.1/users/search.json - * - * @param query The query to run against people search. - * @param page Specifies the page of results to retrieve. Number of statuses - * per page is fixed to 20. - * @return the list of Users matches the provided - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/search | Twitter Developers - */ - ResponseList searchUsers(String query, int page) throws TwitterException; + /** + * Run a search for users similar to the Find People button on Twitter.com; + * the same results returned by people search on Twitter.com will be + * returned by using this API.
+ * Usage note: It is only possible to retrieve the first 1000 matches from + * this API.
+ * This method calls http://api.twitter.com/1.1/users/search.json + * + * @param query The query to run against people search. + * @param page Specifies the page of results to retrieve. Number of statuses + * per page is fixed to 20. + * @return the list of Users matches the provided + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/search | Twitter Developers + */ + ResponseList searchUsers(String query, int page) throws TwitterException; - /** - * Returns extended information of a given user, specified by ID or screen - * name as per the required id parameter. The author's most recent status - * will be returned inline.
- * This method calls http://api.twitter.com/1.1/users/show.json - * - * @param userId the ID of the user for whom to request the detail - * @return users - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/show | Twitter Developers - * @since Twitter4J 2.1.0 - */ - User showUser(long userId) throws TwitterException; + /** + * Returns extended information of a given user, specified by ID or screen + * name as per the required id parameter. The author's most recent status + * will be returned inline.
+ * This method calls http://api.twitter.com/1.1/users/show.json + * + * @param userId the ID of the user for whom to request the detail + * @return users + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/show | Twitter Developers + * @since Twitter4J 2.1.0 + */ + User showUser(long userId) throws TwitterException; - /** - * Returns extended information of a given user, specified by ID or screen - * name as per the required id parameter. The author's most recent status - * will be returned inline.
- * This method calls http://api.twitter.com/1.1/users/show.json - * - * @param screenName the screen name of the user for whom to request the - * detail - * @return User - * @throws TwitterException when Twitter service or network is unavailable - * @see GET - * users/show | Twitter Developers - */ - User showUser(String screenName) throws TwitterException; + /** + * Returns extended information of a given user, specified by ID or screen + * name as per the required id parameter. The author's most recent status + * will be returned inline.
+ * This method calls http://api.twitter.com/1.1/users/show.json + * + * @param screenName the screen name of the user for whom to request the + * detail + * @return User + * @throws TwitterException when Twitter service or network is unavailable + * @see GET + * users/show | Twitter Developers + */ + User showUser(String screenName) throws TwitterException; - /** - * Updates the current trend, geo, language, timezone and sleep time - * information for the authenticating user.
- * This method has not been finalized and the interface is subject to change - * in incompatible ways.
- * This method calls http://api.twitter.com/1.1/account/settings.json - * - * @param trendLocationWoeid Optional. The Yahoo! Where On Earth ID to use - * as the user's default trend location. - * @param sleepTimeEnabled Optional. Whether sleep time is enabled for the - * user - * @param startSleepTime Optional. The hour that sleep time should begin if - * it is enabled. - * @param endSleepTime Optional. The hour that sleep time should end if it - * is enabled. - * @param timeZone Optional. The timezone dates and times should be - * displayed in for the user. - * @param lang Optional. The language which Twitter should render in for - * this user. (two letter ISO 639-1) - * @return the current trend, geo and sleep time information for the - * authenticating user. - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * account/settings | Twitter Developers - * @since Twitter4J 2.2.4 - */ - AccountSettings updateAccountSettings(Integer trendLocationWoeid, Boolean sleepTimeEnabled, String startSleepTime, - String endSleepTime, String timeZone, String lang) throws TwitterException; + /** + * Updates the current trend, geo, language, timezone and sleep time + * information for the authenticating user.
+ * This method has not been finalized and the interface is subject to change + * in incompatible ways.
+ * This method calls http://api.twitter.com/1.1/account/settings.json + * + * @param settingsUpdate Settings to be updated + * @return the current trend, geo and sleep time information for the + * authenticating user. + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * account/settings | Twitter Developers + * @since Twitter4J 2.2.4 + */ + AccountSettings updateAccountSettings(SettingsUpdate settingsUpdate) throws TwitterException; - /** - * Sets values that users are able to set under the "Account" tab of their - * settings page. Only the parameters specified(non-null) will be updated.
- * This method calls http://api.twitter.com/1.1/account/update_profile.json - * - * @param name Optional. Maximum of 20 characters. - * @param url Optional. Maximum of 100 characters. Will be prepended with - * "http://" if not present. - * @param location Optional. Maximum of 30 characters. The contents are not - * normalized or geocoded in any way. - * @param description Optional. Maximum of 160 characters. - * @return the updated user - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * account/update_profile | Twitter Developers - * @since Twitter4J 2.1.8 - */ - User updateProfile(String name, String url, String location, String description) throws TwitterException; + /** + * Sets values that users are able to set under the "Account" tab of their + * settings page. Only the parameters specified(non-null) will be updated.
+ * This method calls http://api.twitter.com/1.1/account/update_profile.json + * + * @param name Optional. Maximum of 20 characters. + * @param url Optional. Maximum of 100 characters. Will be prepended with + * "http://" if not present. + * @param location Optional. Maximum of 30 characters. The contents are not + * normalized or geocoded in any way. + * @param description Optional. Maximum of 160 characters. + * @return the updated user + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * account/update_profile | Twitter Developers + * @since Twitter4J 2.1.8 + */ + User updateProfile(String name, String url, String location, String description) throws TwitterException; - /** - * Updates the authenticating user's profile background image.
- * This method calls - * http://api.twitter.com/1.1/account/update_profile_background_image.json - * - * @param image Must be a valid GIF, JPG, or PNG image of less than 800 - * kilobytes in size. Images with width larger than 2048 pixels - * will be forceably scaled down. - * @param tile If set to true the background image will be displayed tiled. - * The image will not be tiled otherwise. - * @return the updated user - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - * @see POST - * account/update_profile_background_image | Twitter Developers - * @since Twitter4J 2.1.0 - */ - User updateProfileBackgroundImage(File image, boolean tile) throws TwitterException; + /** + * Updates the authenticating user's profile background image.
+ * This method calls + * http://api.twitter.com/1.1/account/update_profile_background_image.json + * + * @param image Must be a valid GIF, JPG, or PNG image of less than 800 + * kilobytes in size. Images with width larger than 2048 pixels + * will be forceably scaled down. + * @param tile If set to true the background image will be displayed tiled. + * The image will not be tiled otherwise. + * @return the updated user + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + * @see POST + * account/update_profile_background_image | Twitter Developers + * @since Twitter4J 2.1.0 + */ + User updateProfileBackgroundImage(File image, boolean tile) throws TwitterException; - /** - * Updates the authenticating user's profile background image.
- * This method calls - * http://api.twitter.com/1.1/account/update_profile_background_image.json - * - * @param image Must be a valid GIF, JPG, or PNG image of less than 800 - * kilobytes in size. Images with width larger than 2048 pixels - * will be forceably scaled down. - * @param tile If set to true the background image will be displayed tiled. - * The image will not be tiled otherwise. - * @return the updated user - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - * @see POST - * account/update_profile_background_image | Twitter Developers - * @since Twitter4J 2.1.11 - */ - User updateProfileBackgroundImage(InputStream image, boolean tile) throws TwitterException; + /** + * Updates the authenticating user's profile background image.
+ * This method calls + * http://api.twitter.com/1.1/account/update_profile_background_image.json + * + * @param image Must be a valid GIF, JPG, or PNG image of less than 800 + * kilobytes in size. Images with width larger than 2048 pixels + * will be forceably scaled down. + * @param tile If set to true the background image will be displayed tiled. + * The image will not be tiled otherwise. + * @return the updated user + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + * @see POST + * account/update_profile_background_image | Twitter Developers + * @since Twitter4J 2.1.11 + */ + User updateProfileBackgroundImage(InputStream image, boolean tile) throws TwitterException; - /** - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed - *
422The image could not be resized or it too large
- * - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - */ - void updateProfileBannerImage(File banner) throws TwitterException; + /** + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed + *
422The image could not be resized or it too large
+ * + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + */ + void updateProfileBannerImage(File banner) throws TwitterException; - /** - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed - *
422The image could not be resized or it too large
- * - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - */ - void updateProfileBannerImage(File banner, int width, int height, int offsetLeft, int offsetTop) - throws TwitterException; + /** + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed + *
422The image could not be resized or it too large
+ * + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + */ + void updateProfileBannerImage(File banner, int width, int height, int offsetLeft, int offsetTop) + throws TwitterException; - /** - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed - *
422The image could not be resized or it too large
- * - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - */ - void updateProfileBannerImage(InputStream banner) throws TwitterException; + /** + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed + *
422The image could not be resized or it too large
+ * + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + */ + void updateProfileBannerImage(InputStream banner) throws TwitterException; - /** - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed - *
422The image could not be resized or it too large
- * - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - */ - void updateProfileBannerImage(InputStream banner, int width, int height, int offsetLeft, int offsetTop) - throws TwitterException; + /** + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Code(s)Meaning
200,201,202Profile banner image successfully uploaded
400Either an image was not provided or the image data could be processed + *
422The image could not be resized or it too large
+ * + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + */ + void updateProfileBannerImage(InputStream banner, int width, int height, int offsetLeft, int offsetTop) + throws TwitterException; - /** - * Sets one or more hex values that control the color scheme of the - * authenticating user's profile page on twitter.com. Each parameter's value - * must be a valid hexidecimal value, and may be either three or six - * characters (ex: #fff or #ffffff).
- * This method calls - * http://api.twitter.com/1.1/account/update_profile_colors.json - * - * @param profileBackgroundColor optional, can be null - * @param profileTextColor optional, can be null - * @param profileLinkColor optional, can be null - * @param profileSidebarFillColor optional, can be null - * @param profileSidebarBorderColor optional, can be null - * @return the updated user - * @throws TwitterException when Twitter service or network is unavailable - * @see POST - * account/update_profile_colors | Twitter Developers - * @since Twitter4J 2.0.0 - */ - User updateProfileColors(String profileBackgroundColor, String profileTextColor, String profileLinkColor, - String profileSidebarFillColor, String profileSidebarBorderColor) throws TwitterException; + /** + * Sets one or more hex values that control the color scheme of the + * authenticating user's profile page on twitter.com. Each parameter's value + * must be a valid hexidecimal value, and may be either three or six + * characters (ex: #fff or #ffffff).
+ * This method calls + * http://api.twitter.com/1.1/account/update_profile_colors.json + * + * @param profileBackgroundColor optional, can be null + * @param profileTextColor optional, can be null + * @param profileLinkColor optional, can be null + * @param profileSidebarFillColor optional, can be null + * @param profileSidebarBorderColor optional, can be null + * @return the updated user + * @throws TwitterException when Twitter service or network is unavailable + * @see POST + * account/update_profile_colors | Twitter Developers + * @since Twitter4J 2.0.0 + */ + User updateProfileColors(String profileBackgroundColor, String profileTextColor, String profileLinkColor, + String profileSidebarFillColor, String profileSidebarBorderColor) throws TwitterException; - /** - * Updates the authenticating user's profile image.
- * This method calls - * http://api.twitter.com/1.1/account/update_profile_image.json - * - * @param image Must be a valid GIF, JPG, or PNG image of less than 700 - * kilobytes in size. Images with width larger than 500 pixels - * will be scaled down. - * @return the updated user - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - * @see POST - * account/update_profile_image | Twitter Developers - * @since Twitter4J 2.1.0 - */ - User updateProfileImage(File image) throws TwitterException; + /** + * Updates the authenticating user's profile image.
+ * This method calls + * http://api.twitter.com/1.1/account/update_profile_image.json + * + * @param image Must be a valid GIF, JPG, or PNG image of less than 700 + * kilobytes in size. Images with width larger than 500 pixels + * will be scaled down. + * @return the updated user + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + * @see POST + * account/update_profile_image | Twitter Developers + * @since Twitter4J 2.1.0 + */ + User updateProfileImage(File image) throws TwitterException; - /** - * Updates the authenticating user's profile image.
- * This method calls - * http://api.twitter.com/1.1/account/update_profile_image.json - * - * @param image Must be a valid GIF, JPG, or PNG image of less than 700 - * kilobytes in size. Images with width larger than 500 pixels - * will be scaled down. - * @return the updated user - * @throws TwitterException when Twitter service or network is unavailable, - * or when the specified file is not found - * (FileNotFoundException will be nested), or when the specified - * file object in not representing a file (IOException will be - * nested) - * @see POST - * account/update_profile_image | Twitter Developers - * @since Twitter4J 2.1.11 - */ - User updateProfileImage(InputStream image) throws TwitterException; + /** + * Updates the authenticating user's profile image.
+ * This method calls + * http://api.twitter.com/1.1/account/update_profile_image.json + * + * @param image Must be a valid GIF, JPG, or PNG image of less than 700 + * kilobytes in size. Images with width larger than 500 pixels + * will be scaled down. + * @return the updated user + * @throws TwitterException when Twitter service or network is unavailable, + * or when the specified file is not found + * (FileNotFoundException will be nested), or when the specified + * file object in not representing a file (IOException will be + * nested) + * @see POST + * account/update_profile_image | Twitter Developers + * @since Twitter4J 2.1.11 + */ + User updateProfileImage(InputStream image) throws TwitterException; - /** - * Returns an HTTP 200 OK response code and a representation of the - * requesting user if authentication was successful; returns a 401 status - * code and an error message if not. Use this method to test if supplied - * user credentials are valid.
- * This method calls - * http://api.twitter.com/1.1/account/verify_credentials.json - * - * @return user - * @throws twitter4j.TwitterException when Twitter service or network is - * unavailable, or if supplied credential is wrong - * (TwitterException.getStatusCode() == 401) - * @see GET - * account/verify_credentials | Twitter Developers - * @since Twitter4J 2.0.0 - */ - User verifyCredentials() throws TwitterException; + /** + * Returns an HTTP 200 OK response code and a representation of the + * requesting user if authentication was successful; returns a 401 status + * code and an error message if not. Use this method to test if supplied + * user credentials are valid.
+ * This method calls + * http://api.twitter.com/1.1/account/verify_credentials.json + * + * @return user + * @throws twitter4j.TwitterException when Twitter service or network is + * unavailable, or if supplied credential is wrong + * (TwitterException.getStatusCode() == 401) + * @see GET + * account/verify_credentials | Twitter Developers + * @since Twitter4J 2.0.0 + */ + User verifyCredentials() throws TwitterException; } diff --git a/twidere/src/main/java/twitter4j/internal/json/RelationshipJSONImpl.java b/twidere/src/main/java/twitter4j/internal/json/RelationshipJSONImpl.java index 4330cb808..72f581618 100644 --- a/twidere/src/main/java/twitter4j/internal/json/RelationshipJSONImpl.java +++ b/twidere/src/main/java/twitter4j/internal/json/RelationshipJSONImpl.java @@ -16,10 +16,6 @@ package twitter4j.internal.json; -import static twitter4j.internal.util.InternalParseUtil.getBoolean; -import static twitter4j.internal.util.InternalParseUtil.getHTMLUnescapedString; -import static twitter4j.internal.util.InternalParseUtil.getLong; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -30,234 +26,248 @@ import twitter4j.TwitterException; import twitter4j.conf.Configuration; import twitter4j.http.HttpResponse; +import static twitter4j.internal.util.InternalParseUtil.getBoolean; +import static twitter4j.internal.util.InternalParseUtil.getHTMLUnescapedString; +import static twitter4j.internal.util.InternalParseUtil.getLong; + /** * A data class that has detailed information about a relationship between two * users - * + * * @author Perry Sakkaris - psakkaris at gmail.com * @see GET - * friendships/show | Twitter Developers + * friendships/show | Twitter Developers * @since Twitter4J 2.1.0 */ /* package */class RelationshipJSONImpl extends TwitterResponseImpl implements Relationship { - private static final long serialVersionUID = 2816753598969317818L; - private final long targetUserId; - private final String targetUserScreenName; - private final boolean sourceBlockingTarget; - private final boolean sourceNotificationsEnabled; - private final boolean sourceFollowingTarget; - private final boolean sourceFollowedByTarget; - private final long sourceUserId; - private final String sourceUserScreenName; - private final boolean sourceCanDM; - private final boolean sourceCanMediaTag; - private final boolean sourceMutingTarget; - private final boolean sourceMarkedTargetAsSpam; + private static final long serialVersionUID = 2816753598969317818L; + private final long targetUserId; + private final String targetUserScreenName; + private final boolean sourceBlockingTarget; + private final boolean sourceBlockedByTarget; + private final boolean sourceNotificationsEnabled; + private final boolean sourceFollowingTarget; + private final boolean sourceFollowedByTarget; + private final long sourceUserId; + private final String sourceUserScreenName; + private final boolean sourceCanDM; + private final boolean sourceCanMediaTag; + private final boolean sourceMutingTarget; + private final boolean sourceMarkedTargetAsSpam; - /* package */RelationshipJSONImpl(final HttpResponse res, final Configuration conf) throws TwitterException { - this(res, res.asJSONObject()); - } + /* package */RelationshipJSONImpl(final HttpResponse res, final Configuration conf) throws TwitterException { + this(res, res.asJSONObject()); + } - /* package */RelationshipJSONImpl(final HttpResponse res, final JSONObject json) throws TwitterException { - super(res); - try { - final JSONObject relationship = json.getJSONObject("relationship"); - final JSONObject sourceJson = relationship.getJSONObject("source"); - final JSONObject targetJson = relationship.getJSONObject("target"); - sourceUserId = getLong("id", sourceJson); - targetUserId = getLong("id", targetJson); - sourceUserScreenName = getHTMLUnescapedString("screen_name", sourceJson); - targetUserScreenName = getHTMLUnescapedString("screen_name", targetJson); - sourceBlockingTarget = getBoolean("blocking", sourceJson); - sourceFollowingTarget = getBoolean("following", sourceJson); - sourceFollowedByTarget = getBoolean("followed_by", sourceJson); - sourceNotificationsEnabled = getBoolean("notifications_enabled", sourceJson); - sourceCanDM = getBoolean("can_dm", sourceJson); - sourceCanMediaTag = getBoolean("can_media_tag", sourceJson); - sourceMutingTarget = getBoolean("muting", sourceJson); - sourceMarkedTargetAsSpam = getBoolean("marked_spam", sourceJson); - } catch (final JSONException jsone) { - throw new TwitterException(jsone.getMessage() + ":" + json.toString(), jsone); - } - } + /* package */RelationshipJSONImpl(final HttpResponse res, final JSONObject json) throws TwitterException { + super(res); + try { + final JSONObject relationship = json.getJSONObject("relationship"); + final JSONObject sourceJson = relationship.getJSONObject("source"); + final JSONObject targetJson = relationship.getJSONObject("target"); + sourceUserId = getLong("id", sourceJson); + targetUserId = getLong("id", targetJson); + sourceUserScreenName = getHTMLUnescapedString("screen_name", sourceJson); + targetUserScreenName = getHTMLUnescapedString("screen_name", targetJson); + sourceBlockingTarget = getBoolean("blocking", sourceJson); + sourceBlockedByTarget = getBoolean("blocked_by", sourceJson); + sourceFollowingTarget = getBoolean("following", sourceJson); + sourceFollowedByTarget = getBoolean("followed_by", sourceJson); + sourceNotificationsEnabled = getBoolean("notifications_enabled", sourceJson); + sourceCanDM = getBoolean("can_dm", sourceJson); + sourceCanMediaTag = getBoolean("can_media_tag", sourceJson); + sourceMutingTarget = getBoolean("muting", sourceJson); + sourceMarkedTargetAsSpam = getBoolean("marked_spam", sourceJson); + } catch (final JSONException jsone) { + throw new TwitterException(jsone.getMessage() + ":" + json.toString(), jsone); + } + } - /* package */RelationshipJSONImpl(final JSONObject json) throws TwitterException { - this(null, json); - } + /* package */RelationshipJSONImpl(final JSONObject json) throws TwitterException { + this(null, json); + } - @Override - public boolean canSourceDMTarget() { - return sourceCanDM; - } + @Override + public boolean canSourceDMTarget() { + return sourceCanDM; + } - @Override - public boolean canSourceMediaTagTarget() { - return sourceCanMediaTag; - } + @Override + public boolean canSourceMediaTagTarget() { + return sourceCanMediaTag; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (!(obj instanceof RelationshipJSONImpl)) return false; - final RelationshipJSONImpl other = (RelationshipJSONImpl) obj; - if (sourceBlockingTarget != other.sourceBlockingTarget) return false; - if (sourceCanDM != other.sourceCanDM) return false; - if (sourceCanMediaTag != other.sourceCanMediaTag) return false; - if (sourceFollowedByTarget != other.sourceFollowedByTarget) return false; - if (sourceFollowingTarget != other.sourceFollowingTarget) return false; - if (sourceMarkedTargetAsSpam != other.sourceMarkedTargetAsSpam) return false; - if (sourceMutingTarget != other.sourceMutingTarget) return false; - if (sourceNotificationsEnabled != other.sourceNotificationsEnabled) return false; - if (sourceUserId != other.sourceUserId) return false; - if (sourceUserScreenName == null) { - if (other.sourceUserScreenName != null) return false; - } else if (!sourceUserScreenName.equals(other.sourceUserScreenName)) return false; - if (targetUserId != other.targetUserId) return false; - if (targetUserScreenName == null) { - if (other.targetUserScreenName != null) return false; - } else if (!targetUserScreenName.equals(other.targetUserScreenName)) return false; - return true; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof RelationshipJSONImpl)) return false; + final RelationshipJSONImpl other = (RelationshipJSONImpl) obj; + if (sourceBlockingTarget != other.sourceBlockingTarget) return false; + if (sourceCanDM != other.sourceCanDM) return false; + if (sourceCanMediaTag != other.sourceCanMediaTag) return false; + if (sourceFollowedByTarget != other.sourceFollowedByTarget) return false; + if (sourceFollowingTarget != other.sourceFollowingTarget) return false; + if (sourceMarkedTargetAsSpam != other.sourceMarkedTargetAsSpam) return false; + if (sourceMutingTarget != other.sourceMutingTarget) return false; + if (sourceNotificationsEnabled != other.sourceNotificationsEnabled) return false; + if (sourceUserId != other.sourceUserId) return false; + if (sourceUserScreenName == null) { + if (other.sourceUserScreenName != null) return false; + } else if (!sourceUserScreenName.equals(other.sourceUserScreenName)) return false; + if (targetUserId != other.targetUserId) return false; + if (targetUserScreenName == null) { + if (other.targetUserScreenName != null) return false; + } else if (!targetUserScreenName.equals(other.targetUserScreenName)) return false; + return true; + } - /** - * {@inheritDoc} - */ - @Override - public long getSourceUserId() { - return sourceUserId; - } + /** + * {@inheritDoc} + */ + @Override + public long getSourceUserId() { + return sourceUserId; + } - /** - * {@inheritDoc} - */ - @Override - public String getSourceUserScreenName() { - return sourceUserScreenName; - } + /** + * {@inheritDoc} + */ + @Override + public String getSourceUserScreenName() { + return sourceUserScreenName; + } - /** - * {@inheritDoc} - */ - @Override - public long getTargetUserId() { - return targetUserId; - } + /** + * {@inheritDoc} + */ + @Override + public long getTargetUserId() { + return targetUserId; + } - /** - * {@inheritDoc} - */ - @Override - public String getTargetUserScreenName() { - return targetUserScreenName; - } + /** + * {@inheritDoc} + */ + @Override + public String getTargetUserScreenName() { + return targetUserScreenName; + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (sourceBlockingTarget ? 1231 : 1237); - result = prime * result + (sourceCanDM ? 1231 : 1237); - result = prime * result + (sourceCanMediaTag ? 1231 : 1237); - result = prime * result + (sourceFollowedByTarget ? 1231 : 1237); - result = prime * result + (sourceFollowingTarget ? 1231 : 1237); - result = prime * result + (sourceMarkedTargetAsSpam ? 1231 : 1237); - result = prime * result + (sourceMutingTarget ? 1231 : 1237); - result = prime * result + (sourceNotificationsEnabled ? 1231 : 1237); - result = prime * result + (int) (sourceUserId ^ sourceUserId >>> 32); - result = prime * result + (sourceUserScreenName == null ? 0 : sourceUserScreenName.hashCode()); - result = prime * result + (int) (targetUserId ^ targetUserId >>> 32); - result = prime * result + (targetUserScreenName == null ? 0 : targetUserScreenName.hashCode()); - return result; - } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (sourceBlockingTarget ? 1231 : 1237); + result = prime * result + (sourceCanDM ? 1231 : 1237); + result = prime * result + (sourceCanMediaTag ? 1231 : 1237); + result = prime * result + (sourceFollowedByTarget ? 1231 : 1237); + result = prime * result + (sourceFollowingTarget ? 1231 : 1237); + result = prime * result + (sourceMarkedTargetAsSpam ? 1231 : 1237); + result = prime * result + (sourceMutingTarget ? 1231 : 1237); + result = prime * result + (sourceNotificationsEnabled ? 1231 : 1237); + result = prime * result + (int) (sourceUserId ^ sourceUserId >>> 32); + result = prime * result + (sourceUserScreenName == null ? 0 : sourceUserScreenName.hashCode()); + result = prime * result + (int) (targetUserId ^ targetUserId >>> 32); + result = prime * result + (targetUserScreenName == null ? 0 : targetUserScreenName.hashCode()); + return result; + } - /** - * {@inheritDoc} - */ - @Override - public boolean isSourceBlockingTarget() { - return sourceBlockingTarget; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isSourceBlockingTarget() { + return sourceBlockingTarget; + } - /** - * {@inheritDoc} - */ - @Override - public boolean isSourceFollowedByTarget() { - return sourceFollowedByTarget; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isSourceBlockedByTarget() { + return sourceBlockedByTarget; + } - /** - * {@inheritDoc} - */ - @Override - public boolean isSourceFollowingTarget() { - return sourceFollowingTarget; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isSourceFollowedByTarget() { + return sourceFollowedByTarget; + } - @Override - public boolean isSourceMarkedTargetAsSpam() { - return sourceMarkedTargetAsSpam; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isSourceFollowingTarget() { + return sourceFollowingTarget; + } - @Override - public boolean isSourceMutingTarget() { - return sourceMutingTarget; - } + @Override + public boolean isSourceMarkedTargetAsSpam() { + return sourceMarkedTargetAsSpam; + } - /** - * {@inheritDoc} - */ - @Override - public boolean isSourceNotificationsEnabled() { - return sourceNotificationsEnabled; - } + @Override + public boolean isSourceMutingTarget() { + return sourceMutingTarget; + } - /** - * {@inheritDoc} - */ - @Override - public boolean isTargetFollowedBySource() { - return sourceFollowingTarget; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isSourceNotificationsEnabled() { + return sourceNotificationsEnabled; + } - /** - * {@inheritDoc} - */ - @Override - public boolean isTargetFollowingSource() { - return sourceFollowedByTarget; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isTargetFollowedBySource() { + return sourceFollowingTarget; + } - @Override - public String toString() { - return "RelationshipJSONImpl{targetUserId=" + targetUserId + ", targetUserScreenName=" + targetUserScreenName - + ", sourceBlockingTarget=" + sourceBlockingTarget + ", sourceNotificationsEnabled=" - + sourceNotificationsEnabled + ", sourceFollowingTarget=" + sourceFollowingTarget - + ", sourceFollowedByTarget=" + sourceFollowedByTarget + ", sourceUserId=" + sourceUserId - + ", sourceUserScreenName=" + sourceUserScreenName + ", sourceCanDM=" + sourceCanDM - + ", sourceCanMediaTag=" + sourceCanMediaTag + ", sourceMutingTarget=" + sourceMutingTarget - + ", sourceMarkedTargetAsSpam=" + sourceMarkedTargetAsSpam + "}"; - } + /** + * {@inheritDoc} + */ + @Override + public boolean isTargetFollowingSource() { + return sourceFollowedByTarget; + } - /* package */ - static ResponseList createRelationshipList(final HttpResponse res, final Configuration conf) - throws TwitterException { - try { - final JSONArray list = res.asJSONArray(); - final int size = list.length(); - final ResponseList relationships = new ResponseListImpl(size, res); - for (int i = 0; i < size; i++) { - final JSONObject json = list.getJSONObject(i); - final Relationship relationship = new RelationshipJSONImpl(json); - relationships.add(relationship); - } - return relationships; - } catch (final JSONException jsone) { - throw new TwitterException(jsone); - } catch (final TwitterException te) { - throw te; - } - } + @Override + public String toString() { + return "RelationshipJSONImpl{targetUserId=" + targetUserId + ", targetUserScreenName=" + targetUserScreenName + + ", sourceBlockingTarget=" + sourceBlockingTarget + ", sourceNotificationsEnabled=" + + sourceNotificationsEnabled + ", sourceFollowingTarget=" + sourceFollowingTarget + + ", sourceFollowedByTarget=" + sourceFollowedByTarget + ", sourceUserId=" + sourceUserId + + ", sourceUserScreenName=" + sourceUserScreenName + ", sourceCanDM=" + sourceCanDM + + ", sourceCanMediaTag=" + sourceCanMediaTag + ", sourceMutingTarget=" + sourceMutingTarget + + ", sourceMarkedTargetAsSpam=" + sourceMarkedTargetAsSpam + "}"; + } + + /* package */ + static ResponseList createRelationshipList(final HttpResponse res, final Configuration conf) + throws TwitterException { + try { + final JSONArray list = res.asJSONArray(); + final int size = list.length(); + final ResponseList relationships = new ResponseListImpl(size, res); + for (int i = 0; i < size; i++) { + final JSONObject json = list.getJSONObject(i); + final Relationship relationship = new RelationshipJSONImpl(json); + relationships.add(relationship); + } + return relationships; + } catch (final JSONException jsone) { + throw new TwitterException(jsone); + } catch (final TwitterException te) { + throw te; + } + } } diff --git a/twidere/src/main/res/layout/activity_global_search_box.xml b/twidere/src/main/res/layout/activity_global_search_box.xml new file mode 100644 index 000000000..e73f771cf --- /dev/null +++ b/twidere/src/main/res/layout/activity_global_search_box.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/card_item_activity_summary.xml b/twidere/src/main/res/layout/card_item_activity_summary.xml new file mode 100644 index 000000000..eca140499 --- /dev/null +++ b/twidere/src/main/res/layout/card_item_activity_summary.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/list_item_activity_title_summary.xml b/twidere/src/main/res/layout/card_item_activity_summary_compact.xml similarity index 100% rename from twidere/src/main/res/layout/list_item_activity_title_summary.xml rename to twidere/src/main/res/layout/card_item_activity_summary_compact.xml diff --git a/twidere/src/main/res/layout/card_item_message_conversation.xml b/twidere/src/main/res/layout/card_item_message_conversation.xml index ccff01c5b..439aea103 100644 --- a/twidere/src/main/res/layout/card_item_message_conversation.xml +++ b/twidere/src/main/res/layout/card_item_message_conversation.xml @@ -39,23 +39,12 @@ - - + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/card_item_status_compat.xml b/twidere/src/main/res/layout/card_item_status_compat.xml deleted file mode 100644 index 3945f5428..000000000 --- a/twidere/src/main/res/layout/card_item_status_compat.xml +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/twidere/src/main/res/layout/fragment_content_pages.xml b/twidere/src/main/res/layout/fragment_content_pages.xml index 983f25771..be29a4b8a 100644 --- a/twidere/src/main/res/layout/fragment_content_pages.xml +++ b/twidere/src/main/res/layout/fragment_content_pages.xml @@ -17,34 +17,47 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - - + android:layout_height="match_parent"> - - - - - - \ No newline at end of file + android:orientation="vertical"> + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/fragment_messages_conversation.xml b/twidere/src/main/res/layout/fragment_messages_conversation.xml index 889e7f057..6b222c45a 100644 --- a/twidere/src/main/res/layout/fragment_messages_conversation.xml +++ b/twidere/src/main/res/layout/fragment_messages_conversation.xml @@ -71,6 +71,25 @@ android:paddingRight="@dimen/element_spacing_normal" android:scrollbarStyle="outsideInset"/> + + + + + + + + + android:textColor="?android:textColorPrimary" + tools:text="Name"/> + android:textColor="?android:attr/textColorSecondary" + tools:text="\@username"/> @@ -152,15 +153,17 @@ + android:textAppearance="?android:textAppearanceMedium" + android:textColor="?android:textColorPrimary" + android:textIsSelectable="true" + tools:text="@string/sample_status_text"/> diff --git a/twidere/src/main/res/layout/header_status_compact.xml b/twidere/src/main/res/layout/header_status_compact.xml new file mode 100644 index 000000000..1c70ec1ea --- /dev/null +++ b/twidere/src/main/res/layout/header_status_compact.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/header_user_list_details.xml b/twidere/src/main/res/layout/header_user_list_details.xml index 02adf0e91..649ceb6d0 100644 --- a/twidere/src/main/res/layout/header_user_list_details.xml +++ b/twidere/src/main/res/layout/header_user_list_details.xml @@ -61,7 +61,7 @@ android:contentDescription="@string/profile_image" android:foreground="?android:selectableItemBackground" android:scaleType="fitCenter" - android:src="@drawable/ic_profile_image_default"/> + tools:src="@drawable/ic_profile_image_default"/> - - - - - - + android:padding="@dimen/element_spacing_small" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:attr/textColorSecondary" + tools:text="@string/sample_status_text"/> diff --git a/twidere/src/main/res/layout/image_viewer.xml b/twidere/src/main/res/layout/image_viewer.xml new file mode 100644 index 000000000..1be45d7ae --- /dev/null +++ b/twidere/src/main/res/layout/image_viewer.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/list_item_user_list.xml b/twidere/src/main/res/layout/list_item_user_list.xml new file mode 100644 index 000000000..85ffa288c --- /dev/null +++ b/twidere/src/main/res/layout/list_item_user_list.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/twidere/src/main/res/layout/spinner_item_account_icon.xml b/twidere/src/main/res/layout/spinner_item_account_icon.xml index 79bc12d7d..f90895654 100644 --- a/twidere/src/main/res/layout/spinner_item_account_icon.xml +++ b/twidere/src/main/res/layout/spinner_item_account_icon.xml @@ -28,8 +28,9 @@ Open Twitter links another %d others + Type name to search + No user found + Members + Subscriber \ No newline at end of file diff --git a/twidere/src/main/res/values/themes.xml b/twidere/src/main/res/values/themes.xml index 0c251616e..0c7eff9c1 100644 --- a/twidere/src/main/res/values/themes.xml +++ b/twidere/src/main/res/values/themes.xml @@ -14,6 +14,13 @@ @android:color/black + + + + + +

di`nc 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 M+m@T|z|aTxe}WVlf&c&j literal 0 HcmV?d00001 diff --git a/twidere.component.viewer.media/proguard-rules.pro b/twidere.component.viewer.media/proguard-rules.pro new file mode 100644 index 000000000..ee5b46f04 --- /dev/null +++ b/twidere.component.viewer.media/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 /Users/mariotaku/Tools/android-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.component.viewer.media/src/androidTest/java/org/mariotaku/twidere/viewer/imageviewer/ApplicationTest.java b/twidere.component.viewer.media/src/androidTest/java/org/mariotaku/twidere/viewer/imageviewer/ApplicationTest.java new file mode 100644 index 000000000..7bdc680b5 --- /dev/null +++ b/twidere.component.viewer.media/src/androidTest/java/org/mariotaku/twidere/viewer/imageviewer/ApplicationTest.java @@ -0,0 +1,32 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.twidere.viewer.imageviewer; + +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.component.viewer.media/src/main/AndroidManifest.xml b/twidere.component.viewer.media/src/main/AndroidManifest.xml new file mode 100644 index 000000000..1a3692113 --- /dev/null +++ b/twidere.component.viewer.media/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/AbsTileDecoder.java b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/AbsTileDecoder.java new file mode 100644 index 000000000..b40e463b1 --- /dev/null +++ b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/AbsTileDecoder.java @@ -0,0 +1,39 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.tileimageview.decoder; + +import android.graphics.BitmapRegionDecoder; + +import com.qozix.tileview.TileView; +import com.qozix.tileview.graphics.BitmapDecoder; + +/** + * Created by mariotaku on 15/1/5. + */ +public abstract class AbsTileDecoder implements BitmapDecoder { + + public abstract void attachToTileView(TileView tileView); + + public abstract boolean isRecycled(); + + public abstract boolean isSameDecoder(BitmapRegionDecoder decoder); + + public abstract void recycle(); +} diff --git a/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/BitmapRegionTileDecoder.java b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/BitmapRegionTileDecoder.java new file mode 100644 index 000000000..2bfe892de --- /dev/null +++ b/twidere.component.viewer.media/src/main/java/org/mariotaku/tileimageview/decoder/BitmapRegionTileDecoder.java @@ -0,0 +1,188 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2015 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 org.mariotaku.tileimageview.decoder; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Rect; + +import com.qozix.tileview.TileView; +import com.qozix.tileview.graphics.BitmapDecoder; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Display an image with {@link com.qozix.tileview.TileView} using {@link android.graphics.BitmapRegionDecoder}
+ *
+ * Usage: