mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
* #3222 Merge master into Structured Data branch, fix conflicts (#3447)
* [WIP] Fixes #2942. Set 'depicts' automatically for images uploaded via 'Nearby'
* Feature/refractor uploads [WIP] (#2887)
* Fix duplicate param information (#2515)
* Bug fix issue #2476 (#2526)
* Added wikidataEntityID in all db versions, handled db.execSql via method runQuery
* Versioning and changelog for v2.10.2 (#2531)
* Update changelog.md
* Versioning for v2.10.2
* Update changelog.md
* Bugfix/issue 2580 (#2584)
* Corrected string placedholders in certain string files
* Corrected string placedholders in certain string files[Bug fix #2580]
* Bug Fix #2585 (#2647)
* Bug Fix #2585
* Added null checks on view in SearchImageFragment when updating views from external sources
* Disposed the disposables in SearchActivity and SearchImageFragment when no longer in active lifecycle
* use FragmentUtils to verify fragment active state
* Bug Fix issue #2648 (#2678)
* Bug Fix issue #2648
* Handled external storage permission before file download
* * Removed redudant check for permission in MediaDetailPagerFragment (Dexter already does that)
* Removed duplicate code in PermissionUtil$checkPermissionsAndPerformAction, used the existing function with conditional extra parameters
* string name typo correction
* BugFix issue #2652 (#2706)
* Addded null check on bookmark before operating on it
* BugFix issue #2711 (#2712)
* Added null checks in OkHttpJsonApiClient$searchImages MwQueryResponse
* BugFix #2718 (#2719)
* Handled null auth cookies
* Fix #2791: NPE when nominating for deletion and leaving screen (#2792)
* Bug Fix issue #2789 (#2790)
* Handled Illegal State Exception for non existent appropriate view parents in ViewUtils$showShortSnackbar
* BugFix #2720 (#2831)
BugFix deprecated licenes #2720
* ui fixes, wip, upload
* *Issue #2886, BugFix #2832[wip]
* updated UploadActivity code
* modified ui
* Updated UploadPresenterTest
* * updated interfaces names to follow names suffixed with Contract
* added test cases
* card view elevation
* view pager disabled swipe
* bug fix, duplicate image
* used existing non-swipable view pager
* Avoid image view resize with keyboard, added adjustPan and stateVisible as softinputMode for UploadActivity
* retain UploadBaseFragment instances on orientation changes
* * Added test cases for UploadMediaPresenter
* Injected io and main thread schedulers
* categories presenter test cased wip
* Added CategoriesPresenter test
* * Added the logic to show open map (with to be uploaded image's coordinates while uploading image)
* codacy suggested changes * added java docs
* Added travis_wait fot android-wait-for-emulator
* ranamed interface onResponseCallback to Callback
* * Added api to delete picture in UploadModel
* cleanUp in UploadModel. once upload has been initiated
* Removed unused methods from UploadModel and the corresponding test class
* * Added tests for UploadPresenter
* Travis suggested changes
* Addded copy previous title and description
* * Made the upload add descriptions visible when keyboard visible
* add description request focus only when user manually requests it
* Added JavaDocs, review suggested changes
* Fix dagger injection
* use DialogUtil to show info in descriptions
* use activity context for DialogUtil
* Minor changes
* refactored title
* ui for depicts
* bug fix
* basic architecture for depicts
* adde architecture components for depicts
* [WIP] ApacheHttpClientMediaWikiApi.wikidataEditEntity: JSON param creation uses object instead of string
* resolved dagger errors
* multilingual captions and next button error resolved
* fixed next button issues in depicts fragment
* captions and depicts
* resolved previous button click issues
* fixed bindview error and added multi-captions
* replaced description and caption with uploadmediadetail
* refactored few classes
* modified ui of depicts
* minor fixes
* Bug fix, reduced the add description edit text clickable bound (#2973)
* moved depicts before categories
* replaced previous filename with captions
* removed time from filename
* added depicts suggestions
* [WIP] Wikidata Sandbox (Q4115189) test
* changes layout of layout_upload_depicts
* changed layout of upload_depicts
* code stuck at IO_SCHEDULER
* labels and description for depicts activity
* Bugfix/uploads (#3000)
* merged with master
* BugFix IllegalStateException
* setRetainState(true), not required with FragmentStatePagerAdapter
* Increase the ViewPager's Offscreen Limit, we want all the fragments to be active
* BugFix, clear selected categoris for previous upload session
* Clear Selected Categories
* Addded JavaDocs for CategoriesModel
* Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
* Added class level JavaDoc UploadRemoteDataSource
* Added class level JavaDoc for UploadRepository
* Added JavaDocs for ThumbnailsAdapter
* Added JavaDocs for MediaLicensePresenter, CategoriesPresenter
* Removed null check on category query
* Show default catgeories based on image title and gps location when category text empty
* Allow search for empty category search
* Attached image scale listener to upload media image
* Bug fix, reduced the add description edit text clickable bound
* Fix memory leak (#3001)
* Bugfix/uploads (#3002)
* merged with master
* BugFix IllegalStateException
* setRetainState(true), not required with FragmentStatePagerAdapter
* Increase the ViewPager's Offscreen Limit, we want all the fragments to be active
* BugFix, clear selected categoris for previous upload session
* Clear Selected Categories
* Addded JavaDocs for CategoriesModel
* Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
* Added class level JavaDoc UploadRemoteDataSource
* Added class level JavaDoc for UploadRepository
* Added JavaDocs for ThumbnailsAdapter
* Added JavaDocs for MediaLicensePresenter, CategoriesPresenter
* Removed null check on category query
* Show default catgeories based on image title and gps location when category text empty
* Allow search for empty category search
* Attached image scale listener to upload media image
* Bug fix, reduced the add description edit text clickable bound
* Added tooltip in Title in UploadMediaFragment
* BugFix recent categories
* Updated test methods
* Bugfix/uploads (#3011)
* merged with master
* BugFix IllegalStateException
* setRetainState(true), not required with FragmentStatePagerAdapter
* Increase the ViewPager's Offscreen Limit, we want all the fragments to be active
* BugFix, clear selected categoris for previous upload session
* Clear Selected Categories
* Addded JavaDocs for CategoriesModel
* Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
* Added class level JavaDoc UploadRemoteDataSource
* Added class level JavaDoc for UploadRepository
* Added JavaDocs for ThumbnailsAdapter
* Added JavaDocs for MediaLicensePresenter, CategoriesPresenter
* Removed null check on category query
* Show default catgeories based on image title and gps location when category text empty
* Allow search for empty category search
* Attached image scale listener to upload media image
* Bug fix, reduced the add description edit text clickable bound
* Added tooltip in Title in UploadMediaFragment
* BugFix recent categories
* Updated test methods
* Avoid memory leak, free the adpater in MediaLicenseFragment.onDestroyView
* bugfix/uploads (#3012)
* merged with master
* BugFix IllegalStateException
* setRetainState(true), not required with FragmentStatePagerAdapter
* Increase the ViewPager's Offscreen Limit, we want all the fragments to be active
* BugFix, clear selected categoris for previous upload session
* Clear Selected Categories
* Addded JavaDocs for CategoriesModel
* Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
* Added class level JavaDoc UploadRemoteDataSource
* Added class level JavaDoc for UploadRepository
* Added JavaDocs for ThumbnailsAdapter
* Added JavaDocs for MediaLicensePresenter, CategoriesPresenter
* Removed null check on category query
* Show default catgeories based on image title and gps location when category text empty
* Allow search for empty category search
* Attached image scale listener to upload media image
* Bug fix, reduced the add description edit text clickable bound
* Added tooltip in Title in UploadMediaFragment
* BugFix recent categories
* Updated test methods
* Avoid memory leak, free the adpater in MediaLicenseFragment.onDestroyView
* BugFix Illegal State Exception in ViewpPagerAdapter
* Remove irrelevant comment
* merge conflict with strings (#3016)
* [WIP] Fixed duplicated subscriprion for 'addPropertyP180'
* added documentation
* fixed issue #3006
* resolved issue #3004
* fixed issue with categoryPresenterTest.kt
* send captions as labels
* fixed issue with the captions
* optimised imports
* added upload for captions
* minor changes
* resolved issue with uploading captions
* resolved issue with api call
* uploading captions to wikibase
* added some tests and documentation
* undo formatting changes
* uploaded captions as labels to wikibase
* minor changes
* resolved error with spinner adpater
* adding captions to local database
* Fixed issue #3035
* fixed issue #3033
* fixed issue #3005
* fixed issue #3005
* added search for depicts
* fixed issue with compile time
* fixe issue with project build
* fixed issue #3044
* merged uploading depicts into branch
* uploading depicts
* rebased branch
* fixed crash due to depicts
* modified depicts interface
* Resolve merge conflicts
* Fix issues with API calls
* Use wikidata token
* searching depictions from depicts activity
* added some documentation and other changes
* fixed crash on selecting depictions
* sending wikidataentity id to upload depictions
* added changes after review
* Fixed issue with next button diabling in media detail activity
* added tests for depictions
* added all the unit tests and fixed few more issues
* showing captions in media details
* show captions in media details
* added documentations and worked upon review comments
* parsing response for depictions
* displaying captions and depiction QID in media detail
* added documentation
* fetching labels from QIDs
* captions working perfectly
* added documentations and code cleaning
* minor changes
* minor changes
* Showing items in explore
* added search via depicts in explore
* Added setOffscreenPageLimit in ViewPager
* show captions in explore
* show captions in home
* showing depict images under items
* added documentation and code refactoring
* enabled pagination in depiction search
* added some tests and media deatils in depiction detail activity
* fixed bug with back button in media
* fixed issue #3100
* fixed issue #3098
* fixed issue #3099
* fixed issue #3104 and #3098
* showing captions in place of title in home and explore:media
* show captions in explore:depiction image list activity
* showing depictions in media details
* showing depictions in media details in production flavor
* fixed issue #3108 and #3107
* fix isse #3108
* fixed issue #3110 and #3112
* fixed issue #3113
* added documentations
* fixed issue #3076 and #3109
* added depiction search test
* fixed issue #3113
* fixed issue #3111
* fixed issue #3106
* Showing items in explore
* minor change
* fixed issue #3118 and some other changes
* added MVP in searchdepictionsfragment
* added mvp architecture
* added MVP architecture to DepictedImagesDetailsActivity
* added documentation and some minor changes
* added image to depicted item in search depictions
* * Use callbacks from renderer to fetch thumbnails
* adding fresco to load image in depictions
* adding thumbnail image for depictions in upload and explore
* pagination issues
* fixed issue --(showing previous depiction thumbnail in explore)
* Fixed the logic for pagination
* hide progress on success of last page
* adding sub-items and parent items to search in explore
* minor changes for review comments
* fixed issue #3119
* fixed issue #3130
* changes after review comments
* showing child classes for depictions
* Showing child items
* showing parent classes for depicted items
* adding localised search for parent and child items
* clicking on any child class or parent class should call the corresponding class items
* fixed issue of showing wrong thumbnail for P18 item
* fixed issue #3132
* added test for DepictedImagesPresenter.java
* added unit tests for depicted items parent and child classes
* removed unused imports and code formatting
* fixed issue in search test
* deleting unnecessary .attach_pid9313 file
* deleting unnecessary .attach_pid9655 file
* added SearchDepictionsPresenterTest
* changes after review comments
* updates for review comments
* added more documentations
* removed unused code and classes and addressed spacing changes
* changes after review
* fixed build issues in the app
* worked on some review comments
* fixed issue:wrong thumbnail appears on wikidata item
* minor change
* worked on some review changes
* worked on review comments
* minor change
* addressed remaining review comments
* replaced hardcoded jpgs with pageIds to fetch captions
* added documentation
* removed hardcoded extensions and worked on review comments
* review comments
* [WIP] Added Depicts values for flavors
* [WIP] Minor fix
* [WIP] Minor fixes
* [WIP] Fixed URL
* [WIP] Fixed URLs and tokens
* Fixed MediaClient: added check for null in continuation store
* Fixed Media::from, changed return from null to new Media()
* [WIP] Merged with master
* Fix #3254 Displays a proper message in explore section when no result for caption
* Updated Mockito to org.mockito:mockito-inline:2.13.0
* [WIP] Fixed tests after merging
* [WIP] Fixed some JUnit tests
* Fixed 'accessing from wrong thread' error
* #3222 Delete manifest declaration of activity as fragment - stop casting MainActivity to CatgoryImagesCallback - fix tests
* Remove unit test not associated with any class - make CategoryPresenterTest more idiomatic
* fix compilation errors
Co-authored-by: Vitaly V. Pinchuk <vetal.978@gmail.com>
Co-authored-by: Ashish Kumar <ashishkumar468@gmail.com>
Co-authored-by: vanshikaarora <vanshikaa937@gmail.com>
Co-authored-by: Vivek Maskara <maskaravivek@gmail.com>
Co-authored-by: Vanshika Arora <34261945+vanshikaarora@users.noreply.github.com>
Co-authored-by: Somanshu and Himanshu <somanshS14@gmail.com>
* #3482 Use Room in Structured Data branch - remove unused code (#3483)
* #3482 Use Room in Structured Data branch - remove unused code
* #3482 Use Room in Structured Data branch - fix unit test compilation
* #3482 Use Room in Structured Data branch - add kdoc
* #3490 Depiction Search in upload shows No Results before it gets results (#3491)
* #3482 Use Room in Structured Data branch - remove unused code
* #3482 Use Room in Structured Data branch - fix unit test compilation
* #3490 Depiction Search in upload shows No Results before it gets results - stop showing error on subscription
* #3490 Depiction Search in upload shows No Results before it gets results - update test cases
* make labels nullable too
* fix unit test compilation
* #3222 remove lingering reference to depiction content provider
* Fix Crash
* #3222 Merge master into Structured Data branch, fix conflicts - review fixes
* Fix method invocations
* #3529 Captions/depictions are not saved to Commons (#3574)
* #3529 Captions/depictions are not saved to Commons - make copy of list of depictionEntityIds - uncomment editBaseDepictsProperty - refactor upload related classes
* #3529 Captions/depictions are not saved to Commons - fix wrong ArrayList usage
* #3529 Captions/depictions are not saved to Commons - fix test
* #3503 Remove Title/Caption From MediaUploadDetail and only use Caption/Description pairs (#3578)
* #3529 Captions/depictions are not saved to Commons - make copy of list of depictionEntityIds - uncomment editBaseDepictsProperty - refactor upload related classes
* #3529 Captions/depictions are not saved to Commons - fix wrong ArrayList usage
* #3529 Captions/depictions are not saved to Commons - fix test
* #3503 Remove Title/Caption From MediaUploadDetail and only use Caption/Description pairs - replace title with the first MediaDetail
* #3503 Remove Title/Caption From MediaUploadDetail and only use Caption/Description pairs - restore button disabling
* #3503 Remove Title/Caption From MediaUploadDetail and only use Caption/Description pairs - fix nearby place
* fix thumbnail issue 3526 (#3617)
* #3222 Merge master into Structured Data branch, fix conflicts - fix bad merge
* #3529 Captions/depictions are not saved to Commons (#3588)
* #3529 Captions/depictions are not saved to Commons - update flow to update appropriate data
* #3529 Captions/depictions are not saved to Commons - fix invoking of setlabel
* #3529 Captions/depictions are not saved to Commons - fix unit tests
* #3529 Captions/depictions are not saved to Commons - use constant for @Named
* #3529 Captions/depictions are not saved to Commons - remove captions interface
* #3529 Captions/depictions are not saved to Commons - delete unused Contribution fields - enforce Single Responsibility by using PageContentsCreator
* #3529 Captions/depictions are not saved to Commons - prefix id with M - remove language from url and only add from Field
* #3529 Captions/depictions are not saved to Commons - make edits of depictions and captions sequential
* #3529 Captions/depictions are not saved to Commons - remove unused model fields
* #3529 Captions/depictions are not saved to Commons - weaken type of categories - copy list on Contribution creation
* #3529 Captions/depictions are not saved to Commons - mark Media fields private - weaken types - remove partly implemented fields
* #3529 Captions/depictions are not saved to Commons - add semi colon
* #3529 Captions/depictions are not saved to Commons - fix test
* Fix issue 3526 Unlike "Items" tab, "child classes" tab does not display description nor image thumbnail (#3619)
* fix thumbnail issue 3526
* Fix Description issue 3526
* revert changes on this file, not finished with it yet
* Fix Description for Child and Parent classes - issue 3526
* Remove conflict text in file
* Remove retrofit.HEAD import
* Incorporated review comments
* Fix issue 3137 (#3637)
* Fix issue 3137
* Remove import Timber
* Remove unnecessary space
* #3222 Merge master into Structured Data branch, fix conflicts - revert logging
* Fix build
* #3661 No Depictions Selected Dialog has reversed buttons - fix button order
* Revert "#3661 No Depictions Selected Dialog has reversed buttons - fix button order"
This reverts commit d8f9809584.
* #3222 Merge master into Structured Data branch, fix conflicts - remove unused methods/fields
* #3661 No Depictions Selected Dialog has reversed buttons - fix button order (#3662)
* #3653 Many Mnull requests - stop requesting captions for null ids (#3657)
* #3653 Many Mnull requests - stop requesting captions for null ids
* #3653 Many Mnull requests - move log line
* #3633 [structured-data branch] In depictions selection screen, suggest nearby items (#3650)
* #3633 [structured-data branch] In depictions selection screen, suggest nearby items - for empty search terms show nearby items for depictions
* #3633 [structured-data branch] In depictions selection screen, suggest nearby items - use linear radii progression to search for places
* #3666 Crash when uploading on structured-data branch - revert cleanup of UploadController (#3670)
* #3222 Merge Structured Data branch into master - fix caption rendering in new UI
* #3222 Merge Structured Data branch into master - upgrade retrofit + okhttp
* #3664 Stop using JsonObject on StructuredData (#3672)
* #3664 Stop using JsonObject on StructuredData - remove usage in Media classes - remove from depicts client - create partial network models
* #3664 Stop using JsonObject on StructuredData - allow partial mapping of polymorphic models by returning null in typeadapter
* #3664 Stop using JsonObject on StructuredData - use models for editing depicts property
* #3664 Stop using JsonObject on StructuredData - use models for sparql parent query
* #3664 Stop using JsonObject on StructuredData - fix unit test compilation
* #3664 Stop using JsonObject on StructuredData - unify sparql responses
* #3664 Stop using JsonObject on StructuredData - minor cleanup of misnamed/unused/too broad visibility
* #3664 Stop using JsonObject on StructuredData - share variable names and logic for the Sarql queries
* #3664 Stop using JsonObject on StructuredData - add error logging
Co-authored-by: Vitaly V. Pinchuk <vetal.978@gmail.com>
Co-authored-by: Ashish Kumar <ashishkumar468@gmail.com>
Co-authored-by: vanshikaarora <vanshikaa937@gmail.com>
Co-authored-by: Vivek Maskara <maskaravivek@gmail.com>
Co-authored-by: Vanshika Arora <34261945+vanshikaarora@users.noreply.github.com>
Co-authored-by: Somanshu and Himanshu <somanshS14@gmail.com>
Co-authored-by: vvijayalakshmi21 <34595292+vvijayalakshmi21@users.noreply.github.com>
This commit is contained in:
parent
22c20687f3
commit
0f906b20c9
168 changed files with 7463 additions and 2123 deletions
1
.idea/inspectionProfiles/Project_Default.xml
generated
1
.idea/inspectionProfiles/Project_Default.xml
generated
|
|
@ -7,6 +7,7 @@
|
||||||
<inspection_tool class="ConfusingElse" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="ConfusingElse" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="reportWhenNoStatementFollow" value="true" />
|
<option name="reportWhenNoStatementFollow" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,9 @@ dependencies {
|
||||||
|
|
||||||
implementation project(':wikimedia-data-client')
|
implementation project(':wikimedia-data-client')
|
||||||
// Utils
|
// Utils
|
||||||
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
|
||||||
implementation 'in.yuvi:http.fluent:1.3'
|
implementation 'in.yuvi:http.fluent:1.3'
|
||||||
implementation 'com.google.code.gson:gson:2.8.5'
|
implementation 'com.google.code.gson:gson:2.8.5'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.5.0'
|
||||||
implementation 'com.squareup.okio:okio:2.2.2'
|
implementation 'com.squareup.okio:okio:2.2.2'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
|
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
|
||||||
|
|
@ -44,6 +43,7 @@ dependencies {
|
||||||
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
||||||
implementation 'com.karumi:dexter:5.0.0'
|
implementation 'com.karumi:dexter:5.0.0'
|
||||||
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||||
|
|
||||||
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
|
|
@ -53,7 +53,7 @@ dependencies {
|
||||||
api('com.github.tony19:logback-android-classic:1.1.1-6') {
|
api('com.github.tony19:logback-android-classic:1.1.1-6') {
|
||||||
exclude group: 'com.google.android', module: 'android'
|
exclude group: 'com.google.android', module: 'android'
|
||||||
}
|
}
|
||||||
implementation "com.squareup.okhttp3:logging-interceptor:4.2.0"
|
implementation "com.squareup.okhttp3:logging-interceptor:4.5.0"
|
||||||
|
|
||||||
// Dependency injector
|
// Dependency injector
|
||||||
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
|
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
|
||||||
|
|
@ -65,7 +65,7 @@ dependencies {
|
||||||
|
|
||||||
//Mocking
|
//Mocking
|
||||||
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
|
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
|
||||||
testImplementation 'org.mockito:mockito-inline:2.8.47'
|
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||||
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
|
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
|
||||||
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
|
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
|
||||||
|
|
@ -108,9 +108,10 @@ dependencies {
|
||||||
|
|
||||||
//Room
|
//Room
|
||||||
implementation "androidx.room:room-runtime:$ROOM_VERSION"
|
implementation "androidx.room:room-runtime:$ROOM_VERSION"
|
||||||
kapt "androidx.room:room-compiler:$ROOM_VERSION" // For Kotlin use kapt instead of annotationProcessor
|
implementation "androidx.room:room-ktx:$ROOM_VERSION"
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
|
|
||||||
implementation "androidx.room:room-rxjava2:$ROOM_VERSION"
|
implementation "androidx.room:room-rxjava2:$ROOM_VERSION"
|
||||||
|
kapt "androidx.room:room-compiler:$ROOM_VERSION" // For Kotlin use kapt instead of annotationProcessor
|
||||||
|
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
|
||||||
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||||
|
|
||||||
// Pref
|
// Pref
|
||||||
|
|
@ -208,6 +209,7 @@ android {
|
||||||
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
||||||
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
|
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
||||||
|
buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
|
||||||
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
||||||
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json\""
|
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json\""
|
||||||
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
|
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
|
||||||
|
|
@ -229,6 +231,7 @@ android {
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
|
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
|
||||||
buildConfigField "String", "TEST_USERNAME", "\"" + System.getenv("test_user_name") + "\""
|
buildConfigField "String", "TEST_USERNAME", "\"" + System.getenv("test_user_name") + "\""
|
||||||
buildConfigField "String", "TEST_PASSWORD", "\"" + System.getenv("test_user_password") + "\""
|
buildConfigField "String", "TEST_PASSWORD", "\"" + System.getenv("test_user_password") + "\""
|
||||||
|
buildConfigField "String", "DEPICTS_PROPERTY", "\"P180\""
|
||||||
|
|
||||||
dimension 'tier'
|
dimension 'tier'
|
||||||
}
|
}
|
||||||
|
|
@ -240,6 +243,7 @@ android {
|
||||||
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
||||||
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
|
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
||||||
|
buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
|
||||||
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
||||||
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns_beta_active.json\""
|
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns_beta_active.json\""
|
||||||
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
|
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
|
||||||
|
|
@ -261,6 +265,7 @@ android {
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
|
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
|
||||||
buildConfigField "String", "TEST_USERNAME", "\"" + System.getenv("test_user_name") + "\""
|
buildConfigField "String", "TEST_USERNAME", "\"" + System.getenv("test_user_name") + "\""
|
||||||
buildConfigField "String", "TEST_PASSWORD", "\"" + System.getenv("test_user_password") + "\""
|
buildConfigField "String", "TEST_PASSWORD", "\"" + System.getenv("test_user_password") + "\""
|
||||||
|
buildConfigField "String", "DEPICTS_PROPERTY", "\"P245962\""
|
||||||
|
|
||||||
dimension 'tier'
|
dimension 'tier'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package fr.free.nrw.commons
|
||||||
|
|
||||||
|
import androidx.test.runner.AndroidJUnit4
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
|
import androidx.test.rule.ActivityTestRule
|
||||||
|
import fr.free.nrw.commons.upload.UploadActivity
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
import org.hamcrest.core.AllOf
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class DepictionSearchTest {
|
||||||
|
@get:Rule
|
||||||
|
var activityRule = ActivityTestRule(UploadActivity::class.java)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun TestForCaptionsAndDepictions() {
|
||||||
|
val imageUri = Uri.parse("file://mnt/sdcard/image.jpg")
|
||||||
|
|
||||||
|
// Build a result to return from the Camera app
|
||||||
|
|
||||||
|
|
||||||
|
// Stub out the File picker. When an intent is sent to the File picker, this tells
|
||||||
|
// Espresso to respond with the ActivityResult we just created
|
||||||
|
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("caption in english"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("description in english"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages)).perform(ViewActions.click());
|
||||||
|
Espresso.onData(AllOf.allOf(Matchers.anything("spinner text"))).atPosition(1).perform(ViewActions.click());
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("caption in some other language"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("description in some other language"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.btn_next))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
package fr.free.nrw.commons
|
package fr.free.nrw.commons
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.test.espresso.Espresso
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.intent.Intents
|
||||||
|
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import androidx.test.rule.ActivityTestRule
|
import androidx.test.rule.ActivityTestRule
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import fr.free.nrw.commons.upload.UploadActivity
|
import fr.free.nrw.commons.upload.UploadActivity
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsFragment
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
import org.hamcrest.core.AllOf
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
@ -16,4 +25,25 @@ class UploadActivityTest {
|
||||||
fun orientationChange() {
|
fun orientationChange() {
|
||||||
UITestHelper.changeOrientation(activityRule)
|
UITestHelper.changeOrientation(activityRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun TestForCaptionsAndDepictions() {
|
||||||
|
val imageUri = Uri.parse("file://mnt/sdcard/image.jpg")
|
||||||
|
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("caption in english"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("description in english"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.spinner_description_languages)).perform(ViewActions.click());
|
||||||
|
Espresso.onData(AllOf.allOf(Matchers.anything("spinner text"))).atPosition(1).perform(ViewActions.click());
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.caption_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("caption in some other language"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.description_item_edit_text))
|
||||||
|
.perform(ViewActions.typeText("description in some other language"))
|
||||||
|
Espresso.onView(ViewMatchers.withId(R.id.btn_next))
|
||||||
|
.perform(ViewActions.click())
|
||||||
|
Intents.intended(IntentMatchers.hasComponent(DepictsFragment::class.java.name))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +119,11 @@
|
||||||
android:label="@string/title_activity_featured_images"
|
android:label="@string/title_activity_featured_images"
|
||||||
android:parentActivityName=".contributions.MainActivity" />
|
android:parentActivityName=".contributions.MainActivity" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".depictions.WikidataItemDetailsActivity"
|
||||||
|
android:label="@string/title_activity_featured_images"
|
||||||
|
android:parentActivityName=".contributions.MainActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".explore.categories.ExploreActivity"
|
android:name=".explore.categories.ExploreActivity"
|
||||||
android:label="@string/title_activity_explore"
|
android:label="@string/title_activity_explore"
|
||||||
|
|
@ -178,7 +183,7 @@
|
||||||
android:authorities="${applicationId}.categories.contentprovider"
|
android:authorities="${applicationId}.categories.contentprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/provider_categories"
|
android:label="@string/provider_categories"
|
||||||
android:syncable="false" />
|
android:syncable="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".explore.recentsearches.RecentSearchesContentProvider"
|
android:name=".explore.recentsearches.RecentSearchesContentProvider"
|
||||||
|
|
|
||||||
|
|
@ -1,62 +1,5 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Application;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Process;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
|
||||||
import com.facebook.imagepipeline.core.ImagePipeline;
|
|
||||||
import com.facebook.imagepipeline.core.ImagePipelineConfig;
|
|
||||||
import com.mapbox.mapboxsdk.Mapbox;
|
|
||||||
import com.squareup.leakcanary.LeakCanary;
|
|
||||||
import com.squareup.leakcanary.RefWatcher;
|
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
|
||||||
import org.acra.ACRA;
|
|
||||||
import org.acra.annotation.AcraCore;
|
|
||||||
import org.acra.annotation.AcraDialog;
|
|
||||||
import org.acra.annotation.AcraMailSender;
|
|
||||||
import org.acra.data.StringFormat;
|
|
||||||
import org.wikipedia.AppAdapter;
|
|
||||||
import org.wikipedia.language.AppLanguageLookUpTable;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
|
||||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
|
||||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
|
||||||
import fr.free.nrw.commons.category.CategoryDao;
|
|
||||||
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
|
|
||||||
import fr.free.nrw.commons.concurrency.ThreadPoolService;
|
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
|
||||||
import fr.free.nrw.commons.db.AppDatabase;
|
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
import fr.free.nrw.commons.logging.FileLoggingTree;
|
|
||||||
import fr.free.nrw.commons.logging.LogUtils;
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
|
||||||
import fr.free.nrw.commons.upload.FileUtils;
|
|
||||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.internal.functions.Functions;
|
|
||||||
import io.reactivex.plugins.RxJavaPlugins;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE;
|
import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE;
|
||||||
import static org.acra.ReportField.ANDROID_VERSION;
|
import static org.acra.ReportField.ANDROID_VERSION;
|
||||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||||
|
|
@ -65,6 +8,57 @@ import static org.acra.ReportField.PHONE_MODEL;
|
||||||
import static org.acra.ReportField.STACK_TRACE;
|
import static org.acra.ReportField.STACK_TRACE;
|
||||||
import static org.acra.ReportField.USER_COMMENT;
|
import static org.acra.ReportField.USER_COMMENT;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Application;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Process;
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||||
|
import com.facebook.imagepipeline.core.ImagePipelineConfig;
|
||||||
|
import com.mapbox.mapboxsdk.Mapbox;
|
||||||
|
import com.squareup.leakcanary.LeakCanary;
|
||||||
|
import com.squareup.leakcanary.RefWatcher;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
|
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||||
|
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
||||||
|
import fr.free.nrw.commons.category.CategoryDao;
|
||||||
|
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
|
||||||
|
import fr.free.nrw.commons.concurrency.ThreadPoolService;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||||
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.logging.FileLoggingTree;
|
||||||
|
import fr.free.nrw.commons.logging.LogUtils;
|
||||||
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
import fr.free.nrw.commons.upload.FileUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||||
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.internal.functions.Functions;
|
||||||
|
import io.reactivex.plugins.RxJavaPlugins;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import org.acra.ACRA;
|
||||||
|
import org.acra.annotation.AcraCore;
|
||||||
|
import org.acra.annotation.AcraDialog;
|
||||||
|
import org.acra.annotation.AcraMailSender;
|
||||||
|
import org.acra.data.StringFormat;
|
||||||
|
import org.wikipedia.AppAdapter;
|
||||||
|
import org.wikipedia.language.AppLanguageLookUpTable;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
@AcraCore(
|
@AcraCore(
|
||||||
buildConfigClass = BuildConfig.class,
|
buildConfigClass = BuildConfig.class,
|
||||||
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
||||||
|
|
@ -120,8 +114,7 @@ public class CommonsApplication extends Application {
|
||||||
return languageLookUpTable;
|
return languageLookUpTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject ContributionDao contributionDao;
|
||||||
AppDatabase appDatabase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to declare and initialize various components and dependencies
|
* Used to declare and initialize various components and dependencies
|
||||||
|
|
@ -299,7 +292,7 @@ public class CommonsApplication extends Application {
|
||||||
|
|
||||||
CategoryDao.Table.onDelete(db);
|
CategoryDao.Table.onDelete(db);
|
||||||
dbOpenHelper.deleteTable(db,CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
|
dbOpenHelper.deleteTable(db,CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
|
||||||
appDatabase.getContributionDao().deleteAll();
|
contributionDao.deleteAll();
|
||||||
BookmarkPicturesDao.Table.onDelete(db);
|
BookmarkPicturesDao.Table.onDelete(db);
|
||||||
BookmarkLocationsDao.Table.onDelete(db);
|
BookmarkLocationsDao.Table.onDelete(db);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,31 +3,24 @@ package fr.free.nrw.commons;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.PrimaryKey;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.media.Depictions;
|
||||||
|
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||||
|
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
||||||
import org.wikipedia.gallery.ExtMetadata;
|
import org.wikipedia.gallery.ExtMetadata;
|
||||||
import org.wikipedia.gallery.ImageInfo;
|
import org.wikipedia.gallery.ImageInfo;
|
||||||
import org.wikipedia.page.PageTitle;
|
import org.wikipedia.page.PageTitle;
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
|
||||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
|
||||||
import fr.free.nrw.commons.utils.MediaDataExtractorUtil;
|
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
public class Media implements Parcelable {
|
public class Media implements Parcelable {
|
||||||
|
|
||||||
|
|
@ -35,32 +28,42 @@ public class Media implements Parcelable {
|
||||||
|
|
||||||
// Primary metadata fields
|
// Primary metadata fields
|
||||||
@Nullable
|
@Nullable
|
||||||
public Uri localUri;
|
private Uri localUri;
|
||||||
public String thumbUrl;
|
private String thumbUrl;
|
||||||
public String imageUrl;
|
private String imageUrl;
|
||||||
public String filename;
|
private String filename;
|
||||||
public String description; // monolingual description on input...
|
private String thumbnailTitle;
|
||||||
public String discussion;
|
/*
|
||||||
long dataLength;
|
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
|
||||||
public Date dateCreated;
|
* This is a replacement of the previously used titles for images (titles were not multilingual)
|
||||||
@Nullable public Date dateUploaded;
|
* Also now captions replace the previous convention of using title for filename
|
||||||
public int width;
|
*/
|
||||||
public int height;
|
private String caption;
|
||||||
public String license;
|
private String description; // monolingual description on input...
|
||||||
public String licenseUrl;
|
private String discussion;
|
||||||
public String creator;
|
private long dataLength;
|
||||||
public ArrayList<String> categories; // as loaded at runtime?
|
private Date dateCreated;
|
||||||
public boolean requestedDeletion;
|
@Nullable private Date dateUploaded;
|
||||||
public HashMap<String, String> descriptions; // multilingual descriptions as loaded
|
private String license;
|
||||||
public HashMap<String, String> tags = new HashMap<>();
|
private String licenseUrl;
|
||||||
@Nullable public LatLng coordinates;
|
private String creator;
|
||||||
|
/**
|
||||||
|
* Wikibase Identifier associated with media files
|
||||||
|
*/
|
||||||
|
private String pageId;
|
||||||
|
private List<String> categories; // as loaded at runtime?
|
||||||
|
/**
|
||||||
|
* Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories.
|
||||||
|
* However unlike categories depictions is multi-lingual
|
||||||
|
*/
|
||||||
|
private Depictions depictions;
|
||||||
|
private boolean requestedDeletion;
|
||||||
|
@Nullable private LatLng coordinates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides local constructor
|
* Provides local constructor
|
||||||
*/
|
*/
|
||||||
protected Media() {
|
public Media() {
|
||||||
this.categories = new ArrayList<>();
|
|
||||||
this.descriptions = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,7 +72,6 @@ public class Media implements Parcelable {
|
||||||
* @param filename Media filename
|
* @param filename Media filename
|
||||||
*/
|
*/
|
||||||
public Media(String filename) {
|
public Media(String filename) {
|
||||||
this();
|
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,9 +86,9 @@ public class Media implements Parcelable {
|
||||||
* @param dateUploaded Media date uploaded
|
* @param dateUploaded Media date uploaded
|
||||||
* @param creator Media creator
|
* @param creator Media creator
|
||||||
*/
|
*/
|
||||||
public Media(Uri localUri, String imageUrl, String filename, String description,
|
public Media(Uri localUri, String imageUrl, String filename,
|
||||||
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
|
String description,
|
||||||
this();
|
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
|
||||||
this.localUri = localUri;
|
this.localUri = localUri;
|
||||||
this.thumbUrl = imageUrl;
|
this.thumbUrl = imageUrl;
|
||||||
this.imageUrl = imageUrl;
|
this.imageUrl = imageUrl;
|
||||||
|
|
@ -96,8 +98,17 @@ public class Media implements Parcelable {
|
||||||
this.dateCreated = dateCreated;
|
this.dateCreated = dateCreated;
|
||||||
this.dateUploaded = dateUploaded;
|
this.dateUploaded = dateUploaded;
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
this.categories = new ArrayList<>();
|
}
|
||||||
this.descriptions = new HashMap<>();
|
|
||||||
|
public Media(Uri localUri, String filename,
|
||||||
|
String description, String creator, List<String> categories) {
|
||||||
|
this(localUri,null, filename,
|
||||||
|
description, -1, null, new Date(), creator);
|
||||||
|
this.categories = categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Media(String title, Date date, String user) {
|
||||||
|
this(null, null, title, "", -1, date, date, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,7 +123,7 @@ public class Media implements Parcelable {
|
||||||
public static Media from(MwQueryPage page) {
|
public static Media from(MwQueryPage page) {
|
||||||
ImageInfo imageInfo = page.imageInfo();
|
ImageInfo imageInfo = page.imageInfo();
|
||||||
if (imageInfo == null) {
|
if (imageInfo == null) {
|
||||||
return null;
|
return new Media(); // null is not allowed
|
||||||
}
|
}
|
||||||
ExtMetadata metadata = imageInfo.getMetadata();
|
ExtMetadata metadata = imageInfo.getMetadata();
|
||||||
if (metadata == null) {
|
if (metadata == null) {
|
||||||
|
|
@ -127,7 +138,7 @@ public class Media implements Parcelable {
|
||||||
Media media = new Media(null,
|
Media media = new Media(null,
|
||||||
imageInfo.getOriginalUrl(),
|
imageInfo.getOriginalUrl(),
|
||||||
page.title(),
|
page.title(),
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
safeParseDate(metadata.dateTime()),
|
safeParseDate(metadata.dateTime()),
|
||||||
safeParseDate(metadata.dateTime()),
|
safeParseDate(metadata.dateTime()),
|
||||||
|
|
@ -138,12 +149,14 @@ public class Media implements Parcelable {
|
||||||
media.setThumbUrl(imageInfo.getThumbUrl());
|
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
media.setPageId(String.valueOf(page.pageId()));
|
||||||
|
|
||||||
String language = Locale.getDefault().getLanguage();
|
String language = Locale.getDefault().getLanguage();
|
||||||
if (StringUtils.isBlank(language)) {
|
if (StringUtils.isBlank(language)) {
|
||||||
language = "default";
|
language = "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
media.setDescriptions(Collections.singletonMap(language, metadata.imageDescription()));
|
media.setDescription(metadata.imageDescription());
|
||||||
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
|
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.getCategories()));
|
||||||
String latitude = metadata.getGpsLatitude();
|
String latitude = metadata.getGpsLatitude();
|
||||||
String longitude = metadata.getGpsLongitude();
|
String longitude = metadata.getGpsLongitude();
|
||||||
|
|
@ -172,28 +185,23 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return pageId for the current media object*/
|
||||||
|
public String getPageId() {
|
||||||
|
return pageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*sets pageId for the current media object
|
||||||
|
*/
|
||||||
|
public void setPageId(String pageId) {
|
||||||
|
this.pageId = pageId;
|
||||||
|
}
|
||||||
|
|
||||||
public String getThumbUrl() {
|
public String getThumbUrl() {
|
||||||
return thumbUrl;
|
return thumbUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets tag of media
|
|
||||||
* @param key Media key
|
|
||||||
* @return Media tag
|
|
||||||
*/
|
|
||||||
public Object getTag(String key) {
|
|
||||||
return tags.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies( or creates a) tag of media
|
|
||||||
* @param key Media key
|
|
||||||
* @param value Media value
|
|
||||||
*/
|
|
||||||
public void setTag(String key, String value) {
|
|
||||||
tags.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets media display title
|
* Gets media display title
|
||||||
* @return Media title
|
* @return Media title
|
||||||
|
|
@ -202,6 +210,21 @@ public class Media implements Parcelable {
|
||||||
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
|
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Caption(if available) as the thumbnail title of the image
|
||||||
|
*/
|
||||||
|
public void setThumbnailTitle(String title) {
|
||||||
|
this.thumbnailTitle = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return title to be shown on image thumbnail
|
||||||
|
* If caption is available for the image then it returns caption else filename
|
||||||
|
*/
|
||||||
|
public String getThumbnailTitle() {
|
||||||
|
return thumbnailTitle != null? thumbnailTitle : getDisplayTitle();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets file page title
|
* Gets file page title
|
||||||
* @return New media page title
|
* @return New media page title
|
||||||
|
|
@ -268,6 +291,24 @@ public class Media implements Parcelable {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
|
||||||
|
* This is a replacement of the previously used titles for images (titles were not multilingual)
|
||||||
|
* Also now captions replace the previous convention of using title for filename
|
||||||
|
*
|
||||||
|
* @return caption
|
||||||
|
*/
|
||||||
|
public String getCaption() {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return depictions associated with the current media
|
||||||
|
*/
|
||||||
|
public Depictions getDepiction() {
|
||||||
|
return depictions;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file description.
|
* Sets the file description.
|
||||||
* @param description the new description of the file
|
* @param description the new description of the file
|
||||||
|
|
@ -334,38 +375,6 @@ public class Media implements Parcelable {
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the width of the media.
|
|
||||||
* @return file width as an int
|
|
||||||
*/
|
|
||||||
public int getWidth() {
|
|
||||||
return width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the width of the media.
|
|
||||||
* @param width file width as an int
|
|
||||||
*/
|
|
||||||
public void setWidth(int width) {
|
|
||||||
this.width = width;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the height of the media.
|
|
||||||
* @return file height as an int
|
|
||||||
*/
|
|
||||||
public int getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the height of the media.
|
|
||||||
* @param height file height as an int
|
|
||||||
*/
|
|
||||||
public void setHeight(int height) {
|
|
||||||
this.height = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the license name of the file.
|
* Gets the license name of the file.
|
||||||
* @return license as a String
|
* @return license as a String
|
||||||
|
|
@ -417,8 +426,8 @@ public class Media implements Parcelable {
|
||||||
* @return file categories as an ArrayList of Strings
|
* @return file categories as an ArrayList of Strings
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public ArrayList<String> getCategories() {
|
public List<String> getCategories() {
|
||||||
return (ArrayList<String>) categories.clone(); // feels dirty
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -429,38 +438,7 @@ public class Media implements Parcelable {
|
||||||
* @param categories file categories as a list of Strings
|
* @param categories file categories as a list of Strings
|
||||||
*/
|
*/
|
||||||
public void setCategories(List<String> categories) {
|
public void setCategories(List<String> categories) {
|
||||||
this.categories.clear();
|
this.categories = categories;
|
||||||
this.categories.addAll(categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies (or sets) media descriptions
|
|
||||||
* @param descriptions Media descriptions
|
|
||||||
*/
|
|
||||||
void setDescriptions(Map<String, String> descriptions) {
|
|
||||||
this.descriptions.clear();
|
|
||||||
this.descriptions.putAll(descriptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets media description in preferred language
|
|
||||||
* @param preferredLanguage Language preferred
|
|
||||||
* @return Description in preferred language
|
|
||||||
*/
|
|
||||||
public String getDescription(String preferredLanguage) {
|
|
||||||
if (descriptions.containsKey(preferredLanguage)) {
|
|
||||||
// See if the requested language is there.
|
|
||||||
return descriptions.get(preferredLanguage);
|
|
||||||
} else if (descriptions.containsKey("en")) {
|
|
||||||
// Ah, English. Language of the world, until the Chinese crush us.
|
|
||||||
return descriptions.get("en");
|
|
||||||
} else if (descriptions.containsKey("default")) {
|
|
||||||
// No languages marked...
|
|
||||||
return descriptions.get("default");
|
|
||||||
} else {
|
|
||||||
// FIXME: return the first available non-English description?
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable private static Date safeParseDate(String dateStr) {
|
@Nullable private static Date safeParseDate(String dateStr) {
|
||||||
|
|
@ -473,16 +451,17 @@ public class Media implements Parcelable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set requested deletion to true
|
* Set requested deletion to true
|
||||||
|
* @param requestedDeletion
|
||||||
*/
|
*/
|
||||||
public void setRequestedDeletion(){
|
public void setRequestedDeletion(boolean requestedDeletion){
|
||||||
requestedDeletion = true;
|
this.requestedDeletion = requestedDeletion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of requested deletion
|
* Get the value of requested deletion
|
||||||
* @return boolean requestedDeletion
|
* @return boolean requestedDeletion
|
||||||
*/
|
*/
|
||||||
public boolean getRequestedDeletion(){
|
public boolean isRequestedDeletion(){
|
||||||
return requestedDeletion;
|
return requestedDeletion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,6 +474,42 @@ public class Media implements Parcelable {
|
||||||
this.license = license;
|
this.license = license;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
|
||||||
|
* This is a replacement of the previously used titles for images (titles were not multilingual)
|
||||||
|
* Also now captions replace the previous convention of using title for filename
|
||||||
|
*
|
||||||
|
* This function sets captions
|
||||||
|
* @param caption
|
||||||
|
*/
|
||||||
|
public void setCaption(String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets depictions for the current media obtained fro Wikibase API*/
|
||||||
|
public void setDepictions(Depictions depictions) {
|
||||||
|
this.depictions = depictions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalUri(@Nullable final Uri localUri) {
|
||||||
|
this.localUri = localUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUrl(final String imageUrl) {
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDateUploaded(@Nullable final Date dateUploaded) {
|
||||||
|
this.dateUploaded = dateUploaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLicenseUrl(final String licenseUrl) {
|
||||||
|
this.licenseUrl = licenseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Depictions getDepictions() {
|
||||||
|
return depictions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int describeContents() {
|
public int describeContents() {
|
||||||
|
|
@ -513,20 +528,20 @@ public class Media implements Parcelable {
|
||||||
dest.writeString(this.thumbUrl);
|
dest.writeString(this.thumbUrl);
|
||||||
dest.writeString(this.imageUrl);
|
dest.writeString(this.imageUrl);
|
||||||
dest.writeString(this.filename);
|
dest.writeString(this.filename);
|
||||||
|
dest.writeString(this.thumbnailTitle);
|
||||||
|
dest.writeString(this.caption);
|
||||||
dest.writeString(this.description);
|
dest.writeString(this.description);
|
||||||
dest.writeString(this.discussion);
|
dest.writeString(this.discussion);
|
||||||
dest.writeLong(this.dataLength);
|
dest.writeLong(this.dataLength);
|
||||||
dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1);
|
dest.writeLong(this.dateCreated != null ? this.dateCreated.getTime() : -1);
|
||||||
dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1);
|
dest.writeLong(this.dateUploaded != null ? this.dateUploaded.getTime() : -1);
|
||||||
dest.writeInt(this.width);
|
|
||||||
dest.writeInt(this.height);
|
|
||||||
dest.writeString(this.license);
|
dest.writeString(this.license);
|
||||||
dest.writeString(this.licenseUrl);
|
dest.writeString(this.licenseUrl);
|
||||||
dest.writeString(this.creator);
|
dest.writeString(this.creator);
|
||||||
|
dest.writeString(this.pageId);
|
||||||
dest.writeStringList(this.categories);
|
dest.writeStringList(this.categories);
|
||||||
|
dest.writeParcelable(this.depictions, flags);
|
||||||
dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0);
|
dest.writeByte(this.requestedDeletion ? (byte) 1 : (byte) 0);
|
||||||
dest.writeSerializable(this.descriptions);
|
|
||||||
dest.writeSerializable(this.tags);
|
|
||||||
dest.writeParcelable(this.coordinates, flags);
|
dest.writeParcelable(this.coordinates, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -535,6 +550,8 @@ public class Media implements Parcelable {
|
||||||
this.thumbUrl = in.readString();
|
this.thumbUrl = in.readString();
|
||||||
this.imageUrl = in.readString();
|
this.imageUrl = in.readString();
|
||||||
this.filename = in.readString();
|
this.filename = in.readString();
|
||||||
|
this.thumbnailTitle = in.readString();
|
||||||
|
this.caption = in.readString();
|
||||||
this.description = in.readString();
|
this.description = in.readString();
|
||||||
this.discussion = in.readString();
|
this.discussion = in.readString();
|
||||||
this.dataLength = in.readLong();
|
this.dataLength = in.readLong();
|
||||||
|
|
@ -542,15 +559,15 @@ public class Media implements Parcelable {
|
||||||
this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
|
this.dateCreated = tmpDateCreated == -1 ? null : new Date(tmpDateCreated);
|
||||||
long tmpDateUploaded = in.readLong();
|
long tmpDateUploaded = in.readLong();
|
||||||
this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
|
this.dateUploaded = tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
|
||||||
this.width = in.readInt();
|
|
||||||
this.height = in.readInt();
|
|
||||||
this.license = in.readString();
|
this.license = in.readString();
|
||||||
this.licenseUrl = in.readString();
|
this.licenseUrl = in.readString();
|
||||||
this.creator = in.readString();
|
this.creator = in.readString();
|
||||||
this.categories = in.createStringArrayList();
|
this.pageId = in.readString();
|
||||||
|
final ArrayList<String> list = new ArrayList<>();
|
||||||
|
in.readStringList(list);
|
||||||
|
this.categories=list;
|
||||||
|
in.readParcelable(Depictions.class.getClassLoader());
|
||||||
this.requestedDeletion = in.readByte() != 0;
|
this.requestedDeletion = in.readByte() != 0;
|
||||||
this.descriptions = (HashMap<String, String>) in.readSerializable();
|
|
||||||
this.tags = (HashMap<String, String>) in.readSerializable();
|
|
||||||
this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
|
this.coordinates = in.readParcelable(LatLng.class.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
||||||
|
|
||||||
import androidx.core.text.HtmlCompat;
|
import androidx.core.text.HtmlCompat;
|
||||||
|
import fr.free.nrw.commons.media.Depictions;
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.media.MediaClient;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -17,30 +19,61 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class MediaDataExtractor {
|
public class MediaDataExtractor {
|
||||||
private final MediaClient mediaClient;
|
|
||||||
|
private final MediaClient mediaClient;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MediaDataExtractor(MediaClient mediaClient) {
|
public MediaDataExtractor(final MediaClient mediaClient) {
|
||||||
this.mediaClient = mediaClient;
|
this.mediaClient = mediaClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simplified method to extract all details required to show media details.
|
* Simplified method to extract all details required to show media details.
|
||||||
* It fetches media object, deletion status and talk page for the filename
|
* It fetches media object, deletion status, talk page and captions for the filename
|
||||||
* @param filename for which the details are to be fetched
|
* @param filename for which the details are to be fetched
|
||||||
* @return full Media object with all details including deletion status and talk page
|
* @return full Media object with all details including deletion status and talk page
|
||||||
*/
|
*/
|
||||||
public Single<Media> fetchMediaDetails(String filename) {
|
public Single<Media> fetchMediaDetails(final String filename, final String pageId) {
|
||||||
Single<Media> mediaSingle = getMediaFromFileName(filename);
|
return Single.zip(getMediaFromFileName(filename),
|
||||||
Single<Boolean> pageExistsSingle = mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + filename);
|
mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + filename),
|
||||||
Single<String> discussionSingle = getDiscussion(filename);
|
getDiscussion(filename),
|
||||||
return Single.zip(mediaSingle, pageExistsSingle, discussionSingle, (media, deletionStatus, discussion) -> {
|
pageId != null ? getCaption(PAGE_ID_PREFIX + pageId)
|
||||||
media.setDiscussion(discussion);
|
: Single.just(MediaClient.NO_CAPTION),
|
||||||
if (deletionStatus) {
|
getDepictions(filename),
|
||||||
media.setRequestedDeletion();
|
this::combineToMedia);
|
||||||
}
|
}
|
||||||
return media;
|
|
||||||
});
|
@NotNull
|
||||||
|
private Media combineToMedia(final Media media, final Boolean deletionStatus, final String discussion,
|
||||||
|
final String caption, final Depictions depictions) {
|
||||||
|
media.setDiscussion(discussion);
|
||||||
|
media.setCaption(caption);
|
||||||
|
media.setDepictions(depictions);
|
||||||
|
if (deletionStatus) {
|
||||||
|
media.setRequestedDeletion(true);
|
||||||
|
}
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains captions using filename
|
||||||
|
* @param wikibaseIdentifier
|
||||||
|
*
|
||||||
|
* @return caption for the image in user's locale
|
||||||
|
* Ex: "a nice painting" (english locale) and "No Caption" in case the caption is not available for the image
|
||||||
|
*/
|
||||||
|
private Single<String> getCaption(final String wikibaseIdentifier) {
|
||||||
|
return mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch depictions from the MediaWiki API
|
||||||
|
* @param filename the filename we will return the caption for
|
||||||
|
* @return Depictions
|
||||||
|
*/
|
||||||
|
private Single<Depictions> getDepictions(final String filename) {
|
||||||
|
return mediaClient.getDepictions(filename)
|
||||||
|
.doOnError(throwable -> Timber.e(throwable, "error while fetching depictions"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,7 +81,7 @@ public class MediaDataExtractor {
|
||||||
* @param filename Eg. File:Test.jpg
|
* @param filename Eg. File:Test.jpg
|
||||||
* @return return data rich Media object
|
* @return return data rich Media object
|
||||||
*/
|
*/
|
||||||
public Single<Media> getMediaFromFileName(String filename) {
|
public Single<Media> getMediaFromFileName(final String filename) {
|
||||||
return mediaClient.getMedia(filename);
|
return mediaClient.getMedia(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +90,7 @@ public class MediaDataExtractor {
|
||||||
* @param filename
|
* @param filename
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Single<String> getDiscussion(String filename) {
|
private Single<String> getDiscussion(final String filename) {
|
||||||
return mediaClient.getPageHtml(filename.replace("File", "File talk"))
|
return mediaClient.getPageHtml(filename.replace("File", "File talk"))
|
||||||
.map(discussion -> HtmlCompat.fromHtml(discussion, HtmlCompat.FROM_HTML_MODE_LEGACY).toString())
|
.map(discussion -> HtmlCompat.fromHtml(discussion, HtmlCompat.FROM_HTML_MODE_LEGACY).toString())
|
||||||
.onErrorReturn(throwable -> {
|
.onErrorReturn(throwable -> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor.Level;
|
||||||
|
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||||
|
import org.wikipedia.dataclient.okhttp.HttpStatusException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package fr.free.nrw.commons.caching;
|
|
||||||
|
|
||||||
import com.github.varunpant.quadtree.Point;
|
|
||||||
import com.github.varunpant.quadtree.QuadTree;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class CacheController {
|
|
||||||
|
|
||||||
private final QuadTree<List<String>> quadTree;
|
|
||||||
private double x, y;
|
|
||||||
private double xMinus, xPlus, yMinus, yPlus;
|
|
||||||
|
|
||||||
private static final int EARTH_RADIUS = 6378137;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CacheController(QuadTree quadTree) {
|
|
||||||
this.quadTree = quadTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQtPoint(double decLongitude, double decLatitude) {
|
|
||||||
x = decLongitude;
|
|
||||||
y = decLatitude;
|
|
||||||
Timber.d("New QuadTree created");
|
|
||||||
Timber.d("X (longitude) value: %f, Y (latitude) value: %f", x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> findCategory() {
|
|
||||||
Point<List<String>>[] pointsFound;
|
|
||||||
//Convert decLatitude and decLongitude to a coordinate offset range
|
|
||||||
convertCoordRange();
|
|
||||||
pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus);
|
|
||||||
List<String> displayCatList = new ArrayList<>();
|
|
||||||
Timber.d("Points found in quadtree: %s", Arrays.toString(pointsFound));
|
|
||||||
|
|
||||||
if (pointsFound.length != 0) {
|
|
||||||
Timber.d("Entering for loop");
|
|
||||||
|
|
||||||
for (Point<List<String>> point : pointsFound) {
|
|
||||||
Timber.d("Nearby point: %s", point);
|
|
||||||
displayCatList = point.getValue();
|
|
||||||
Timber.d("Nearby cat: %s", point.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.d("Categories found in cache: %s", displayCatList);
|
|
||||||
} else {
|
|
||||||
Timber.d("No categories found in cache");
|
|
||||||
}
|
|
||||||
return displayCatList;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
|
|
||||||
private void convertCoordRange() {
|
|
||||||
//Position, decimal degrees
|
|
||||||
double lat = y;
|
|
||||||
double lon = x;
|
|
||||||
|
|
||||||
//offsets in meters
|
|
||||||
double offset = 100;
|
|
||||||
|
|
||||||
//Coordinate offsets in radians
|
|
||||||
double dLat = offset / EARTH_RADIUS;
|
|
||||||
double dLon = offset / (EARTH_RADIUS * Math.cos(Math.PI * lat / 180));
|
|
||||||
|
|
||||||
//OffsetPosition, decimal degrees
|
|
||||||
yPlus = lat + dLat * 180 / Math.PI;
|
|
||||||
yMinus = lat - dLat * 180 / Math.PI;
|
|
||||||
xPlus = lon + dLon * 180 / Math.PI;
|
|
||||||
xMinus = lon - dLon * 180 / Math.PI;
|
|
||||||
Timber.d("Search within: xMinus=%s, yMinus=%s, xPlus=%s, yPlus=%s",
|
|
||||||
xMinus, yMinus, xPlus, yPlus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -27,19 +23,19 @@ public class CategoriesModel{
|
||||||
private final CategoryClient categoryClient;
|
private final CategoryClient categoryClient;
|
||||||
private final CategoryDao categoryDao;
|
private final CategoryDao categoryDao;
|
||||||
private final JsonKvStore directKvStore;
|
private final JsonKvStore directKvStore;
|
||||||
|
private final GpsCategoryModel gpsCategoryModel;
|
||||||
|
|
||||||
private HashMap<String, ArrayList<String>> categoriesCache;
|
|
||||||
private List<CategoryItem> selectedCategories;
|
private List<CategoryItem> selectedCategories;
|
||||||
|
|
||||||
@Inject GpsCategoryModel gpsCategoryModel;
|
|
||||||
@Inject
|
@Inject
|
||||||
public CategoriesModel(CategoryClient categoryClient,
|
public CategoriesModel(CategoryClient categoryClient,
|
||||||
CategoryDao categoryDao,
|
CategoryDao categoryDao,
|
||||||
@Named("default_preferences") JsonKvStore directKvStore) {
|
@Named("default_preferences") JsonKvStore directKvStore,
|
||||||
|
final GpsCategoryModel gpsCategoryModel) {
|
||||||
this.categoryClient = categoryClient;
|
this.categoryClient = categoryClient;
|
||||||
this.categoryDao = categoryDao;
|
this.categoryDao = categoryDao;
|
||||||
this.directKvStore = directKvStore;
|
this.directKvStore = directKvStore;
|
||||||
this.categoriesCache = new HashMap<>();
|
this.gpsCategoryModel = gpsCategoryModel;
|
||||||
this.selectedCategories = new ArrayList<>();
|
this.selectedCategories = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,10 +90,6 @@ public class CategoriesModel{
|
||||||
categoryDao.save(category);
|
categoryDao.save(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean cacheContainsKey(String term) {
|
|
||||||
return categoriesCache.containsKey(term);
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regional category search
|
* Regional category search
|
||||||
|
|
@ -108,20 +100,18 @@ public class CategoriesModel{
|
||||||
public Observable<CategoryItem> searchAll(String term, List<String> imageTitleList) {
|
public Observable<CategoryItem> searchAll(String term, List<String> imageTitleList) {
|
||||||
//If query text is empty, show him category based on gps and title and recent searches
|
//If query text is empty, show him category based on gps and title and recent searches
|
||||||
if (TextUtils.isEmpty(term)) {
|
if (TextUtils.isEmpty(term)) {
|
||||||
Observable<CategoryItem> categoryItemObservable = gpsCategories()
|
Observable<CategoryItem> categoryItemObservable =
|
||||||
.concatWith(titleCategories(imageTitleList));
|
Observable.concat(gpsCategories(), titleCategories(imageTitleList));
|
||||||
if (hasDirectCategories()) {
|
if (hasDirectCategories()) {
|
||||||
categoryItemObservable.concatWith(directCategories().concatWith(recentCategories()));
|
return Observable.concat(
|
||||||
|
categoryItemObservable,
|
||||||
|
directCategories(),
|
||||||
|
recentCategories()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return categoryItemObservable;
|
return categoryItemObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if user types in something that is in cache, return cached category
|
|
||||||
if (cacheContainsKey(term)) {
|
|
||||||
return Observable.fromIterable(getCachedCategories(term))
|
|
||||||
.map(name -> new CategoryItem(name, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
//otherwise, search API for matching categories
|
//otherwise, search API for matching categories
|
||||||
//term passed as lower case to make search case-insensitive(taking only lower case for everything)
|
//term passed as lower case to make search case-insensitive(taking only lower case for everything)
|
||||||
return categoryClient
|
return categoryClient
|
||||||
|
|
@ -130,15 +120,6 @@ public class CategoriesModel{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns cached categories
|
|
||||||
* @param term
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private ArrayList<String> getCachedCategories(String term) {
|
|
||||||
return categoriesCache.get(term);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if we have a category in DirectKV Store
|
* Returns if we have a category in DirectKV Store
|
||||||
* @return
|
* @return
|
||||||
|
|
@ -256,7 +237,6 @@ public class CategoriesModel{
|
||||||
* Cleanup the existing in memory cache's
|
* Cleanup the existing in memory cache's
|
||||||
*/
|
*/
|
||||||
public void cleanUp() {
|
public void cleanUp() {
|
||||||
this.categoriesCache.clear();
|
|
||||||
this.selectedCategories.clear();
|
this.selectedCategories.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -12,15 +16,7 @@ import android.widget.ListAdapter;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import dagger.android.support.DaggerFragment;
|
import dagger.android.support.DaggerFragment;
|
||||||
|
|
@ -33,17 +29,22 @@ import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays images for a particular category with load more on scrolling incorporated
|
* Displays images for a particular category with load more on scrolling incorporated
|
||||||
*/
|
*/
|
||||||
public class CategoryImagesListFragment extends DaggerFragment {
|
public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
|
|
||||||
private static int TIMEOUT_SECONDS = 15;
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
/**
|
||||||
|
* counts the total number of items loaded from the API
|
||||||
|
*/
|
||||||
|
private int mediaSize = 0;
|
||||||
|
|
||||||
private GridViewAdapter gridAdapter;
|
private GridViewAdapter gridAdapter;
|
||||||
|
|
||||||
|
|
@ -256,6 +257,38 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
statusTextView.setVisibility(GONE);
|
statusTextView.setVisibility(GONE);
|
||||||
|
for (Media m : collection) {
|
||||||
|
final String pageId = m.getPageId();
|
||||||
|
if (pageId != null) {
|
||||||
|
replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||||
|
* else show filename
|
||||||
|
*/
|
||||||
|
public void replaceTitlesWithCaptions(String wikibaseIdentifier, int i) {
|
||||||
|
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(subscriber -> {
|
||||||
|
handleLabelforImage(subscriber, i);
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If caption is available for the image, then modify grid adapter
|
||||||
|
* to show captions
|
||||||
|
*/
|
||||||
|
private void handleLabelforImage(String s, int position) {
|
||||||
|
if (!s.trim().equals(getString(R.string.detail_caption_empty))) {
|
||||||
|
gridAdapter.getItem(position).setThumbnailTitle(s);
|
||||||
|
gridAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -55,7 +57,7 @@ public class GridViewAdapter extends ArrayAdapter {
|
||||||
data = new ArrayList<>();
|
data = new ArrayList<>();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (data.size() <= 0) {
|
if (data.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String fileName = data.get(0).getFilename();
|
String fileName = data.get(0).getFilename();
|
||||||
|
|
@ -86,12 +88,22 @@ public class GridViewAdapter extends ArrayAdapter {
|
||||||
SimpleDraweeView imageView = convertView.findViewById(R.id.categoryImageView);
|
SimpleDraweeView imageView = convertView.findViewById(R.id.categoryImageView);
|
||||||
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
||||||
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
||||||
fileName.setText(item.getDisplayTitle());
|
fileName.setText(item.getThumbnailTitle());
|
||||||
setAuthorView(item, author);
|
setAuthorView(item, author);
|
||||||
imageView.setImageURI(item.getThumbUrl());
|
imageView.setImageURI(item.getThumbUrl());
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the Media item at the given position
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Media getItem(int position) {
|
||||||
|
return data.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows author information if its present
|
* Shows author information if its present
|
||||||
* @param item
|
* @param item
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,22 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.StringDef;
|
|
||||||
import androidx.room.Entity;
|
import androidx.room.Entity;
|
||||||
import androidx.room.PrimaryKey;
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import fr.free.nrw.commons.upload.WikidataPlace;
|
||||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
|
||||||
|
|
||||||
@Entity(tableName = "contribution")
|
@Entity(tableName = "contribution")
|
||||||
public class Contribution extends Media {
|
public class Contribution extends Media {
|
||||||
|
|
||||||
//{{According to Exif data|2009-01-09}}
|
|
||||||
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
|
|
||||||
|
|
||||||
//2009-01-09 → 9 January 2009
|
|
||||||
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
|
|
||||||
|
|
||||||
// No need to be bitwise - they're mutually exclusive
|
// No need to be bitwise - they're mutually exclusive
|
||||||
public static final int STATE_COMPLETED = -1;
|
public static final int STATE_COMPLETED = -1;
|
||||||
|
|
@ -38,219 +24,133 @@ public class Contribution extends Media {
|
||||||
public static final int STATE_QUEUED = 2;
|
public static final int STATE_QUEUED = 2;
|
||||||
public static final int STATE_IN_PROGRESS = 3;
|
public static final int STATE_IN_PROGRESS = 3;
|
||||||
|
|
||||||
@Retention(SOURCE)
|
|
||||||
@StringDef({SOURCE_CAMERA, SOURCE_GALLERY, SOURCE_EXTERNAL})
|
|
||||||
public @interface FileSource {}
|
|
||||||
|
|
||||||
public static final String SOURCE_CAMERA = "camera";
|
|
||||||
public static final String SOURCE_GALLERY = "gallery";
|
|
||||||
public static final String SOURCE_EXTERNAL = "external";
|
|
||||||
@PrimaryKey (autoGenerate = true)
|
@PrimaryKey (autoGenerate = true)
|
||||||
@NonNull
|
private long _id;
|
||||||
public long _id;
|
private int state;
|
||||||
public Uri contentUri;
|
private long transferred;
|
||||||
public String source;
|
private String decimalCoords;
|
||||||
public String editSummary;
|
private String dateCreatedSource;
|
||||||
public int state;
|
private WikidataPlace wikidataPlace;
|
||||||
public long transferred;
|
/**
|
||||||
public String decimalCoords;
|
* Each depiction loaded in depictions activity is associated with a wikidata entity id,
|
||||||
public boolean isMultiple;
|
* this Id is in turn used to upload depictions to wikibase
|
||||||
public String wikiDataEntityId;
|
*/
|
||||||
public String wikiItemName;
|
private List<DepictedItem> depictedItems = new ArrayList<>();
|
||||||
private String p18Value;
|
private String mimeType;
|
||||||
public Uri contentProviderUri;
|
/**
|
||||||
public String dateCreatedSource;
|
* This hasmap stores the list of multilingual captions, where
|
||||||
|
* key of the HashMap is the language and value is the caption in the corresponding language
|
||||||
|
* Ex: key = "en", value: "<caption in short in English>"
|
||||||
|
* key = "de" , value: "<caption in german>"
|
||||||
|
*/
|
||||||
|
private Map<String, String> captions = new HashMap<>();
|
||||||
|
|
||||||
public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
|
public Contribution() {
|
||||||
Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords) {
|
|
||||||
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
|
|
||||||
this.decimalCoords = decimalCoords;
|
|
||||||
this.editSummary = editSummary;
|
|
||||||
this.dateCreatedSource = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
|
public Contribution(final UploadItem item, final SessionManager sessionManager,
|
||||||
Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords, int state) {
|
final List<DepictedItem> depictedItems, final List<String> categories) {
|
||||||
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
|
super(item.getMediaUri(),
|
||||||
this.decimalCoords = decimalCoords;
|
item.getFileName(),
|
||||||
this.editSummary = editSummary;
|
UploadMediaDetail.formatList(item.getUploadMediaDetails()),
|
||||||
this.dateCreatedSource = "";
|
sessionManager.getAuthorName(),
|
||||||
this.state=state;
|
categories);
|
||||||
|
captions = UploadMediaDetail.formatCaptions(item.getUploadMediaDetails());
|
||||||
|
decimalCoords = item.getGpsCoords().getDecimalCoords();
|
||||||
|
dateCreatedSource = "";
|
||||||
|
this.depictedItems = depictedItems;
|
||||||
|
wikidataPlace = WikidataPlace.from(item.getPlace());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Contribution(final MwQueryLogEvent queryLogEvent, final String user) {
|
||||||
|
super(queryLogEvent.title(),queryLogEvent.date(), user);
|
||||||
|
decimalCoords = "";
|
||||||
|
dateCreatedSource = "";
|
||||||
|
state = STATE_COMPLETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDateCreatedSource(final String dateCreatedSource) {
|
||||||
public void setDateCreatedSource(String dateCreatedSource) {
|
|
||||||
this.dateCreatedSource = dateCreatedSource;
|
this.dateCreatedSource = dateCreatedSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getMultiple() {
|
public String getDateCreatedSource() {
|
||||||
return isMultiple;
|
return dateCreatedSource;
|
||||||
}
|
|
||||||
|
|
||||||
public void setMultiple(boolean multiple) {
|
|
||||||
isMultiple = multiple;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getTransferred() {
|
public long getTransferred() {
|
||||||
return transferred;
|
return transferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTransferred(long transferred) {
|
public void setTransferred(final long transferred) {
|
||||||
this.transferred = transferred;
|
this.transferred = transferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEditSummary() {
|
|
||||||
return editSummary != null ? editSummary : CommonsApplication.DEFAULT_EDIT_SUMMARY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getContentUri() {
|
|
||||||
return contentUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContentUri(Uri contentUri) {
|
|
||||||
this.contentUri = contentUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getState() {
|
public int getState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(int state) {
|
public void setState(final int state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDateUploaded(Date date) {
|
/**
|
||||||
this.dateUploaded = date;
|
* @return array list of entityids for the depictions
|
||||||
|
*/
|
||||||
|
public List<DepictedItem> getDepictedItems() {
|
||||||
|
return depictedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPageContents(Context applicationContext) {
|
public void setWikidataPlace(final WikidataPlace wikidataPlace) {
|
||||||
StringBuilder buffer = new StringBuilder();
|
this.wikidataPlace = wikidataPlace;
|
||||||
buffer
|
}
|
||||||
.append("== {{int:filedesc}} ==\n")
|
|
||||||
.append("{{Information\n")
|
|
||||||
.append("|description=").append(getDescription()).append("\n")
|
|
||||||
.append("|source=").append("{{own}}\n")
|
|
||||||
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
|
|
||||||
|
|
||||||
String templatizedCreatedDate = getTemplatizedCreatedDate();
|
public WikidataPlace getWikidataPlace() {
|
||||||
if (!StringUtils.isBlank(templatizedCreatedDate)) {
|
return wikidataPlace;
|
||||||
buffer.append("|date=").append(templatizedCreatedDate);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
buffer.append("}}").append("\n");
|
public long get_id() {
|
||||||
|
return _id;
|
||||||
|
}
|
||||||
|
|
||||||
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
|
public void set_id(final long _id) {
|
||||||
if (decimalCoords != null) {
|
this._id = _id;
|
||||||
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
buffer.append("== {{int:license-header}} ==\n")
|
public String getDecimalCoords() {
|
||||||
.append(licenseTemplateFor(getLicense())).append("\n\n")
|
return decimalCoords;
|
||||||
.append("{{Uploaded from Mobile|platform=Android|version=")
|
}
|
||||||
.append(ConfigUtils.getVersionNameWithSha(applicationContext)).append("}}\n");
|
|
||||||
if(categories!=null&&categories.size()!=0) {
|
public void setDecimalCoords(final String decimalCoords) {
|
||||||
for (int i = 0; i < categories.size(); i++) {
|
this.decimalCoords = decimalCoords;
|
||||||
String category = categories.get(i);
|
}
|
||||||
buffer.append("\n[[Category:").append(category).append("]]");
|
|
||||||
}
|
public void setDepictedItems(final List<DepictedItem> depictedItems) {
|
||||||
}
|
this.depictedItems = depictedItems;
|
||||||
else
|
}
|
||||||
buffer.append("{{subst:unc}}");
|
|
||||||
return buffer.toString();
|
public void setMimeType(String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
|
* Captions are a feature part of Structured data. They are meant to store short, multilingual descriptions about files
|
||||||
* @return
|
* This is a replacement of the previously used titles for images (titles were not multilingual)
|
||||||
|
* Also now captions replace the previous convention of using title for filename
|
||||||
|
*
|
||||||
|
* key of the HashMap is the language and value is the caption in the corresponding language
|
||||||
|
*
|
||||||
|
* returns list of captions stored in hashmap
|
||||||
*/
|
*/
|
||||||
private String getTemplatizedCreatedDate() {
|
public Map<String, String> getCaptions() {
|
||||||
if (dateCreated != null) {
|
return captions;
|
||||||
java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd");
|
|
||||||
if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) {
|
|
||||||
return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, dateFormat.format(dateCreated)) + "\n";
|
|
||||||
} else {
|
|
||||||
return String.format(Locale.ENGLISH, TEMPLATE_DATA_OTHER_SOURCE, dateFormat.format(dateCreated)) + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setCaptions(Map<String, String> captions) {
|
||||||
public void setFilename(String filename) {
|
this.captions = captions;
|
||||||
this.filename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImageUrl(String imageUrl) {
|
|
||||||
this.imageUrl = imageUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Contribution() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSource() {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSource(String source) {
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private String licenseTemplateFor(String license) {
|
|
||||||
switch (license) {
|
|
||||||
case Prefs.Licenses.CC_BY_3:
|
|
||||||
return "{{self|cc-by-3.0}}";
|
|
||||||
case Prefs.Licenses.CC_BY_4:
|
|
||||||
return "{{self|cc-by-4.0}}";
|
|
||||||
case Prefs.Licenses.CC_BY_SA_3:
|
|
||||||
return "{{self|cc-by-sa-3.0}}";
|
|
||||||
case Prefs.Licenses.CC_BY_SA_4:
|
|
||||||
return "{{self|cc-by-sa-4.0}}";
|
|
||||||
case Prefs.Licenses.CC0:
|
|
||||||
return "{{self|cc-zero}}";
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeException("Unrecognized license value: " + license);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWikiDataEntityId() {
|
|
||||||
return wikiDataEntityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWikiItemName() {
|
|
||||||
return wikiItemName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
|
|
||||||
* using the setter method
|
|
||||||
* @param wikiDataEntityId wikiDataEntityId
|
|
||||||
*/
|
|
||||||
public void setWikiDataEntityId(String wikiDataEntityId) {
|
|
||||||
this.wikiDataEntityId = wikiDataEntityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWikiItemName(String wikiItemName) {
|
|
||||||
this.wikiItemName = wikiItemName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getP18Value() {
|
|
||||||
return p18Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the corresponding image property of wiki entity is known as in case of nearby uploads,
|
|
||||||
* it can be set using the setter method
|
|
||||||
* @param p18Value p18 value, image property of the wikidata item
|
|
||||||
*/
|
|
||||||
public void setP18Value(String p18Value) {
|
|
||||||
this.p18Value = p18Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setContentProviderUri(Uri contentProviderUri) {
|
|
||||||
this.contentProviderUri = contentProviderUri;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -259,48 +159,34 @@ public class Contribution extends Media {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(final Parcel dest, final int flags) {
|
||||||
super.writeToParcel(dest, flags);
|
super.writeToParcel(dest, flags);
|
||||||
dest.writeLong(this._id);
|
dest.writeLong(_id);
|
||||||
dest.writeParcelable(this.contentUri, flags);
|
dest.writeInt(state);
|
||||||
dest.writeString(this.source);
|
dest.writeLong(transferred);
|
||||||
dest.writeString(this.editSummary);
|
dest.writeString(decimalCoords);
|
||||||
dest.writeInt(this.state);
|
dest.writeString(dateCreatedSource);
|
||||||
dest.writeLong(this.transferred);
|
dest.writeSerializable((HashMap) captions);
|
||||||
dest.writeString(this.decimalCoords);
|
|
||||||
dest.writeByte(this.isMultiple ? (byte) 1 : (byte) 0);
|
|
||||||
dest.writeString(this.wikiDataEntityId);
|
|
||||||
dest.writeString(this.wikiItemName);
|
|
||||||
dest.writeString(this.p18Value);
|
|
||||||
dest.writeParcelable(this.contentProviderUri, flags);
|
|
||||||
dest.writeString(this.dateCreatedSource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Contribution(Parcel in) {
|
protected Contribution(final Parcel in) {
|
||||||
super(in);
|
super(in);
|
||||||
this._id = in.readLong();
|
_id = in.readLong();
|
||||||
this.contentUri = in.readParcelable(Uri.class.getClassLoader());
|
state = in.readInt();
|
||||||
this.source = in.readString();
|
transferred = in.readLong();
|
||||||
this.editSummary = in.readString();
|
decimalCoords = in.readString();
|
||||||
this.state = in.readInt();
|
dateCreatedSource = in.readString();
|
||||||
this.transferred = in.readLong();
|
captions = (HashMap<String, String>) in.readSerializable();
|
||||||
this.decimalCoords = in.readString();
|
|
||||||
this.isMultiple = in.readByte() != 0;
|
|
||||||
this.wikiDataEntityId = in.readString();
|
|
||||||
this.wikiItemName = in.readString();
|
|
||||||
this.p18Value = in.readString();
|
|
||||||
this.contentProviderUri = in.readParcelable(Uri.class.getClassLoader());
|
|
||||||
this.dateCreatedSource = in.readString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<Contribution> CREATOR = new Creator<Contribution>() {
|
public static final Creator<Contribution> CREATOR = new Creator<Contribution>() {
|
||||||
@Override
|
@Override
|
||||||
public Contribution createFromParcel(Parcel source) {
|
public Contribution createFromParcel(final Parcel source) {
|
||||||
return new Contribution(source);
|
return new Contribution(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Contribution[] newArray(int size) {
|
public Contribution[] newArray(final int size) {
|
||||||
return new Contribution[size];
|
return new Contribution[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,13 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||||
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.filepicker.DefaultCallback;
|
import fr.free.nrw.commons.filepicker.DefaultCallback;
|
||||||
import fr.free.nrw.commons.filepicker.FilePicker;
|
import fr.free.nrw.commons.filepicker.FilePicker;
|
||||||
|
|
@ -23,12 +17,11 @@ import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.upload.UploadActivity;
|
import fr.free.nrw.commons.upload.UploadActivity;
|
||||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import java.util.ArrayList;
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
|
import java.util.List;
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
|
import javax.inject.Inject;
|
||||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
import javax.inject.Named;
|
||||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
|
import javax.inject.Singleton;
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ContributionController {
|
public class ContributionController {
|
||||||
|
|
@ -109,7 +102,7 @@ public class ContributionController {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onImagesPicked(@NonNull List<UploadableFile> imagesFiles, FilePicker.ImageSource source, int type) {
|
public void onImagesPicked(@NonNull List<UploadableFile> imagesFiles, FilePicker.ImageSource source, int type) {
|
||||||
Intent intent = handleImagesPicked(activity, imagesFiles, getSourceFromImageSource(source));
|
Intent intent = handleImagesPicked(activity, imagesFiles);
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -125,11 +118,9 @@ public class ContributionController {
|
||||||
* Attaches place object for nearby uploads
|
* Attaches place object for nearby uploads
|
||||||
*/
|
*/
|
||||||
private Intent handleImagesPicked(Context context,
|
private Intent handleImagesPicked(Context context,
|
||||||
List<UploadableFile> imagesFiles,
|
List<UploadableFile> imagesFiles) {
|
||||||
String source) {
|
|
||||||
Intent shareIntent = new Intent(context, UploadActivity.class);
|
Intent shareIntent = new Intent(context, UploadActivity.class);
|
||||||
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
|
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
|
||||||
shareIntent.putExtra(EXTRA_SOURCE, source);
|
|
||||||
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, new ArrayList<>(imagesFiles));
|
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, new ArrayList<>(imagesFiles));
|
||||||
Place place = defaultKvStore.getJson(PLACE_OBJECT, Place.class);
|
Place place = defaultKvStore.getJson(PLACE_OBJECT, Place.class);
|
||||||
if (place != null) {
|
if (place != null) {
|
||||||
|
|
@ -139,13 +130,4 @@ public class ContributionController {
|
||||||
return shareIntent;
|
return shareIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get image upload source
|
|
||||||
*/
|
|
||||||
private String getSourceFromImageSource(FilePicker.ImageSource source) {
|
|
||||||
if (source.equals(FilePicker.ImageSource.CAMERA_IMAGE)) {
|
|
||||||
return SOURCE_CAMERA;
|
|
||||||
}
|
|
||||||
return SOURCE_GALLERY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,10 @@ import androidx.room.Insert;
|
||||||
import androidx.room.OnConflictStrategy;
|
import androidx.room.OnConflictStrategy;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import androidx.room.Transaction;
|
import androidx.room.Transaction;
|
||||||
|
|
||||||
import androidx.room.Update;
|
import androidx.room.Update;
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class ContributionDao {
|
public abstract class ContributionDao {
|
||||||
|
|
@ -40,9 +37,6 @@ public abstract class ContributionDao {
|
||||||
@Delete
|
@Delete
|
||||||
public abstract Single<Integer> delete(Contribution contribution);
|
public abstract Single<Integer> delete(Contribution contribution);
|
||||||
|
|
||||||
@Query("SELECT * from contribution WHERE contentProviderUri=:uri")
|
|
||||||
public abstract List<Contribution> getContributionWithUri(String uri);
|
|
||||||
|
|
||||||
@Query("SELECT * from contribution WHERE filename=:fileName")
|
@Query("SELECT * from contribution WHERE filename=:fileName")
|
||||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,34 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
|
||||||
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
|
||||||
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||||
import java.util.HashMap;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private static final long TIMEOUT_SECONDS = 15;
|
||||||
private final Callback callback;
|
private final Callback callback;
|
||||||
@BindView(R.id.contributionImage)
|
@BindView(R.id.contributionImage)
|
||||||
SimpleDraweeView imageView;
|
SimpleDraweeView imageView;
|
||||||
|
|
@ -37,20 +38,26 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
@BindView(R.id.contributionProgress) ProgressBar progressView;
|
@BindView(R.id.contributionProgress) ProgressBar progressView;
|
||||||
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
|
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
|
||||||
|
|
||||||
|
|
||||||
private int position;
|
private int position;
|
||||||
private Contribution contribution;
|
private Contribution contribution;
|
||||||
private Random random = new Random();
|
private Random random = new Random();
|
||||||
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
private final MediaClient mediaClient;
|
||||||
|
|
||||||
ContributionViewHolder(View parent, Callback callback) {
|
ContributionViewHolder(View parent, Callback callback,
|
||||||
|
MediaClient mediaClient) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
this.mediaClient = mediaClient;
|
||||||
ButterKnife.bind(this, parent);
|
ButterKnife.bind(this, parent);
|
||||||
this.callback=callback;
|
this.callback=callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(int position, Contribution contribution) {
|
public void init(int position, Contribution contribution) {
|
||||||
this.contribution = contribution;
|
this.contribution = contribution;
|
||||||
|
fetchAndDisplayCaption(contribution);
|
||||||
this.position = position;
|
this.position = position;
|
||||||
String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri());
|
String imageSource = chooseImageSource(contribution.getThumbUrl(), contribution.getLocalUri());
|
||||||
if (!TextUtils.isEmpty(imageSource)) {
|
if (!TextUtils.isEmpty(imageSource)) {
|
||||||
final ImageRequest imageRequest =
|
final ImageRequest imageRequest =
|
||||||
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
||||||
|
|
@ -58,7 +65,6 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
.build();
|
.build();
|
||||||
imageView.setImageRequest(imageRequest);
|
imageView.setImageRequest(imageRequest);
|
||||||
}
|
}
|
||||||
titleView.setText(contribution.getDisplayTitle());
|
|
||||||
|
|
||||||
seqNumView.setText(String.valueOf(position + 1));
|
seqNumView.setText(String.valueOf(position + 1));
|
||||||
seqNumView.setVisibility(View.VISIBLE);
|
seqNumView.setVisibility(View.VISIBLE);
|
||||||
|
|
@ -97,6 +103,38 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In contributions first we show the title for the image stored in cache,
|
||||||
|
* then we fetch captions associated with the image and replace title on the thumbnail with caption
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
private void fetchAndDisplayCaption(Contribution contribution) {
|
||||||
|
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
|
||||||
|
titleView.setText(contribution.getDisplayTitle());
|
||||||
|
} else {
|
||||||
|
final String pageId = contribution.getPageId();
|
||||||
|
if (pageId != null) {
|
||||||
|
Timber.d("Fetching caption for %s", contribution.getFilename());
|
||||||
|
String wikibaseMediaId = PAGE_ID_PREFIX
|
||||||
|
+ pageId; // Create Wikibase media id from the page id. Example media id: M80618155 for https://commons.wikimedia.org/wiki/File:Tantanmen.jpeg with has the pageid 80618155
|
||||||
|
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseMediaId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(subscriber -> {
|
||||||
|
if (!subscriber.trim().equals(MediaClient.NO_CAPTION)) {
|
||||||
|
titleView.setText(subscriber);
|
||||||
|
} else {
|
||||||
|
titleView.setText(contribution.getDisplayTitle());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
titleView.setText(contribution.getDisplayTitle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the image source for the image view, first preference is given to thumbUrl if that is
|
* Returns the image source for the image view, first preference is given to thumbUrl if that is
|
||||||
* null, moves to local uri and if both are null return null
|
* null, moves to local uri and if both are null return null
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
||||||
|
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
|
||||||
|
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
@ -12,21 +16,12 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
|
||||||
import fr.free.nrw.commons.MediaDataExtractor;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
|
|
@ -60,12 +55,11 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
|
||||||
import static fr.free.nrw.commons.contributions.MainActivity.CONTRIBUTIONS_TAB_POSITION;
|
|
||||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
|
||||||
|
|
||||||
public class ContributionsFragment
|
public class ContributionsFragment
|
||||||
extends CommonsDaggerSupportFragment
|
extends CommonsDaggerSupportFragment
|
||||||
implements
|
implements
|
||||||
|
|
@ -221,7 +215,7 @@ public class ContributionsFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fetchMediaUriFor(Contribution contribution) {
|
public void fetchMediaUriFor(Contribution contribution) {
|
||||||
Timber.d("Fetching thumbnail for %s", contribution.filename);
|
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
|
||||||
contributionsPresenter.fetchMediaDetails(contribution);
|
contributionsPresenter.fetchMediaDetails(contribution);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,28 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents The View Adapter for the List of Contributions
|
* Represents The View Adapter for the List of Contributions
|
||||||
*/
|
*/
|
||||||
public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionViewHolder> {
|
public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionViewHolder> {
|
||||||
|
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
private final MediaClient mediaClient;
|
||||||
private List<Contribution> contributions;
|
private List<Contribution> contributions;
|
||||||
|
|
||||||
public ContributionsListAdapter(Callback callback) {
|
public ContributionsListAdapter(Callback callback,
|
||||||
|
MediaClient mediaClient) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.mediaClient = mediaClient;
|
||||||
contributions = new ArrayList<>();
|
contributions = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +35,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
||||||
public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
ContributionViewHolder viewHolder = new ContributionViewHolder(
|
ContributionViewHolder viewHolder = new ContributionViewHolder(
|
||||||
LayoutInflater.from(parent.getContext())
|
LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.layout_contribution, parent, false), callback);
|
.inflate(R.layout.layout_contribution, parent, false), callback, mediaClient);
|
||||||
return viewHolder;
|
return viewHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
return contributions.get(position)._id;
|
return contributions.get(position).get_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -10,31 +13,24 @@ import android.view.animation.AnimationUtils;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import static android.view.View.GONE;
|
import java.util.ArrayList;
|
||||||
import static android.view.View.VISIBLE;
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by root on 01.06.2018.
|
* Created by root on 01.06.2018.
|
||||||
|
|
@ -60,6 +56,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
@Inject @Named("default_preferences") JsonKvStore kvStore;
|
@Inject @Named("default_preferences") JsonKvStore kvStore;
|
||||||
@Inject ContributionController controller;
|
@Inject ContributionController controller;
|
||||||
|
@Inject MediaClient mediaClient;
|
||||||
|
|
||||||
private Animation fab_close;
|
private Animation fab_close;
|
||||||
private Animation fab_open;
|
private Animation fab_open;
|
||||||
|
|
@ -89,7 +86,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initAdapter() {
|
private void initAdapter() {
|
||||||
adapter = new ContributionsListAdapter(callback);
|
adapter = new ContributionsListAdapter(callback, mediaClient);
|
||||||
adapter.setHasStableIds(true);
|
adapter.setHasStableIds(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,30 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.DataSetObserver;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.MediaDataExtractor;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
|
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
|
||||||
import fr.free.nrw.commons.db.AppDatabase;
|
import fr.free.nrw.commons.db.AppDatabase;
|
||||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||||
import fr.free.nrw.commons.mwapi.UserClient;
|
import fr.free.nrw.commons.mwapi.UserClient;
|
||||||
import fr.free.nrw.commons.utils.ExecutorUtils;
|
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Scheduler;
|
||||||
import io.reactivex.SingleObserver;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The presenter class for Contributions
|
* The presenter class for Contributions
|
||||||
*/
|
*/
|
||||||
|
|
@ -105,12 +90,7 @@ public class ContributionsPresenter implements UserActionListener {
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.doOnNext(mwQueryLogEvent -> Timber.d("Received image %s", mwQueryLogEvent.title()))
|
.doOnNext(mwQueryLogEvent -> Timber.d("Received image %s", mwQueryLogEvent.title()))
|
||||||
.filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted()).doOnNext(mwQueryLogEvent -> Timber.d("Image %s passed filters", mwQueryLogEvent.title()))
|
.filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted()).doOnNext(mwQueryLogEvent -> Timber.d("Image %s passed filters", mwQueryLogEvent.title()))
|
||||||
.map(image -> {
|
.map(image -> new Contribution(image, user))
|
||||||
Contribution contribution = new Contribution(null, null, image.title(),
|
|
||||||
"", -1, image.date(), image.date(), user,
|
|
||||||
"", "", STATE_COMPLETED);
|
|
||||||
return contribution;
|
|
||||||
})
|
|
||||||
.toList()
|
.toList()
|
||||||
.subscribe(this::saveContributionsToDB, error -> {
|
.subscribe(this::saveContributionsToDB, error -> {
|
||||||
Timber.e("Failed to fetch contributions: %s", error.getMessage());
|
Timber.e("Failed to fetch contributions: %s", error.getMessage());
|
||||||
|
|
@ -197,11 +177,11 @@ public class ContributionsPresenter implements UserActionListener {
|
||||||
@Override
|
@Override
|
||||||
public void fetchMediaDetails(Contribution contribution) {
|
public void fetchMediaDetails(Contribution contribution) {
|
||||||
compositeDisposable.add(mediaDataExtractor
|
compositeDisposable.add(mediaDataExtractor
|
||||||
.getMediaFromFileName(contribution.filename)
|
.getMediaFromFileName(contribution.getFilename())
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(media -> {
|
.subscribe(media -> {
|
||||||
contribution.thumbUrl=media.thumbUrl;
|
contribution.setThumbUrl(media.getThumbUrl());
|
||||||
updateContribution(contribution);
|
updateContribution(contribution);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package fr.free.nrw.commons.db;
|
|
||||||
|
|
||||||
import androidx.room.Database;
|
|
||||||
import androidx.room.RoomDatabase;
|
|
||||||
import androidx.room.TypeConverters;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
|
||||||
|
|
||||||
@Database(entities = {Contribution.class}, version = 1, exportSchema = false)
|
|
||||||
@TypeConverters({Converters.class})
|
|
||||||
abstract public class AppDatabase extends RoomDatabase {
|
|
||||||
public abstract ContributionDao getContributionDao();
|
|
||||||
}
|
|
||||||
17
app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
Normal file
17
app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fr.free.nrw.commons.db
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionDao
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The database for accessing the respective DAOs
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Database(entities = [Contribution::class], version = 1, exportSchema = false)
|
||||||
|
@TypeConverters(Converters::class)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun contributionDao(): ContributionDao
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
package fr.free.nrw.commons.db;
|
package fr.free.nrw.commons.db;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.room.TypeConverter;
|
import androidx.room.TypeConverter;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import org.wikipedia.json.GsonUtil;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.media.Depictions;
|
||||||
|
import fr.free.nrw.commons.upload.WikidataPlace;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class supplies converters to write/read types to/from the database.
|
||||||
|
*/
|
||||||
public class Converters {
|
public class Converters {
|
||||||
|
|
||||||
public static Gson getGson() {
|
public static Gson getGson() {
|
||||||
|
|
@ -44,33 +44,74 @@ public class Converters {
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static String listObjectToString(ArrayList<String> objectList) {
|
public static String listObjectToString(List<String> objectList) {
|
||||||
return objectList == null ? null : getGson().toJson(objectList);
|
return writeObjectToString(objectList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static ArrayList<String> stringToArrayListObject(String objectList) {
|
public static List<String> stringToListObject(String objectList) {
|
||||||
return objectList == null ? null : getGson().fromJson(objectList,new TypeToken<ArrayList<String>>(){}.getType());
|
return readObjectWithTypeToken(objectList, new TypeToken<List<String>>() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static String mapObjectToString(HashMap<String,String> objectList) {
|
public static String mapObjectToString(Map<String,String> objectList) {
|
||||||
return objectList == null ? null : getGson().toJson(objectList);
|
return writeObjectToString(objectList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static HashMap<String,String> stringToMap(String objectList) {
|
public static Map<String,String> stringToMap(String objectList) {
|
||||||
return objectList == null ? null : getGson().fromJson(objectList,new TypeToken<HashMap<String,String>>(){}.getType());
|
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static String latlngObjectToString(LatLng latlng) {
|
public static String latlngObjectToString(LatLng latlng) {
|
||||||
return latlng == null ? null : getGson().toJson(latlng);
|
return writeObjectToString(latlng);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
public static LatLng stringToLatLng(String objectList) {
|
public static LatLng stringToLatLng(String objectList) {
|
||||||
return objectList == null ? null : getGson().fromJson(objectList,LatLng.class);
|
return readObjectFromString(objectList,LatLng.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String wikidataPlaceToString(WikidataPlace wikidataPlace) {
|
||||||
|
return writeObjectToString(wikidataPlace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static WikidataPlace stringToWikidataPlace(String wikidataPlace) {
|
||||||
|
return readObjectFromString(wikidataPlace, WikidataPlace.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String depictionListToString(List<DepictedItem> depictedItems) {
|
||||||
|
return writeObjectToString(depictedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static List<DepictedItem> stringToList(String depictedItems) {
|
||||||
|
return readObjectWithTypeToken(depictedItems, new TypeToken<List<DepictedItem>>() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String depictionsToString(Depictions depictedItems) {
|
||||||
|
return writeObjectToString(depictedItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static Depictions stringToDepictions(String depictedItems) {
|
||||||
|
return readObjectFromString(depictedItems, Depictions.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String writeObjectToString(Object object) {
|
||||||
|
return object == null ? null : getGson().toJson(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static<T> T readObjectFromString(String objectAsString, Class<T> clazz) {
|
||||||
|
return objectAsString == null ? null : getGson().fromJson(objectAsString, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T readObjectWithTypeToken(String objectList, TypeToken<T> typeToken) {
|
||||||
|
return objectList == null ? null : getGson().fromJson(objectList, typeToken.getType());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,12 @@
|
||||||
package fr.free.nrw.commons.delete;
|
package fr.free.nrw.commons.delete;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
@ -29,10 +19,16 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.SingleSource;
|
import io.reactivex.SingleSource;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refactored async task to Rx
|
* Refactored async task to Rx
|
||||||
*/
|
*/
|
||||||
|
|
@ -104,7 +100,7 @@ public class DeleteHelper {
|
||||||
}
|
}
|
||||||
String creatorName = creator.replace(" (page does not exist)", "");
|
String creatorName = creator.replace(" (page does not exist)", "");
|
||||||
|
|
||||||
return pageEditClient.prependEdit(media.filename, fileDeleteString + "\n", summary)
|
return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary)
|
||||||
.flatMap(result -> {
|
.flatMap(result -> {
|
||||||
if (result) {
|
if (result) {
|
||||||
return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary);
|
return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package fr.free.nrw.commons.depictions;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import fr.free.nrw.commons.depictions.Media.DepictedImagesContract;
|
||||||
|
import fr.free.nrw.commons.depictions.Media.DepictedImagesPresenter;
|
||||||
|
import fr.free.nrw.commons.depictions.subClass.SubDepictionListContract;
|
||||||
|
import fr.free.nrw.commons.depictions.subClass.SubDepictionListPresenter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class DepictionModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract DepictedImagesContract.UserActionListener bindsDepictedImagesPresenter(
|
||||||
|
DepictedImagesPresenter
|
||||||
|
presenter
|
||||||
|
);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract SubDepictionListContract.UserActionListener bindsSubDepictionListPresenter(
|
||||||
|
SubDepictionListPresenter
|
||||||
|
presenter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package fr.free.nrw.commons.depictions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for Items in DepictionDetailsActivity
|
||||||
|
*/
|
||||||
|
public class GridViewAdapter extends ArrayAdapter {
|
||||||
|
|
||||||
|
private List<Media> data;
|
||||||
|
|
||||||
|
public GridViewAdapter(Context context, int layoutResourceId, List<Media> data) {
|
||||||
|
super(context, layoutResourceId, data);
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds more item to the list
|
||||||
|
* Its triggered on scrolling down in the list
|
||||||
|
* @param images
|
||||||
|
*/
|
||||||
|
public void addItems(List<Media> images) {
|
||||||
|
if (data == null) {
|
||||||
|
data = new ArrayList<>();
|
||||||
|
}
|
||||||
|
data.addAll(images);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the first item in the new list with old list and returns true if they are same
|
||||||
|
* Its triggered on successful response of the fetch images API.
|
||||||
|
* @param images
|
||||||
|
*/
|
||||||
|
public boolean containsAll(List<Media> images){
|
||||||
|
if (images == null || images.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (data == null) {
|
||||||
|
data = new ArrayList<>();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (data.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String fileName = data.get(0).getFilename();
|
||||||
|
String imageName = images.get(0).getFilename();
|
||||||
|
return imageName.equals(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return data == null || data.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the UI for the depicted image item
|
||||||
|
* @param position
|
||||||
|
* @param convertView
|
||||||
|
* @param parent
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(getContext()).inflate(R.layout.layout_depict_image, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Media item = data.get(position);
|
||||||
|
SimpleDraweeView imageView = convertView.findViewById(R.id.depict_image_view);
|
||||||
|
TextView fileName = convertView.findViewById(R.id.depict_image_title);
|
||||||
|
TextView author = convertView.findViewById(R.id.depict_image_author);
|
||||||
|
fileName.setText(item.getThumbnailTitle());
|
||||||
|
setAuthorView(item, author);
|
||||||
|
imageView.setImageURI(item.getThumbUrl());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Media getItem(int position) {
|
||||||
|
return data.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows author information if its present
|
||||||
|
* @param item
|
||||||
|
* @param author
|
||||||
|
*/
|
||||||
|
private void setAuthorView(Media item, TextView author) {
|
||||||
|
if (!TextUtils.isEmpty(item.getCreator())) {
|
||||||
|
String uploadedByTemplate = getContext().getString(R.string.image_uploaded_by);
|
||||||
|
|
||||||
|
String uploadedBy = String.format(Locale.getDefault(), uploadedByTemplate, item.getCreator());
|
||||||
|
author.setText(uploadedBy);
|
||||||
|
} else {
|
||||||
|
author.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package fr.free.nrw.commons.depictions.Media;
|
||||||
|
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract with which DepictedImagesFragment and its presenter will talk to each other
|
||||||
|
*/
|
||||||
|
public interface DepictedImagesContract {
|
||||||
|
|
||||||
|
interface View {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
void handleNoInternet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
void initErrorView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the adapter with a list of Media objects
|
||||||
|
*
|
||||||
|
* @param mediaList List of new Media to be displayed
|
||||||
|
*/
|
||||||
|
void setAdapter(List<Media> mediaList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seat caption to the image at the given position
|
||||||
|
*/
|
||||||
|
void handleLabelforImage(String caption, int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display snackbar
|
||||||
|
*/
|
||||||
|
void showSnackBar();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the view that there are no more items to be loaded for this search query
|
||||||
|
* or reset the isLastPage for the current query
|
||||||
|
* @param isLastPage
|
||||||
|
*/
|
||||||
|
void setIsLastPage(boolean isLastPage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set visibility of progressbar depending on the boolean value
|
||||||
|
*/
|
||||||
|
void progressBarVisible(Boolean value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It return an instance of gridView adapter which helps in extracting media details
|
||||||
|
* used by the gridView
|
||||||
|
*
|
||||||
|
* @return GridView Adapter
|
||||||
|
*/
|
||||||
|
ListAdapter getAdapter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds list to adapter
|
||||||
|
*/
|
||||||
|
void addItemsToAdapter(List<Media> media);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets loading status depending on the boolean value
|
||||||
|
*/
|
||||||
|
void setLoadingStatus(Boolean value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||||
|
*
|
||||||
|
* @param collection List of new Media to be displayed
|
||||||
|
*/
|
||||||
|
void handleSuccess(List<Media> collection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for internet connection and then initializes the grid view with first 10 images of that depiction
|
||||||
|
*/
|
||||||
|
void initList(String entityId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches more images for the item and adds it to the grid view adapter
|
||||||
|
*/
|
||||||
|
void fetchMoreImages();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||||
|
* else show filename
|
||||||
|
*/
|
||||||
|
void replaceTitlesWithCaptions(String title, int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add items to query list
|
||||||
|
*/
|
||||||
|
void addItemsToQueryList(List<Media> collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
package fr.free.nrw.commons.depictions.Media;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AbsListView;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.GridView;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import dagger.android.support.DaggerFragment;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.depictions.GridViewAdapter;
|
||||||
|
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for showing image list after selected an item from SearchActivity In Explore
|
||||||
|
*/
|
||||||
|
public class DepictedImagesFragment extends DaggerFragment implements DepictedImagesContract.View {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String PAGE_ID_PREFIX = "M";
|
||||||
|
@BindView(R.id.statusMessage)
|
||||||
|
TextView statusTextView;
|
||||||
|
@BindView(R.id.loadingImagesProgressBar)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.depicts_image_list)
|
||||||
|
GridView gridView;
|
||||||
|
@BindView(R.id.parentLayout)
|
||||||
|
RelativeLayout parentLayout;
|
||||||
|
@Inject
|
||||||
|
DepictedImagesPresenter presenter;
|
||||||
|
private GridViewAdapter gridAdapter;
|
||||||
|
private String entityId = null;
|
||||||
|
private boolean isLastPage;
|
||||||
|
private boolean isLoading = true;
|
||||||
|
private int mediaSize = 0;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_depict_image, container, false);
|
||||||
|
ButterKnife.bind(this, v);
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||||
|
initViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the UI elements for the fragment
|
||||||
|
* Setup the grid view to and scroll listener for it
|
||||||
|
*/
|
||||||
|
private void initViews() {
|
||||||
|
String depictsName = getArguments().getString("wikidataItemName");
|
||||||
|
entityId = getArguments().getString("entityId");
|
||||||
|
if (getArguments() != null && depictsName != null) {
|
||||||
|
initList();
|
||||||
|
setScrollListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initList() {
|
||||||
|
presenter.initList(entityId);
|
||||||
|
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
} else {
|
||||||
|
presenter.initList(entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
statusTextView.setText(getString(R.string.no_internet));
|
||||||
|
} else {
|
||||||
|
ViewUtil.showShortSnackbar(parentLayout, R.string.no_internet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initErrorView() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
statusTextView.setText(getString(R.string.no_images_found));
|
||||||
|
} else {
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scroll listener for the grid view so that more images are fetched when the user scrolls down
|
||||||
|
* Checks if the item has more images before loading
|
||||||
|
* Also checks whether images are currently being fetched before triggering another request
|
||||||
|
*/
|
||||||
|
private void setScrollListener() {
|
||||||
|
gridView.setOnScrollListener(new AbsListView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||||
|
if (!isLastPage && !isLoading && (firstVisibleItem + visibleItemCount >= totalItemCount)) {
|
||||||
|
isLoading = true;
|
||||||
|
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
} else {
|
||||||
|
presenter.fetchMoreImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isLastPage) {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seat caption to the image at the given position
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleLabelforImage(String caption, int position) {
|
||||||
|
if (!caption.trim().equals(getString(R.string.detail_caption_empty))) {
|
||||||
|
gridAdapter.getItem(position).setThumbnailTitle(caption);
|
||||||
|
gridAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display snackbar
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void showSnackBar() {
|
||||||
|
ViewUtil.showShortSnackbar(parentLayout, R.string.error_loading_images);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set visibility of progressbar depending on the boolean value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void progressBarVisible(Boolean value) {
|
||||||
|
if (value) {
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
} else {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It return an instance of gridView adapter which helps in extracting media details
|
||||||
|
* used by the gridView
|
||||||
|
*
|
||||||
|
* @return GridView Adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ListAdapter getAdapter() {
|
||||||
|
return gridAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the adapter with a list of Media objects
|
||||||
|
*
|
||||||
|
* @param mediaList List of new Media to be displayed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setAdapter(List<Media> mediaList) {
|
||||||
|
gridAdapter = new fr.free.nrw.commons.depictions.GridViewAdapter(getContext(), R.layout.layout_depict_image, mediaList);
|
||||||
|
gridView.setAdapter(gridAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds list to adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addItemsToAdapter(List<Media> media) {
|
||||||
|
gridAdapter.addAll(media);
|
||||||
|
gridAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets loading status depending on the boolean value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setLoadingStatus(Boolean value) {
|
||||||
|
if (!value) {
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
isLoading = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the view that there are no more items to be loaded for this search query
|
||||||
|
* or reset the isLastPage for the current query
|
||||||
|
* @param isLastPage
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setIsLastPage(boolean isLastPage) {
|
||||||
|
this.isLastPage=isLastPage;
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||||
|
*
|
||||||
|
* @param collection List of new Media to be displayed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleSuccess(List<Media> collection) {
|
||||||
|
presenter.addItemsToQueryList(collection);
|
||||||
|
if (gridAdapter == null) {
|
||||||
|
setAdapter(collection);
|
||||||
|
} else {
|
||||||
|
if (gridAdapter.containsAll(collection)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gridAdapter.addItems(collection);
|
||||||
|
|
||||||
|
try {
|
||||||
|
((WikidataItemDetailsActivity) getContext()).viewPagerNotifyDataSetChanged();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
isLoading = false;
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
for (Media media : collection) {
|
||||||
|
final String pageId = media.getPageId();
|
||||||
|
if (pageId != null) {
|
||||||
|
presenter.replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
package fr.free.nrw.commons.depictions.Media;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||||
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter for DepictedImagesFragment
|
||||||
|
*/
|
||||||
|
public class DepictedImagesPresenter implements DepictedImagesContract.UserActionListener {
|
||||||
|
|
||||||
|
private static final DepictedImagesContract.View DUMMY = (DepictedImagesContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
DepictedImagesContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{DepictedImagesContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
DepictsClient depictsClient;
|
||||||
|
MediaClient mediaClient;
|
||||||
|
@Named("default_preferences")
|
||||||
|
JsonKvStore depictionKvStore;
|
||||||
|
private final Scheduler ioScheduler;
|
||||||
|
private final Scheduler mainThreadScheduler;
|
||||||
|
private DepictedImagesContract.View view = DUMMY;
|
||||||
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
/**
|
||||||
|
* Wikibase enitityId for the depicted Item
|
||||||
|
* Ex: Q9394
|
||||||
|
*/
|
||||||
|
private String entityId = null;
|
||||||
|
private List<Media> queryList = new ArrayList<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DepictedImagesPresenter(@Named("default_preferences") JsonKvStore depictionKvStore, DepictsClient depictsClient, MediaClient mediaClient, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||||
|
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||||
|
this.depictionKvStore = depictionKvStore;
|
||||||
|
this.depictsClient = depictsClient;
|
||||||
|
this.ioScheduler = ioScheduler;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
this.mediaClient = mediaClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachView(DepictedImagesContract.View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for internet connection and then initializes the grid view with first 10 images of that depiction
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
@Override
|
||||||
|
public void initList(String entityId) {
|
||||||
|
view.setLoadingStatus(true);
|
||||||
|
view.progressBarVisible(true);
|
||||||
|
view.setIsLastPage(false);
|
||||||
|
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, 0)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches more images for the item and adds it to the grid view adapter
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
@Override
|
||||||
|
public void fetchMoreImages() {
|
||||||
|
view.progressBarVisible(true);
|
||||||
|
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, queryList.size())
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(this::handlePaginationSuccess, this::handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
*/
|
||||||
|
private void handlePaginationSuccess(List<Media> media) {
|
||||||
|
queryList.addAll(media);
|
||||||
|
view.progressBarVisible(false);
|
||||||
|
view.addItemsToAdapter(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
*
|
||||||
|
* @param throwable
|
||||||
|
*/
|
||||||
|
public void handleError(Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Error occurred while loading images inside items");
|
||||||
|
try {
|
||||||
|
view.initErrorView();
|
||||||
|
view.showSnackBar();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||||
|
* @param collection List of new Media to be displayed
|
||||||
|
*/
|
||||||
|
public void handleSuccess(List<Media> collection) {
|
||||||
|
if (collection == null || collection.isEmpty()) {
|
||||||
|
if (queryList.isEmpty()) {
|
||||||
|
view.initErrorView();
|
||||||
|
} else {
|
||||||
|
view.setIsLastPage(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.queryList.addAll(collection);
|
||||||
|
view.handleSuccess(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||||
|
* else show filename
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void replaceTitlesWithCaptions(String wikibaseIdentifier, int position) {
|
||||||
|
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(caption -> {
|
||||||
|
view.handleLabelforImage(caption, position);
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add items to query list
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addItemsToQueryList(List<Media> collection) {
|
||||||
|
queryList.addAll(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
package fr.free.nrw.commons.depictions;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment;
|
||||||
|
import fr.free.nrw.commons.depictions.subClass.SubDepictionListFragment;
|
||||||
|
import fr.free.nrw.commons.explore.ViewPagerAdapter;
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity to show depiction media, parent classes and child classes of depicted items in Explore
|
||||||
|
*/
|
||||||
|
public class WikidataItemDetailsActivity extends NavigationBaseActivity implements MediaDetailPagerFragment.MediaDetailProvider, AdapterView.OnItemClickListener {
|
||||||
|
private FragmentManager supportFragmentManager;
|
||||||
|
private DepictedImagesFragment depictionImagesListFragment;
|
||||||
|
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||||
|
/**
|
||||||
|
* Name of the depicted item
|
||||||
|
* Ex: Rabbit
|
||||||
|
*/
|
||||||
|
private String wikidataItemName;
|
||||||
|
@BindView(R.id.mediaContainer)
|
||||||
|
FrameLayout mediaContainer;
|
||||||
|
@BindView(R.id.tab_layout)
|
||||||
|
TabLayout tabLayout;
|
||||||
|
@BindView(R.id.viewPager)
|
||||||
|
ViewPager viewPager;
|
||||||
|
|
||||||
|
ViewPagerAdapter viewPagerAdapter;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_wikidata_item_details);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
supportFragmentManager = getSupportFragmentManager();
|
||||||
|
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(viewPagerAdapter);
|
||||||
|
viewPager.setOffscreenPageLimit(2);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
setTabs();
|
||||||
|
setPageTitle();
|
||||||
|
initDrawer();
|
||||||
|
forceInitBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the passed wikidataItemName from the intents and displays it as the page title
|
||||||
|
*/
|
||||||
|
private void setPageTitle() {
|
||||||
|
if (getIntent() != null && getIntent().getStringExtra("wikidataItemName") != null) {
|
||||||
|
setTitle(getIntent().getStringExtra("wikidataItemName"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on success of API call for featured Images.
|
||||||
|
* The viewpager will notified that number of items have changed.
|
||||||
|
*/
|
||||||
|
public void viewPagerNotifyDataSetChanged() {
|
||||||
|
if (mediaDetailPagerFragment !=null){
|
||||||
|
mediaDetailPagerFragment.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
|
||||||
|
* Set the fragments according to the tab selected in the viewPager.
|
||||||
|
*/
|
||||||
|
private void setTabs() {
|
||||||
|
List<Fragment> fragmentList = new ArrayList<>();
|
||||||
|
List<String> titleList = new ArrayList<>();
|
||||||
|
depictionImagesListFragment = new DepictedImagesFragment();
|
||||||
|
SubDepictionListFragment subDepictionListFragment = new SubDepictionListFragment();
|
||||||
|
SubDepictionListFragment parentDepictionListFragment = new SubDepictionListFragment();
|
||||||
|
wikidataItemName = getIntent().getStringExtra("wikidataItemName");
|
||||||
|
String entityId = getIntent().getStringExtra("entityId");
|
||||||
|
if (getIntent() != null && wikidataItemName != null) {
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
arguments.putString("wikidataItemName", wikidataItemName);
|
||||||
|
arguments.putString("entityId", entityId);
|
||||||
|
arguments.putBoolean("isParentClass", false);
|
||||||
|
depictionImagesListFragment.setArguments(arguments);
|
||||||
|
subDepictionListFragment.setArguments(arguments);
|
||||||
|
Bundle parentClassArguments = new Bundle();
|
||||||
|
parentClassArguments.putString("wikidataItemName", wikidataItemName);
|
||||||
|
parentClassArguments.putString("entityId", entityId);
|
||||||
|
parentClassArguments.putBoolean("isParentClass", true);
|
||||||
|
parentDepictionListFragment.setArguments(parentClassArguments);
|
||||||
|
}
|
||||||
|
fragmentList.add(depictionImagesListFragment);
|
||||||
|
titleList.add(getResources().getString(R.string.title_for_media));
|
||||||
|
fragmentList.add(subDepictionListFragment);
|
||||||
|
titleList.add(getResources().getString(R.string.title_for_child_classes));
|
||||||
|
fragmentList.add(parentDepictionListFragment);
|
||||||
|
titleList.add(getResources().getString(R.string.title_for_parent_classes));
|
||||||
|
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||||
|
viewPager.setOffscreenPageLimit(2);
|
||||||
|
viewPagerAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows media detail fragment when user clicks on any image in the list
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
viewPager.setVisibility(View.GONE);
|
||||||
|
mediaContainer.setVisibility(View.VISIBLE);
|
||||||
|
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
|
||||||
|
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||||
|
mediaDetailPagerFragment = new MediaDetailPagerFragment(false, true);
|
||||||
|
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.mediaContainer, mediaDetailPagerFragment)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
supportFragmentManager.executePendingTransactions();
|
||||||
|
}
|
||||||
|
mediaDetailPagerFragment.showImage(position);
|
||||||
|
forceInitBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||||
|
* @param i It is the index of which media object is to be returned which is same as
|
||||||
|
* current index of viewPager.
|
||||||
|
* @return Media Object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Media getMediaAtPosition(int i) {
|
||||||
|
if (depictionImagesListFragment.getAdapter() == null) {
|
||||||
|
// not yet ready to return data
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (Media) depictionImagesListFragment.getAdapter().getItem(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on backPressed of anyFragment in the activity.
|
||||||
|
* If condition is called when mediaDetailFragment is opened.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (supportFragmentManager.getBackStackEntryCount() == 1){
|
||||||
|
// back to search so show search toolbar and hide navigation toolbar
|
||||||
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
viewPager.setVisibility(View.VISIBLE);
|
||||||
|
mediaContainer.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on from getCount of MediaDetailPagerFragment
|
||||||
|
* The viewpager will contain same number of media items as that of media elements in adapter.
|
||||||
|
* @return Total Media count in the adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
if (depictionImagesListFragment.getAdapter() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return depictionImagesListFragment.getAdapter().getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumers should be simply using this method to use this activity.
|
||||||
|
*
|
||||||
|
* @param context A Context of the application package implementing this class.
|
||||||
|
* @param depictedItem Name of the depicts for displaying its details
|
||||||
|
*/
|
||||||
|
public static void startYourself(Context context, DepictedItem depictedItem) {
|
||||||
|
Intent intent = new Intent(context, WikidataItemDetailsActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
intent.putExtra("wikidataItemName", depictedItem.getName());
|
||||||
|
intent.putExtra("entityId", depictedItem.getId());
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package fr.free.nrw.commons.depictions.models;
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for object obtained while parsing depiction response
|
||||||
|
*/
|
||||||
|
public class Continue {
|
||||||
|
|
||||||
|
@SerializedName("sroffset")
|
||||||
|
@Expose
|
||||||
|
private Integer sroffset;
|
||||||
|
@SerializedName("continue")
|
||||||
|
@Expose
|
||||||
|
private String _continue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Continue() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param sroffset
|
||||||
|
* @param _continue
|
||||||
|
*/
|
||||||
|
public Continue(Integer sroffset, String _continue) {
|
||||||
|
super();
|
||||||
|
this.sroffset = sroffset;
|
||||||
|
this._continue = _continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets sroffset from Continue object
|
||||||
|
*/
|
||||||
|
public Integer getSroffset() {
|
||||||
|
return sroffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSroffset(Integer sroffset) {
|
||||||
|
this.sroffset = sroffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets continue string from Continue object
|
||||||
|
*/
|
||||||
|
public String getContinue() {
|
||||||
|
return _continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContinue(String _continue) {
|
||||||
|
this._continue = _continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package fr.free.nrw.commons.depictions.models;
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for list of depicted images obtained by fetching using depiction entity
|
||||||
|
*/
|
||||||
|
public class DepictionResponse {
|
||||||
|
|
||||||
|
@SerializedName("batchcomplete")
|
||||||
|
@Expose
|
||||||
|
private String batchcomplete;
|
||||||
|
@SerializedName("continue")
|
||||||
|
@Expose
|
||||||
|
private Continue _continue;
|
||||||
|
@SerializedName("query")
|
||||||
|
@Expose
|
||||||
|
private Query query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public DepictionResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @param batchcomplete
|
||||||
|
* @param _continue
|
||||||
|
*/
|
||||||
|
public DepictionResponse(String batchcomplete, Continue _continue, Query query) {
|
||||||
|
super();
|
||||||
|
this.batchcomplete = batchcomplete;
|
||||||
|
this._continue = _continue;
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns batchcomplete string from DepictionResponse object
|
||||||
|
*/
|
||||||
|
public String getBatchcomplete() {
|
||||||
|
return batchcomplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBatchcomplete(String batchcomplete) {
|
||||||
|
this.batchcomplete = batchcomplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns continue object from DepictionResponse object
|
||||||
|
*/
|
||||||
|
public Continue getContinue() {
|
||||||
|
return _continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContinue(Continue _continue) {
|
||||||
|
this._continue = _continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns query object from DepictionResponse object
|
||||||
|
*/
|
||||||
|
public Query getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuery(Query query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package fr.free.nrw.commons.depictions.models;
|
||||||
|
import java.util.List;
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for object obtained while parsing depiction response
|
||||||
|
*
|
||||||
|
* the getSearch() function is used to parse media
|
||||||
|
*/
|
||||||
|
public class Query {
|
||||||
|
|
||||||
|
@SerializedName("searchinfo")
|
||||||
|
@Expose
|
||||||
|
private Searchinfo searchinfo;
|
||||||
|
@SerializedName("search")
|
||||||
|
@Expose
|
||||||
|
private List<Search> search = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Query() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param search
|
||||||
|
* @param searchinfo
|
||||||
|
*/
|
||||||
|
public Query(Searchinfo searchinfo, List<Search> search) {
|
||||||
|
super();
|
||||||
|
this.searchinfo = searchinfo;
|
||||||
|
this.search = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return searchInfo
|
||||||
|
*/
|
||||||
|
public Searchinfo getSearchinfo() {
|
||||||
|
return searchinfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearchinfo(Searchinfo searchinfo) {
|
||||||
|
this.searchinfo = searchinfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the getSearch() function is used to parse media
|
||||||
|
*/
|
||||||
|
public List<Search> getSearch() {
|
||||||
|
return search;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSearch(List<Search> search) {
|
||||||
|
this.search = search;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package fr.free.nrw.commons.depictions.models;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for object obtained while parsing depiction response
|
||||||
|
* this class contains all the details of for the media object
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Search {
|
||||||
|
|
||||||
|
@SerializedName("ns")
|
||||||
|
@Expose
|
||||||
|
private Integer ns;
|
||||||
|
@SerializedName("title")
|
||||||
|
@Expose
|
||||||
|
private String title;
|
||||||
|
@SerializedName("pageid")
|
||||||
|
@Expose
|
||||||
|
private Integer pageid;
|
||||||
|
@SerializedName("size")
|
||||||
|
@Expose
|
||||||
|
private Integer size;
|
||||||
|
@SerializedName("wordcount")
|
||||||
|
@Expose
|
||||||
|
private Integer wordcount;
|
||||||
|
@SerializedName("snippet")
|
||||||
|
@Expose
|
||||||
|
private String snippet;
|
||||||
|
@SerializedName("timestamp")
|
||||||
|
@Expose
|
||||||
|
private String timestamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Search() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param timestamp
|
||||||
|
* @param title
|
||||||
|
* @param ns
|
||||||
|
* @param snippet
|
||||||
|
* @param wordcount
|
||||||
|
* @param size
|
||||||
|
* @param pageid
|
||||||
|
*/
|
||||||
|
public Search(Integer ns, String title, Integer pageid, Integer size, Integer wordcount, String snippet, String timestamp) {
|
||||||
|
super();
|
||||||
|
this.ns = ns;
|
||||||
|
this.title = title;
|
||||||
|
this.pageid = pageid;
|
||||||
|
this.size = size;
|
||||||
|
this.wordcount = wordcount;
|
||||||
|
this.snippet = snippet;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns ns int from Search object
|
||||||
|
*/
|
||||||
|
public Integer getNs() {
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNs(Integer ns) {
|
||||||
|
this.ns = ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns title string from Search object
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns pageid int from Search object
|
||||||
|
*/
|
||||||
|
public Integer getPageid() {
|
||||||
|
return pageid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageid(Integer pageid) {
|
||||||
|
this.pageid = pageid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns size int from Search object
|
||||||
|
*/
|
||||||
|
public Integer getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(Integer size) {
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns wordcount int from Search object
|
||||||
|
*/
|
||||||
|
public Integer getWordcount() {
|
||||||
|
return wordcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWordcount(Integer wordcount) {
|
||||||
|
this.wordcount = wordcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns snippet String from Search object
|
||||||
|
*/
|
||||||
|
public String getSnippet() {
|
||||||
|
return snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSnippet(String snippet) {
|
||||||
|
this.snippet = snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns ns int from Search object
|
||||||
|
*/
|
||||||
|
public String getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimestamp(String timestamp) {
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package fr.free.nrw.commons.depictions.models;
|
||||||
|
import com.google.gson.annotations.Expose;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for object obtained while parsing query object
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Searchinfo {
|
||||||
|
|
||||||
|
@SerializedName("totalhits")
|
||||||
|
@Expose
|
||||||
|
private Integer totalhits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Searchinfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param totalhits
|
||||||
|
*/
|
||||||
|
public Searchinfo(Integer totalhits) {
|
||||||
|
super();
|
||||||
|
this.totalhits = totalhits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns "totalhint" integer in SearchInfo object
|
||||||
|
*/
|
||||||
|
public Integer getTotalhits() {
|
||||||
|
return totalhits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalhits(Integer totalhits) {
|
||||||
|
this.totalhits = totalhits;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package fr.free.nrw.commons.depictions.subClass;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract with which SubDepictionListFragment and its presenter would talk to each other
|
||||||
|
*/
|
||||||
|
public interface SubDepictionListContract {
|
||||||
|
|
||||||
|
interface View {
|
||||||
|
|
||||||
|
void onImageUrlFetched(String response, int position);
|
||||||
|
|
||||||
|
void onSuccess(List<DepictedItem> mediaList);
|
||||||
|
|
||||||
|
void initErrorView();
|
||||||
|
|
||||||
|
void showSnackbar();
|
||||||
|
|
||||||
|
void setIsLastPage(boolean b);
|
||||||
|
|
||||||
|
boolean isParentClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
void saveQuery();
|
||||||
|
|
||||||
|
void fetchThumbnailForEntityId(String entityId, int position);
|
||||||
|
|
||||||
|
void initSubDepictionList(String qid, Boolean isParentClass) throws IOException;
|
||||||
|
|
||||||
|
String getQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
package fr.free.nrw.commons.depictions.subClass;
|
||||||
|
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import dagger.android.support.DaggerFragment;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsAdapterFactory;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsRenderer;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for parent classes and child classes of Depicted items in Explore
|
||||||
|
*/
|
||||||
|
public class SubDepictionListFragment extends DaggerFragment implements SubDepictionListContract.View {
|
||||||
|
|
||||||
|
@BindView(R.id.imagesListBox)
|
||||||
|
RecyclerView depictionsRecyclerView;
|
||||||
|
@BindView(R.id.imageSearchInProgress)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.imagesNotFound)
|
||||||
|
TextView depictionNotFound;
|
||||||
|
@BindView(R.id.bottomProgressBar)
|
||||||
|
ProgressBar bottomProgressBar;
|
||||||
|
/**
|
||||||
|
* Keeps a record of whether current instance of the fragment if of SubClass or ParentClass
|
||||||
|
*/
|
||||||
|
private boolean isParentClass = false;
|
||||||
|
private RVRendererAdapter<DepictedItem> depictionsAdapter;
|
||||||
|
/**
|
||||||
|
* Used by scroll state listener, when hasMoreImages is false scrolling does not fetches any more images
|
||||||
|
*/
|
||||||
|
private boolean hasMoreImages = true;
|
||||||
|
RecyclerView.LayoutManager layoutManager;
|
||||||
|
/**
|
||||||
|
* Stores entityId for the depiction
|
||||||
|
*/
|
||||||
|
private String entityId;
|
||||||
|
/**
|
||||||
|
* Stores name of the depiction searched
|
||||||
|
*/
|
||||||
|
private String depictsName;
|
||||||
|
|
||||||
|
@Inject SubDepictionListPresenter presenter;
|
||||||
|
|
||||||
|
private final SearchDepictionsAdapterFactory adapterFactory = new SearchDepictionsAdapterFactory(new SearchDepictionsRenderer.DepictCallback() {
|
||||||
|
@Override
|
||||||
|
public void depictsClicked(DepictedItem item) {
|
||||||
|
// Open SubDepiction Details page
|
||||||
|
getActivity().finish();
|
||||||
|
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fetchThumbnailUrlForEntity(String entityId, int position) {
|
||||||
|
presenter.fetchThumbnailForEntityId(entityId, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViews() {
|
||||||
|
if (getArguments() != null) {
|
||||||
|
depictsName = getArguments().getString("wikidataItemName");
|
||||||
|
entityId = getArguments().getString("entityId");
|
||||||
|
isParentClass = getArguments().getBoolean("isParentClass");
|
||||||
|
if (entityId != null) {
|
||||||
|
initList(entityId, isParentClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initList(String qid, Boolean isParentClass) {
|
||||||
|
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
} else {
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
try {
|
||||||
|
presenter.initSubDepictionList(qid, isParentClass);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||||
|
ButterKnife.bind(this, v);
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
isParentClass = false;
|
||||||
|
depictionNotFound.setVisibility(GONE);
|
||||||
|
if (getActivity().getResources().getConfiguration().orientation
|
||||||
|
== Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
layoutManager = new LinearLayoutManager(getContext());
|
||||||
|
} else {
|
||||||
|
layoutManager = new GridLayoutManager(getContext(), 2);
|
||||||
|
}
|
||||||
|
initViews();
|
||||||
|
depictionsRecyclerView.setLayoutManager(layoutManager);
|
||||||
|
depictionsAdapter = adapterFactory.create();
|
||||||
|
depictionsRecyclerView.setAdapter(depictionsAdapter);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageUrlFetched(String response, int position) {
|
||||||
|
depictionsAdapter.getItem(position).setImageUrl(response);
|
||||||
|
depictionsAdapter.notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<DepictedItem> mediaList) {
|
||||||
|
hasMoreImages = false;
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
depictionNotFound.setVisibility(GONE);
|
||||||
|
bottomProgressBar.setVisibility(GONE);
|
||||||
|
int itemCount=layoutManager.getItemCount();
|
||||||
|
depictionsAdapter.addAll(mediaList);
|
||||||
|
depictionsRecyclerView.getRecycledViewPool().clear();
|
||||||
|
if(itemCount!=0) {
|
||||||
|
depictionsAdapter.notifyItemRangeInserted(itemCount, mediaList.size()-1);
|
||||||
|
}else{
|
||||||
|
depictionsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initErrorView() {
|
||||||
|
hasMoreImages = false;
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
bottomProgressBar.setVisibility(GONE);
|
||||||
|
depictionNotFound.setVisibility(VISIBLE);
|
||||||
|
String no_depiction = getString(isParentClass? R.string.no_parent_classes: R.string.no_child_classes);
|
||||||
|
depictionNotFound.setText(String.format(Locale.getDefault(), no_depiction, depictsName));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showSnackbar() {
|
||||||
|
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.error_loading_depictions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIsLastPage(boolean b) {
|
||||||
|
hasMoreImages = !b;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isParentClass() {
|
||||||
|
return isParentClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package fr.free.nrw.commons.depictions.subClass;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter for parent classes and child classes of Depicted items in Explore
|
||||||
|
*/
|
||||||
|
public class SubDepictionListPresenter implements SubDepictionListContract.UserActionListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This creates a dynamic proxy instance of the class,
|
||||||
|
* proxy is to control access to the target object
|
||||||
|
* here our target object is the view.
|
||||||
|
* Thus we when onDettach method of fragment is called we replace the binding of view to our object with the proxy instance
|
||||||
|
*/
|
||||||
|
private static final SubDepictionListContract.View DUMMY = (SubDepictionListContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
SubDepictionListContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{SubDepictionListContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
|
||||||
|
private final Scheduler ioScheduler;
|
||||||
|
private final Scheduler mainThreadScheduler;
|
||||||
|
private SubDepictionListContract.View view = DUMMY;
|
||||||
|
RecentSearchesDao recentSearchesDao;
|
||||||
|
/**
|
||||||
|
* Value of the search query
|
||||||
|
*/
|
||||||
|
public String query;
|
||||||
|
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
DepictsClient depictsClient;
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
private List<DepictedItem> queryList = new ArrayList<>();
|
||||||
|
OkHttpJsonApiClient okHttpJsonApiClient;
|
||||||
|
/**
|
||||||
|
* variable used to record the number of API calls already made for fetching Thumbnails
|
||||||
|
*/
|
||||||
|
private int size = 0;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SubDepictionListPresenter(RecentSearchesDao recentSearchesDao, DepictsClient depictsClient, OkHttpJsonApiClient okHttpJsonApiClient, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||||
|
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||||
|
this.recentSearchesDao = recentSearchesDao;
|
||||||
|
this.ioScheduler = ioScheduler;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
this.depictsClient = depictsClient;
|
||||||
|
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onAttachView(SubDepictionListContract.View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the current query in Recent searches
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void saveQuery() {
|
||||||
|
RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||||
|
|
||||||
|
// Newly searched query...
|
||||||
|
if (recentSearch == null) {
|
||||||
|
recentSearch = new RecentSearch(null, query, new Date());
|
||||||
|
} else {
|
||||||
|
recentSearch.setLastSearched(new Date());
|
||||||
|
}
|
||||||
|
recentSearchesDao.save(recentSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls Wikibase APIs to fetch Thumbnail image for a given wikidata item
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fetchThumbnailForEntityId(String entityId, int position) {
|
||||||
|
compositeDisposable.add(depictsClient.getP18ForItem(entityId)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(response -> {
|
||||||
|
view.onImageUrlFetched(response,position);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initSubDepictionList(String qid, Boolean isParentClass) throws IOException {
|
||||||
|
size = 0;
|
||||||
|
if (isParentClass) {
|
||||||
|
compositeDisposable.add(okHttpJsonApiClient.getParentQIDs(qid)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
|
} else {
|
||||||
|
compositeDisposable.add(okHttpJsonApiClient.getChildQIDs(qid)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
*/
|
||||||
|
public void handleSuccess(List<DepictedItem> mediaList) {
|
||||||
|
if (mediaList == null || mediaList.isEmpty()) {
|
||||||
|
if(queryList.isEmpty()){
|
||||||
|
view.initErrorView();
|
||||||
|
}else{
|
||||||
|
view.setIsLastPage(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.queryList.addAll(mediaList);
|
||||||
|
view.onSuccess(mediaList);
|
||||||
|
for (DepictedItem m : mediaList) {
|
||||||
|
fetchThumbnailForEntityId(m.getId(), size++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Error occurred while loading queried depictions");
|
||||||
|
view.initErrorView();
|
||||||
|
view.showSnackbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package fr.free.nrw.commons.depictions.subClass.models
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||||
|
|
||||||
|
data class SparqlResponse(val results: Result) {
|
||||||
|
fun toDepictedItems() =
|
||||||
|
results.bindings.map {
|
||||||
|
DepictedItem(
|
||||||
|
it.itemLabel.value,
|
||||||
|
it.itemDescription?.value ?: "",
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
it.item.value.substringAfterLast("/")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Result(val bindings: List<Binding>)
|
||||||
|
|
||||||
|
data class Binding(
|
||||||
|
val item: SparqInfo,
|
||||||
|
val itemLabel: SparqInfo,
|
||||||
|
val itemDescription: SparqInfo? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SparqInfo(val type: String, val value: String)
|
||||||
|
|
@ -11,6 +11,7 @@ import fr.free.nrw.commons.bookmarks.BookmarksActivity;
|
||||||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||||
import fr.free.nrw.commons.contributions.MainActivity;
|
import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
|
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||||
import fr.free.nrw.commons.explore.SearchActivity;
|
import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
import fr.free.nrw.commons.explore.categories.ExploreActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
|
|
@ -60,6 +61,9 @@ public abstract class ActivityBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract CategoryDetailsActivity bindCategoryDetailsActivity();
|
abstract CategoryDetailsActivity bindCategoryDetailsActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract WikidataItemDetailsActivity bindDepictionDetailsActivity();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract ExploreActivity bindExploreActivity();
|
abstract ExploreActivity bindExploreActivity();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionViewHolder;
|
import fr.free.nrw.commons.contributions.ContributionViewHolder;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsModule;
|
import fr.free.nrw.commons.contributions.ContributionsModule;
|
||||||
|
import fr.free.nrw.commons.depictions.DepictionModule;
|
||||||
|
import fr.free.nrw.commons.explore.SearchModule;
|
||||||
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
||||||
import fr.free.nrw.commons.review.ReviewController;
|
import fr.free.nrw.commons.review.ReviewController;
|
||||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||||
|
|
@ -33,7 +35,7 @@ import fr.free.nrw.commons.widget.PicOfDayAppWidget;
|
||||||
ActivityBuilderModule.class,
|
ActivityBuilderModule.class,
|
||||||
FragmentBuilderModule.class,
|
FragmentBuilderModule.class,
|
||||||
ServiceBuilderModule.class,
|
ServiceBuilderModule.class,
|
||||||
ContentProviderBuilderModule.class, UploadModule.class, ContributionsModule.class
|
ContentProviderBuilderModule.class, UploadModule.class, ContributionsModule.class, SearchModule.class, DepictionModule.class
|
||||||
})
|
})
|
||||||
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
|
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
|
||||||
void inject(CommonsApplication application);
|
void inject(CommonsApplication application);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.content.Context;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import androidx.collection.LruCache;
|
import androidx.collection.LruCache;
|
||||||
import androidx.room.Room;
|
import androidx.room.Room;
|
||||||
import com.github.varunpant.quadtree.QuadTree;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
|
@ -50,7 +49,6 @@ public class CommonsApplicationModule {
|
||||||
private Context applicationContext;
|
private Context applicationContext;
|
||||||
public static final String IO_THREAD="io_thread";
|
public static final String IO_THREAD="io_thread";
|
||||||
public static final String MAIN_THREAD="main_thread";
|
public static final String MAIN_THREAD="main_thread";
|
||||||
private AppDatabase appDatabase;
|
|
||||||
|
|
||||||
public CommonsApplicationModule(Context applicationContext) {
|
public CommonsApplicationModule(Context applicationContext) {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
|
|
@ -105,6 +103,11 @@ public class CommonsApplicationModule {
|
||||||
return context.getContentResolver().acquireContentProviderClient(BuildConfig.CATEGORY_AUTHORITY);
|
return context.getContentResolver().acquireContentProviderClient(BuildConfig.CATEGORY_AUTHORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to provide instance of DepictsContentProviderClient
|
||||||
|
* @param context context
|
||||||
|
* @return DepictsContentProviderClient*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is used to provide instance of RecentSearchContentProviderClient
|
* This method is used to provide instance of RecentSearchContentProviderClient
|
||||||
* which provides content of Recent Searches from database
|
* which provides content of Recent Searches from database
|
||||||
|
|
@ -218,26 +221,15 @@ public class CommonsApplicationModule {
|
||||||
return Objects.toString(AppAdapter.get().getUserName(), "");
|
return Objects.toString(AppAdapter.get().getUserName(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides quad tree
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Provides
|
|
||||||
public QuadTree providesQuadTres() {
|
|
||||||
return new QuadTree<>(-180, -90, +180, +90);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public AppDatabase provideAppDataBase() {
|
public AppDatabase provideAppDataBase() {
|
||||||
appDatabase=Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build();
|
return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build();
|
||||||
return appDatabase;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
public ContributionDao providesContributionsDao() {
|
public ContributionDao providesContributionsDao(AppDatabase appDatabase) {
|
||||||
return appDatabase.getContributionDao();
|
return appDatabase.contributionDao();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,18 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider;
|
||||||
* then that must be mentioned here to inject the dependencies
|
* then that must be mentioned here to inject the dependencies
|
||||||
*/
|
*/
|
||||||
@Module
|
@Module
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({ "WeakerAccess", "unused" })
|
||||||
public abstract class ContentProviderBuilderModule {
|
public abstract class ContentProviderBuilderModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract CategoryContentProvider bindCategoryContentProvider();
|
abstract CategoryContentProvider bindCategoryContentProvider();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract RecentSearchesContentProvider bindRecentSearchesContentProvider();
|
abstract RecentSearchesContentProvider bindRecentSearchesContentProvider();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract BookmarkPicturesContentProvider bindBookmarkContentProvider();
|
abstract BookmarkPicturesContentProvider bindBookmarkContentProvider();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
|
||||||
abstract BookmarkLocationsContentProvider bindBookmarkLocationContentProvider();
|
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract BookmarkLocationsContentProvider bindBookmarkLocationContentProvider();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ import fr.free.nrw.commons.category.CategoryImagesListFragment;
|
||||||
import fr.free.nrw.commons.category.SubCategoryListFragment;
|
import fr.free.nrw.commons.category.SubCategoryListFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
||||||
|
import fr.free.nrw.commons.depictions.Media.DepictedImagesFragment;
|
||||||
|
import fr.free.nrw.commons.depictions.subClass.SubDepictionListFragment;
|
||||||
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragment;
|
||||||
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||||
|
|
@ -17,6 +20,7 @@ import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment;
|
||||||
import fr.free.nrw.commons.review.ReviewImageFragment;
|
import fr.free.nrw.commons.review.ReviewImageFragment;
|
||||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||||
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
||||||
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
||||||
|
|
||||||
|
|
@ -44,6 +48,12 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract DepictedImagesFragment bindDepictedImagesFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract SubDepictionListFragment bindSubDepictionListFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
abstract SubCategoryListFragment bindSubCategoryListFragment();
|
||||||
|
|
||||||
|
|
@ -53,6 +63,9 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SearchCategoryFragment bindSearchCategoryListFragment();
|
abstract SearchCategoryFragment bindSearchCategoryListFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract SearchDepictionsFragment bindSearchDepictionListFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract RecentSearchesFragment bindRecentSearchesFragment();
|
abstract RecentSearchesFragment bindRecentSearchesFragment();
|
||||||
|
|
||||||
|
|
@ -77,6 +90,9 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract UploadCategoriesFragment bindUploadCategoriesFragment();
|
abstract UploadCategoriesFragment bindUploadCategoriesFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract DepictsFragment bindDepictsFragment();
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract MediaLicenseFragment bindMediaLicenseFragment();
|
abstract MediaLicenseFragment bindMediaLicenseFragment();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,8 @@
|
||||||
package fr.free.nrw.commons.di;
|
package fr.free.nrw.commons.di;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.wikipedia.csrf.CsrfTokenClient;
|
|
||||||
import org.wikipedia.dataclient.Service;
|
|
||||||
import org.wikipedia.dataclient.ServiceFactory;
|
|
||||||
import org.wikipedia.dataclient.WikiSite;
|
|
||||||
import org.wikipedia.json.GsonUtil;
|
|
||||||
import org.wikipedia.login.LoginClient;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
|
@ -26,16 +10,30 @@ import fr.free.nrw.commons.actions.PageEditClient;
|
||||||
import fr.free.nrw.commons.actions.PageEditInterface;
|
import fr.free.nrw.commons.actions.PageEditInterface;
|
||||||
import fr.free.nrw.commons.category.CategoryInterface;
|
import fr.free.nrw.commons.category.CategoryInterface;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailInterface;
|
||||||
import fr.free.nrw.commons.media.MediaInterface;
|
import fr.free.nrw.commons.media.MediaInterface;
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||||
import fr.free.nrw.commons.mwapi.UserInterface;
|
import fr.free.nrw.commons.mwapi.UserInterface;
|
||||||
import fr.free.nrw.commons.review.ReviewInterface;
|
import fr.free.nrw.commons.review.ReviewInterface;
|
||||||
import fr.free.nrw.commons.upload.UploadInterface;
|
import fr.free.nrw.commons.upload.UploadInterface;
|
||||||
|
import fr.free.nrw.commons.upload.WikiBaseInterface;
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
|
||||||
import fr.free.nrw.commons.wikidata.WikidataInterface;
|
import fr.free.nrw.commons.wikidata.WikidataInterface;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.logging.HttpLoggingInterceptor;
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor.Level;
|
||||||
|
import org.wikipedia.csrf.CsrfTokenClient;
|
||||||
|
import org.wikipedia.dataclient.Service;
|
||||||
|
import org.wikipedia.dataclient.ServiceFactory;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.json.GsonUtil;
|
||||||
|
import org.wikipedia.login.LoginClient;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
|
|
@ -72,7 +70,7 @@ public class NetworkingModule {
|
||||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> {
|
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> {
|
||||||
Timber.tag("OkHttp").v(message);
|
Timber.tag("OkHttp").v(message);
|
||||||
});
|
});
|
||||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
httpLoggingInterceptor.level(BuildConfig.DEBUG ? Level.BODY: Level.BASIC);
|
||||||
return httpLoggingInterceptor;
|
return httpLoggingInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,8 +84,7 @@ public class NetworkingModule {
|
||||||
toolsForgeUrl,
|
toolsForgeUrl,
|
||||||
WIKIDATA_SPARQL_QUERY_URL,
|
WIKIDATA_SPARQL_QUERY_URL,
|
||||||
BuildConfig.WIKIMEDIA_CAMPAIGNS_URL,
|
BuildConfig.WIKIMEDIA_CAMPAIGNS_URL,
|
||||||
BuildConfig.WIKIMEDIA_API_HOST,
|
gson);
|
||||||
gson);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Named(NAMED_COMMONS_CSRF)
|
@Named(NAMED_COMMONS_CSRF)
|
||||||
|
|
@ -133,6 +130,7 @@ public class NetworkingModule {
|
||||||
return new WikiSite(BuildConfig.WIKIDATA_URL);
|
return new WikiSite(BuildConfig.WIKIDATA_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
||||||
* @return returns a singleton Gson instance
|
* @return returns a singleton Gson instance
|
||||||
|
|
@ -163,6 +161,18 @@ public class NetworkingModule {
|
||||||
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, ReviewInterface.class);
|
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, ReviewInterface.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public DepictsInterface provideDepictsInterface(@Named(NAMED_WIKI_DATA_WIKI_SITE) WikiSite wikidataWikiSite) {
|
||||||
|
return ServiceFactory.get(wikidataWikiSite, BuildConfig.WIKIDATA_URL, DepictsInterface.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public WikiBaseInterface provideWikiBaseInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||||
|
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, WikiBaseInterface.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public UploadInterface provideUploadInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
public UploadInterface provideUploadInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||||
|
|
@ -198,6 +208,12 @@ public class NetworkingModule {
|
||||||
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, MediaInterface.class);
|
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, MediaInterface.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public MediaDetailInterface providesMediaDetailInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikisite) {
|
||||||
|
return ServiceFactory.get(commonsWikisite, BuildConfig.COMMONS_URL, MediaDetailInterface.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||||
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragment;
|
||||||
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
||||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
|
@ -50,6 +51,7 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
|
|
||||||
private SearchImageFragment searchImageFragment;
|
private SearchImageFragment searchImageFragment;
|
||||||
private SearchCategoryFragment searchCategoryFragment;
|
private SearchCategoryFragment searchCategoryFragment;
|
||||||
|
private SearchDepictionsFragment searchDepictionsFragment;
|
||||||
private RecentSearchesFragment recentSearchesFragment;
|
private RecentSearchesFragment recentSearchesFragment;
|
||||||
private FragmentManager supportFragmentManager;
|
private FragmentManager supportFragmentManager;
|
||||||
private MediaDetailPagerFragment mediaDetails;
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
|
|
@ -68,6 +70,7 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
setSearchHistoryFragment();
|
setSearchHistoryFragment();
|
||||||
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
||||||
viewPager.setAdapter(viewPagerAdapter);
|
viewPager.setAdapter(viewPagerAdapter);
|
||||||
|
viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
setTabs();
|
setTabs();
|
||||||
searchView.setQueryHint(getString(R.string.search_commons));
|
searchView.setQueryHint(getString(R.string.search_commons));
|
||||||
|
|
@ -93,11 +96,14 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
List<Fragment> fragmentList = new ArrayList<>();
|
List<Fragment> fragmentList = new ArrayList<>();
|
||||||
List<String> titleList = new ArrayList<>();
|
List<String> titleList = new ArrayList<>();
|
||||||
searchImageFragment = new SearchImageFragment();
|
searchImageFragment = new SearchImageFragment();
|
||||||
|
searchDepictionsFragment = new SearchDepictionsFragment();
|
||||||
searchCategoryFragment= new SearchCategoryFragment();
|
searchCategoryFragment= new SearchCategoryFragment();
|
||||||
fragmentList.add(searchImageFragment);
|
fragmentList.add(searchImageFragment);
|
||||||
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase());
|
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase());
|
||||||
fragmentList.add(searchCategoryFragment);
|
fragmentList.add(searchCategoryFragment);
|
||||||
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase());
|
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase());
|
||||||
|
fragmentList.add(searchDepictionsFragment);
|
||||||
|
titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase());
|
||||||
|
|
||||||
viewPagerAdapter.setTabData(fragmentList, titleList);
|
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||||
viewPagerAdapter.notifyDataSetChanged();
|
viewPagerAdapter.notifyDataSetChanged();
|
||||||
|
|
@ -112,6 +118,11 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
viewPager.setVisibility(View.VISIBLE);
|
viewPager.setVisibility(View.VISIBLE);
|
||||||
tabLayout.setVisibility(View.VISIBLE);
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
searchHistoryContainer.setVisibility(View.GONE);
|
searchHistoryContainer.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (FragmentUtils.isFragmentUIActive(searchDepictionsFragment)) {
|
||||||
|
searchDepictionsFragment.updateDepictionList(query.toString());
|
||||||
|
}
|
||||||
|
|
||||||
if (FragmentUtils.isFragmentUIActive(searchImageFragment)) {
|
if (FragmentUtils.isFragmentUIActive(searchImageFragment)) {
|
||||||
searchImageFragment.updateImageList(query.toString());
|
searchImageFragment.updateImageList(query.toString());
|
||||||
}
|
}
|
||||||
|
|
@ -119,6 +130,7 @@ public class SearchActivity extends NavigationBaseActivity
|
||||||
if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
|
if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
|
||||||
searchCategoryFragment.updateCategoryList(query.toString());
|
searchCategoryFragment.updateCategoryList(query.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
}else {
|
}else {
|
||||||
//Open RecentSearchesFragment
|
//Open RecentSearchesFragment
|
||||||
recentSearchesFragment.updateRecentSearches();
|
recentSearchesFragment.updateRecentSearches();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package fr.free.nrw.commons.explore;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentContract;
|
||||||
|
import fr.free.nrw.commons.explore.depictions.SearchDepictionsFragmentPresenter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dagger Module for explore:depictions related presenters and (some other objects maybe in future)
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class SearchModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract SearchDepictionsFragmentContract.UserActionListener bindsSearchDepictionsFragmentPresenter(
|
||||||
|
SearchDepictionsFragmentPresenter
|
||||||
|
presenter
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -217,7 +217,7 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
||||||
private void initErrorView() {
|
private void initErrorView() {
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
categoriesNotFoundView.setVisibility(VISIBLE);
|
categoriesNotFoundView.setVisibility(VISIBLE);
|
||||||
categoriesNotFoundView.setText(getString(R.string.categories_not_found));
|
categoriesNotFoundView.setText(getString(R.string.categories_not_found,query));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.depictions.models.Search;
|
||||||
|
import fr.free.nrw.commons.media.MediaInterface;
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataProperties;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import org.wikipedia.wikidata.DataValue.DataValueString;
|
||||||
|
import org.wikipedia.wikidata.Statement_partial;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depicts Client to handle custom calls to Commons Wikibase APIs
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class DepictsClient {
|
||||||
|
|
||||||
|
private final DepictsInterface depictsInterface;
|
||||||
|
private final MediaInterface mediaInterface;
|
||||||
|
private static final String NO_DEPICTED_IMAGE = "No Image for Depiction";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DepictsClient(DepictsInterface depictsInterface, MediaInterface mediaInterface) {
|
||||||
|
this.depictsInterface = depictsInterface;
|
||||||
|
this.mediaInterface = mediaInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for depictions using the search item
|
||||||
|
* @return list of depicted items
|
||||||
|
*/
|
||||||
|
public Observable<DepictedItem> searchForDepictions(String query, int limit, int offset) {
|
||||||
|
return depictsInterface.searchForDepicts(
|
||||||
|
query,
|
||||||
|
String.valueOf(limit),
|
||||||
|
Locale.getDefault().getLanguage(),
|
||||||
|
Locale.getDefault().getLanguage(),
|
||||||
|
String.valueOf(offset)
|
||||||
|
)
|
||||||
|
.flatMap(depictSearchResponse ->Observable.fromIterable(depictSearchResponse.getSearch()))
|
||||||
|
.map(DepictedItem::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL for image using image name
|
||||||
|
* Ex: title = Guion Bluford
|
||||||
|
* Url = https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Guion_Bluford.jpg/70px-Guion_Bluford.jpg
|
||||||
|
*/
|
||||||
|
private String getThumbnailUrl(String title) {
|
||||||
|
String baseUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/";
|
||||||
|
title = title.replace(" ", "_");
|
||||||
|
String MD5Hash = getMd5(title);
|
||||||
|
/**
|
||||||
|
* We use 70 pixels as the size of our Thumbnail (as it is the perfect fits our UI)
|
||||||
|
*/
|
||||||
|
return baseUrl + MD5Hash.charAt(0) + '/' + MD5Hash.charAt(0) + MD5Hash.charAt(1) + '/' + title + "/70px-" + title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ex: entityId = Q357458
|
||||||
|
* value returned = Elgin Baylor Night program.jpeg
|
||||||
|
*/
|
||||||
|
public Single<String> getP18ForItem(String entityId) {
|
||||||
|
return depictsInterface.getImageForEntity(entityId)
|
||||||
|
.map(claimsResponse -> {
|
||||||
|
final List<Statement_partial> imageClaim = claimsResponse.getClaims()
|
||||||
|
.get(WikidataProperties.IMAGE.getPropertyName());
|
||||||
|
if (imageClaim != null) {
|
||||||
|
final DataValueString dataValue = (DataValueString) imageClaim
|
||||||
|
.get(0)
|
||||||
|
.getMainSnak()
|
||||||
|
.getDataValue();
|
||||||
|
return getThumbnailUrl((dataValue.getValue()));
|
||||||
|
}
|
||||||
|
return NO_DEPICTED_IMAGE;
|
||||||
|
})
|
||||||
|
.singleOrError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list of images for a particular depict entity
|
||||||
|
*/
|
||||||
|
public Observable<List<Media>> fetchImagesForDepictedItem(String query, int sroffset) {
|
||||||
|
return mediaInterface.fetchImagesForDepictedItem("haswbstatement:" + BuildConfig.DEPICTS_PROPERTY + "=" + query, String.valueOf(sroffset))
|
||||||
|
.map(mwQueryResponse -> {
|
||||||
|
List<Media> mediaList = new ArrayList<>();
|
||||||
|
for (Search s: mwQueryResponse.getQuery().getSearch()) {
|
||||||
|
Media media = new Media(null,
|
||||||
|
getUrl(s.getTitle()),
|
||||||
|
s.getTitle(),
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
safeParseDate(s.getTimestamp()),
|
||||||
|
safeParseDate(s.getTimestamp()),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
mediaList.add(media);
|
||||||
|
}
|
||||||
|
return mediaList;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get url for the image from media of depictions
|
||||||
|
* Ex: Tiger_Woods
|
||||||
|
* Value: https://upload.wikimedia.org/wikipedia/commons/thumb/6/67/Tiger_Woods.jpg/70px-Tiger_Woods.jpg
|
||||||
|
*/
|
||||||
|
private String getUrl(String title) {
|
||||||
|
String baseUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/";
|
||||||
|
title = title.substring(title.indexOf(':')+1);
|
||||||
|
title = title.replace(" ", "_");
|
||||||
|
String MD5Hash = getMd5(title);
|
||||||
|
return baseUrl + MD5Hash.charAt(0) + '/' + MD5Hash.charAt(0) + MD5Hash.charAt(1) + '/' + title + "/640px-" + title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates MD5 hash for the filename
|
||||||
|
*/
|
||||||
|
public String getMd5(String input)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Static getInstance method is called with hashing MD5
|
||||||
|
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||||
|
|
||||||
|
// digest() method is called to calculate message digest
|
||||||
|
// of an input digest() return array of byte
|
||||||
|
byte[] messageDigest = md.digest(input.getBytes());
|
||||||
|
|
||||||
|
// Convert byte array into signum representation
|
||||||
|
BigInteger no = new BigInteger(1, messageDigest);
|
||||||
|
|
||||||
|
// Convert message digest into hex value
|
||||||
|
String hashtext = no.toString(16);
|
||||||
|
while (hashtext.length() < 32) {
|
||||||
|
hashtext = "0" + hashtext;
|
||||||
|
}
|
||||||
|
return hashtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For specifying wrong message digest algorithms
|
||||||
|
catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the date string into the required format
|
||||||
|
* @param dateStr
|
||||||
|
* @return date in the required format
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Date safeParseDate(String dateStr) {
|
||||||
|
try {
|
||||||
|
return CommonsDateUtil.getIso8601DateFormatShort().parse(dateStr);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import com.pedrogomez.renderers.RendererBuilder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter factory for Items in Explore
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class SearchDepictionsAdapterFactory {
|
||||||
|
private final SearchDepictionsRenderer.DepictCallback listener;
|
||||||
|
|
||||||
|
public SearchDepictionsAdapterFactory(SearchDepictionsRenderer.DepictCallback listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RVRendererAdapter<DepictedItem> create() {
|
||||||
|
List<DepictedItem> searchImageItemList = new ArrayList<>();
|
||||||
|
RendererBuilder<DepictedItem> builder = new RendererBuilder<DepictedItem>().bind(DepictedItem.class, new SearchDepictionsRenderer(listener));
|
||||||
|
ListAdapteeCollection<DepictedItem> collection = new ListAdapteeCollection<>(
|
||||||
|
searchImageItemList != null ? searchImageItemList : Collections.<DepictedItem>emptyList());
|
||||||
|
return new RVRendererAdapter<>(builder, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display depictions in search fragment
|
||||||
|
*/
|
||||||
|
public class SearchDepictionsFragment extends CommonsDaggerSupportFragment implements SearchDepictionsFragmentContract.View {
|
||||||
|
|
||||||
|
@BindView(R.id.imagesListBox)
|
||||||
|
RecyclerView depictionsRecyclerView;
|
||||||
|
@BindView(R.id.imageSearchInProgress)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
@BindView(R.id.imagesNotFound)
|
||||||
|
TextView depictionNotFound;
|
||||||
|
@BindView(R.id.bottomProgressBar)
|
||||||
|
ProgressBar bottomProgressBar;
|
||||||
|
RecyclerView.LayoutManager layoutManager;
|
||||||
|
private boolean isLoading = true;
|
||||||
|
private int PAGE_SIZE = 25;
|
||||||
|
@Inject
|
||||||
|
SearchDepictionsFragmentPresenter presenter;
|
||||||
|
private final SearchDepictionsAdapterFactory adapterFactory = new SearchDepictionsAdapterFactory(new SearchDepictionsRenderer.DepictCallback() {
|
||||||
|
@Override
|
||||||
|
public void depictsClicked(DepictedItem item) {
|
||||||
|
WikidataItemDetailsActivity.startYourself(getContext(), item);
|
||||||
|
presenter.saveQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*fetch thumbnail image for all the depicted items (if available)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fetchThumbnailUrlForEntity(String entityId, int position) {
|
||||||
|
presenter.fetchThumbnailForEntityId(entityId,position);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
private RVRendererAdapter<DepictedItem> depictionsAdapter;
|
||||||
|
private boolean isLastPage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
if (getActivity().getResources().getConfiguration().orientation
|
||||||
|
== Configuration.ORIENTATION_PORTRAIT) {
|
||||||
|
layoutManager = new LinearLayoutManager(getContext());
|
||||||
|
} else {
|
||||||
|
layoutManager = new GridLayoutManager(getContext(), 2);
|
||||||
|
}
|
||||||
|
depictionsRecyclerView.setLayoutManager(layoutManager);
|
||||||
|
depictionsAdapter = adapterFactory.create();
|
||||||
|
depictionsRecyclerView.setAdapter(depictionsAdapter);
|
||||||
|
depictionsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||||
|
super.onScrollStateChanged(recyclerView, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy);
|
||||||
|
|
||||||
|
int visibleItemCount = layoutManager.getChildCount();
|
||||||
|
int totalItemCount = layoutManager.getItemCount();
|
||||||
|
int firstVisibleItemPosition=0;
|
||||||
|
if(layoutManager instanceof GridLayoutManager){
|
||||||
|
firstVisibleItemPosition=((GridLayoutManager) layoutManager).findFirstVisibleItemPosition();
|
||||||
|
} else {
|
||||||
|
firstVisibleItemPosition=((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user isn't currently loading items and the last page hasn’t been reached,
|
||||||
|
* then it checks against the current position in view to decide whether or not to load more items.
|
||||||
|
*/
|
||||||
|
if (!isLoading && !isLastPage) {
|
||||||
|
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
|
||||||
|
&& firstVisibleItemPosition >= 0
|
||||||
|
&& totalItemCount >= PAGE_SIZE) {
|
||||||
|
loadMoreItems(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rootView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch PAGE_SIZE number of items
|
||||||
|
*/
|
||||||
|
private void loadMoreItems(boolean reInitialise) {
|
||||||
|
presenter.updateDepictionList(presenter.getQuery(),PAGE_SIZE, reInitialise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user selects "Items" from Search Activity
|
||||||
|
* to load the list of depictions from API
|
||||||
|
*
|
||||||
|
* @param query string searched in the Explore Activity
|
||||||
|
*/
|
||||||
|
public void updateDepictionList(String query) {
|
||||||
|
presenter.initializeQuery(query);
|
||||||
|
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadMoreItems(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initErrorView() {
|
||||||
|
isLoading = false;
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
bottomProgressBar.setVisibility(GONE);
|
||||||
|
depictionNotFound.setVisibility(VISIBLE);
|
||||||
|
String no_depiction = getString(R.string.depictions_not_found);
|
||||||
|
depictionNotFound.setText(String.format(Locale.getDefault(), no_depiction, presenter.getQuery()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
depictionsAdapter.clear();
|
||||||
|
depictionsRecyclerView.cancelPendingInputEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a non empty list is successfully returned from the api then modify the view
|
||||||
|
* like hiding empty labels, hiding progressbar and notifying the apdapter that list of items has been fetched from the API
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<DepictedItem> mediaList) {
|
||||||
|
isLoading = false;
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
depictionNotFound.setVisibility(GONE);
|
||||||
|
bottomProgressBar.setVisibility(GONE);
|
||||||
|
int itemCount = layoutManager.getItemCount();
|
||||||
|
depictionsAdapter.addAll(mediaList);
|
||||||
|
if(itemCount!=0) {
|
||||||
|
depictionsAdapter.notifyItemRangeInserted(itemCount, mediaList.size()-1);
|
||||||
|
}else{
|
||||||
|
depictionsAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadingDepictions(boolean isLoading) {
|
||||||
|
depictionNotFound.setVisibility(GONE);
|
||||||
|
bottomProgressBar.setVisibility(View.VISIBLE);
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
this.isLoading = isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearAdapter() {
|
||||||
|
depictionsAdapter.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showSnackbar() {
|
||||||
|
ViewUtil.showShortSnackbar(depictionsRecyclerView, R.string.error_loading_depictions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RVRendererAdapter<DepictedItem> getAdapter() {
|
||||||
|
return depictionsAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageUrlFetched(String response, int position) {
|
||||||
|
depictionsAdapter.getItem(position).setImageUrl(response);
|
||||||
|
depictionsAdapter.notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the view that there are no more items to be loaded for this search query
|
||||||
|
* or reset the isLastPage for the current query
|
||||||
|
* @param isLastPage
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setIsLastPage(boolean isLastPage) {
|
||||||
|
this.isLastPage=isLastPage;
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract with with SearchDepictionsFragment and its presenter would talk to each other
|
||||||
|
*/
|
||||||
|
public interface SearchDepictionsFragmentContract {
|
||||||
|
|
||||||
|
interface View {
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
void initErrorView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
void handleNoInternet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a non empty list is successfully returned from the api then modify the view
|
||||||
|
* like hiding empty labels, hiding progressbar and notifying the apdapter that list of items has been fetched from the API
|
||||||
|
*/
|
||||||
|
void onSuccess(List<DepictedItem> mediaList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* load depictions
|
||||||
|
*/
|
||||||
|
void loadingDepictions(boolean isLoading);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear adapter
|
||||||
|
*/
|
||||||
|
void clearAdapter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show snackbar
|
||||||
|
*/
|
||||||
|
void showSnackbar();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return adapter
|
||||||
|
*/
|
||||||
|
RVRendererAdapter<DepictedItem> getAdapter();
|
||||||
|
|
||||||
|
void onImageUrlFetched(String response, int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the view that there are no more items to be loaded for this search query
|
||||||
|
* or reset the isLastPage for the current query
|
||||||
|
* @param isLastPage
|
||||||
|
*/
|
||||||
|
void setIsLastPage(boolean isLastPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user selects "Items" from Search Activity
|
||||||
|
* to load the list of depictions from API
|
||||||
|
*
|
||||||
|
* @param query string searched in the Explore Activity
|
||||||
|
* @param reInitialise
|
||||||
|
*/
|
||||||
|
void updateDepictionList(String query, int pageSize, boolean reInitialise);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method saves Search Query in the Recent Searches Database.
|
||||||
|
*/
|
||||||
|
void saveQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whenever a new query is initiated from the search activity clear the previous adapter
|
||||||
|
* and add new value of the query
|
||||||
|
*/
|
||||||
|
void initializeQuery(String query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return query
|
||||||
|
*/
|
||||||
|
String getQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After all the depicted items are loaded fetch thumbnail image for all the depicted items (if available)
|
||||||
|
*/
|
||||||
|
void fetchThumbnailForEntityId(String entityId,int position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearch;
|
||||||
|
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||||
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The presenter class for SearchDepictionsFragment
|
||||||
|
*/
|
||||||
|
public class SearchDepictionsFragmentPresenter extends CommonsDaggerSupportFragment implements SearchDepictionsFragmentContract.UserActionListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This creates a dynamic proxy instance of the class,
|
||||||
|
* proxy is to control access to the target object
|
||||||
|
* here our target object is the view.
|
||||||
|
* Thus we when onDettach method of fragment is called we replace the binding of view to our object with the proxy instance
|
||||||
|
*/
|
||||||
|
private static final SearchDepictionsFragmentContract.View DUMMY = (SearchDepictionsFragmentContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
SearchDepictionsFragmentContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{SearchDepictionsFragmentContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
private final Scheduler ioScheduler;
|
||||||
|
private final Scheduler mainThreadScheduler;
|
||||||
|
|
||||||
|
boolean isLoadingDepictions;
|
||||||
|
String query;
|
||||||
|
RecentSearchesDao recentSearchesDao;
|
||||||
|
DepictsClient depictsClient;
|
||||||
|
JsonKvStore basicKvStore;
|
||||||
|
private SearchDepictionsFragmentContract.View view = DUMMY;
|
||||||
|
private List<DepictedItem> queryList = new ArrayList<>();
|
||||||
|
int offset=0;
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SearchDepictionsFragmentPresenter(@Named("default_preferences") JsonKvStore basicKvStore,
|
||||||
|
RecentSearchesDao recentSearchesDao,
|
||||||
|
DepictsClient depictsClient,
|
||||||
|
@Named(IO_THREAD) Scheduler ioScheduler,
|
||||||
|
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||||
|
this.basicKvStore = basicKvStore;
|
||||||
|
this.recentSearchesDao = recentSearchesDao;
|
||||||
|
this.depictsClient = depictsClient;
|
||||||
|
this.ioScheduler = ioScheduler;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachView(SearchDepictionsFragmentContract.View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user selects "Items" from Search Activity
|
||||||
|
* to load the list of depictions from API
|
||||||
|
*
|
||||||
|
* @param query string searched in the Explore Activity
|
||||||
|
* @param reInitialise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateDepictionList(String query, int pageSize, boolean reInitialise) {
|
||||||
|
this.query = query;
|
||||||
|
view.loadingDepictions(true);
|
||||||
|
if (reInitialise) {
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
saveQuery();
|
||||||
|
compositeDisposable.add(depictsClient.searchForDepictions(query, 25, offset)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.doOnSubscribe(disposable -> saveQuery())
|
||||||
|
.collect(ArrayList<DepictedItem>::new, ArrayList::add)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Error occurred while loading queried depictions");
|
||||||
|
view.initErrorView();
|
||||||
|
view.showSnackbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method saves Search Query in the Recent Searches Database.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void saveQuery() {
|
||||||
|
RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||||
|
|
||||||
|
// Newly searched query...
|
||||||
|
if (recentSearch == null) {
|
||||||
|
recentSearch = new RecentSearch(null, query, new Date());
|
||||||
|
} else {
|
||||||
|
recentSearch.setLastSearched(new Date());
|
||||||
|
}
|
||||||
|
recentSearchesDao.save(recentSearch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whenever a new query is initiated from the search activity clear the previous adapter
|
||||||
|
* and add new value of the query
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initializeQuery(String query) {
|
||||||
|
this.query = query;
|
||||||
|
this.queryList.clear();
|
||||||
|
offset = 0;//Reset the offset on query change
|
||||||
|
compositeDisposable.clear();
|
||||||
|
view.setIsLastPage(false);
|
||||||
|
view.clearAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* it initializes the recycler view by adding items to the adapter
|
||||||
|
*/
|
||||||
|
public void handleSuccess(List<DepictedItem> mediaList) {
|
||||||
|
if (mediaList == null || mediaList.isEmpty()) {
|
||||||
|
if(queryList.isEmpty()){
|
||||||
|
view.initErrorView();
|
||||||
|
}else{
|
||||||
|
view.setIsLastPage(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.queryList.addAll(mediaList);
|
||||||
|
view.onSuccess(mediaList);
|
||||||
|
offset=queryList.size();
|
||||||
|
for (DepictedItem m : mediaList) {
|
||||||
|
fetchThumbnailForEntityId(m.getId(), size++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After all the depicted items are loaded fetch thumbnail image for all the depicted items (if available)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fetchThumbnailForEntityId(String entityId,int position) {
|
||||||
|
compositeDisposable.add(depictsClient.getP18ForItem(entityId)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(response -> {
|
||||||
|
view.onImageUrlFetched(response,position);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package fr.free.nrw.commons.explore.depictions;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
|
import com.facebook.common.references.CloseableReference;
|
||||||
|
import com.facebook.datasource.DataSource;
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||||
|
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
|
||||||
|
import com.facebook.imagepipeline.image.CloseableImage;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderer for DepictedItem
|
||||||
|
*/
|
||||||
|
public class SearchDepictionsRenderer extends Renderer<DepictedItem> {
|
||||||
|
|
||||||
|
@BindView(R.id.depicts_label)
|
||||||
|
TextView tvDepictionLabel;
|
||||||
|
|
||||||
|
@BindView(R.id.description)
|
||||||
|
TextView tvDepictionDesc;
|
||||||
|
|
||||||
|
@BindView(R.id.depicts_image)
|
||||||
|
ImageView imageView;
|
||||||
|
|
||||||
|
private DepictCallback listener;
|
||||||
|
|
||||||
|
int size = 0;
|
||||||
|
private final static String NO_IMAGE_FOR_DEPICTION = "No Image for Depiction";
|
||||||
|
|
||||||
|
public SearchDepictionsRenderer(DepictCallback listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUpView(View rootView) {
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void hookListeners(View rootView) {
|
||||||
|
rootView.setOnClickListener(v -> {
|
||||||
|
DepictedItem item = getContent();
|
||||||
|
if (listener != null) {
|
||||||
|
listener.depictsClicked(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View inflate(LayoutInflater inflater, ViewGroup parent) {
|
||||||
|
return inflater.inflate(R.layout.item_depictions, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render value to all the items in the search depictions list
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
DepictedItem item = getContent();
|
||||||
|
tvDepictionLabel.setText(item.getName());
|
||||||
|
tvDepictionDesc.setText(item.getDescription());
|
||||||
|
imageView.setImageDrawable(getContext().getResources().getDrawable(R.drawable.ic_wikidata_logo_24dp));
|
||||||
|
|
||||||
|
Timber.e("line86"+item.getImageUrl());
|
||||||
|
if (!TextUtils.isEmpty(item.getImageUrl())) {
|
||||||
|
if (!item.getImageUrl().equals(NO_IMAGE_FOR_DEPICTION) && !item.getImageUrl().equals(""))
|
||||||
|
{
|
||||||
|
ImageRequest imageRequest = ImageRequestBuilder
|
||||||
|
.newBuilderWithSource(Uri.parse(item.getImageUrl()))
|
||||||
|
.setAutoRotateEnabled(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||||
|
final DataSource<CloseableReference<CloseableImage>>
|
||||||
|
dataSource = imagePipeline.fetchDecodedImage(imageRequest, getContext());
|
||||||
|
|
||||||
|
dataSource.subscribe(new BaseBitmapDataSubscriber() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||||
|
if (dataSource.isFinished() && bitmap != null) {
|
||||||
|
Timber.d("Bitmap loaded from url %s", item.getImageUrl());
|
||||||
|
//imageView.setImageBitmap(Bitmap.createBitmap(bitmap));
|
||||||
|
imageView.post(() -> imageView.setImageBitmap(Bitmap.createBitmap(bitmap)));
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailureImpl(DataSource dataSource) {
|
||||||
|
Timber.d("Error getting bitmap from image url %s", item.getImageUrl());
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, CallerThreadExecutor.getInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DepictCallback {
|
||||||
|
void depictsClicked(DepictedItem item);
|
||||||
|
|
||||||
|
void fetchThumbnailUrlForEntity(String entityId,int position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package fr.free.nrw.commons.explore.images;
|
package fr.free.nrw.commons.explore.images;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static fr.free.nrw.commons.depictions.Media.DepictedImagesFragment.PAGE_ID_PREFIX;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -8,23 +12,12 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
|
@ -37,11 +30,14 @@ import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the image search screen.
|
* Displays the image search screen.
|
||||||
*/
|
*/
|
||||||
|
|
@ -67,6 +63,11 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
JsonKvStore defaultKvStore;
|
JsonKvStore defaultKvStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A variable to store number of list items for whom API has been called to fetch captions
|
||||||
|
*/
|
||||||
|
private int mediaSize = 0;
|
||||||
|
|
||||||
private RVRendererAdapter<Media> imagesAdapter;
|
private RVRendererAdapter<Media> imagesAdapter;
|
||||||
private List<Media> queryList = new ArrayList<>();
|
private List<Media> queryList = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -101,7 +102,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||||
ButterKnife.bind(this, rootView);
|
ButterKnife.bind(this, rootView);
|
||||||
if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
if (getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||||
imagesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
imagesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -198,10 +199,39 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
imagesAdapter.addAll(mediaList);
|
imagesAdapter.addAll(mediaList);
|
||||||
imagesAdapter.notifyDataSetChanged();
|
imagesAdapter.notifyDataSetChanged();
|
||||||
((SearchActivity) getContext()).viewPagerNotifyDataSetChanged();
|
((SearchActivity)getContext()).viewPagerNotifyDataSetChanged();
|
||||||
|
for (Media m : mediaList) {
|
||||||
|
final String pageId = m.getPageId();
|
||||||
|
if (pageId != null) {
|
||||||
|
replaceTitlesWithCaptions(PAGE_ID_PREFIX + pageId, mediaSize++);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In explore we first show title and simultaneously call the API to retrieve captions
|
||||||
|
* When captions are retrieved they replace title
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void replaceTitlesWithCaptions(String wikibaseIdentifier, int position) {
|
||||||
|
compositeDisposable.add(mediaClient.getCaptionByWikibaseIdentifier(wikibaseIdentifier)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(subscriber -> {
|
||||||
|
handleLabelforImage(subscriber, position);
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLabelforImage(String s, int position) {
|
||||||
|
if (!s.trim().equals(getString(R.string.detail_caption_empty))) {
|
||||||
|
imagesAdapter.getItem(position).setThumbnailTitle(s);
|
||||||
|
imagesAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs and handles API error scenario
|
* Logs and handles API error scenario
|
||||||
* @param throwable
|
* @param throwable
|
||||||
|
|
@ -221,7 +251,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
||||||
private void initErrorView() {
|
private void initErrorView() {
|
||||||
progressBar.setVisibility(GONE);
|
progressBar.setVisibility(GONE);
|
||||||
imagesNotFoundView.setVisibility(VISIBLE);
|
imagesNotFoundView.setVisibility(VISIBLE);
|
||||||
imagesNotFoundView.setText(getString(R.string.images_not_found));
|
imagesNotFoundView.setText(getString(R.string.images_not_found,query));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ class SearchImagesRenderer extends Renderer<Media> {
|
||||||
@Override
|
@Override
|
||||||
public void render() {
|
public void render() {
|
||||||
Media item = getContent();
|
Media item = getContent();
|
||||||
tvImageName.setText(item.getDisplayTitle());
|
tvImageName.setText(item.getThumbnailTitle());
|
||||||
browseImage.setImageURI(item.getThumbUrl());
|
browseImage.setImageURI(item.getThumbUrl());
|
||||||
setAuthorView(item, categoryImageAuthor);
|
setAuthorView(item, categoryImageAuthor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
app/src/main/java/fr/free/nrw/commons/media/Caption.java
Normal file
43
app/src/main/java/fr/free/nrw/commons/media/Caption.java
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for parsing Captions when fetching captions using filename in MediaClient
|
||||||
|
*/
|
||||||
|
public class Caption {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* users language in which caption is written
|
||||||
|
*/
|
||||||
|
@SerializedName("language")
|
||||||
|
private String language;
|
||||||
|
@SerializedName("value")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*/
|
||||||
|
public Caption() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value
|
||||||
|
* @param language
|
||||||
|
*/
|
||||||
|
public Caption(String language, String value) {
|
||||||
|
super();
|
||||||
|
this.language = language;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("language")
|
||||||
|
public String getLanguage() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("value")
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the Wikibase item associated with a Wikimedia Commons file.
|
||||||
|
* For instance the Wikibase item M63996 represents the Commons file "Paul Cézanne - The Pigeon Tower at Bellevue - 1936.19 - Cleveland Museum of Art.jpg"
|
||||||
|
*/
|
||||||
|
public class CommonsWikibaseItem {
|
||||||
|
|
||||||
|
@SerializedName("type")
|
||||||
|
private String type;
|
||||||
|
@SerializedName("id")
|
||||||
|
private String id;
|
||||||
|
@SerializedName("labels")
|
||||||
|
private Map<String, Caption> labels;
|
||||||
|
@SerializedName("statements")
|
||||||
|
private Object statements = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No args constructor for use in serialization
|
||||||
|
*/
|
||||||
|
public CommonsWikibaseItem() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* @param statements
|
||||||
|
* @param labels
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
public CommonsWikibaseItem(String type, String id, Map<String, Caption> labels, Object statements) {
|
||||||
|
super();
|
||||||
|
this.type = type;
|
||||||
|
this.id = id;
|
||||||
|
this.labels = labels;
|
||||||
|
this.statements = statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ex: "mediainfo
|
||||||
|
*/
|
||||||
|
@SerializedName("type")
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Wikibase Id
|
||||||
|
*/
|
||||||
|
@SerializedName("id")
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return value of captions
|
||||||
|
*/
|
||||||
|
@SerializedName("labels")
|
||||||
|
public Map<String, Caption> getLabels() {
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains the Depicts item
|
||||||
|
*/
|
||||||
|
@SerializedName("statements")
|
||||||
|
public Object getStatements() {
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
30
app/src/main/java/fr/free/nrw/commons/media/Depictions.kt
Normal file
30
app/src/main/java/fr/free/nrw/commons/media/Depictions.kt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package fr.free.nrw.commons.media
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataProperties.DEPICTS
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import org.wikipedia.wikidata.DataValue.DataValueEntityId
|
||||||
|
import org.wikipedia.wikidata.Entities
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class Depictions(val depictions: List<IdAndLabel>) : Parcelable {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@WorkerThread
|
||||||
|
fun from(entities: Entities, mediaClient: MediaClient) =
|
||||||
|
Depictions(
|
||||||
|
entities.first?.statements
|
||||||
|
?.getOrElse(DEPICTS.propertyName, { emptyList() })
|
||||||
|
?.map { statement ->
|
||||||
|
(statement.mainSnak.dataValue as DataValueEntityId).value.id
|
||||||
|
}
|
||||||
|
?.map { id -> IdAndLabel(id, fetchLabel(mediaClient, id)) }
|
||||||
|
?: emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun fetchLabel(mediaClient: MediaClient, id: String) =
|
||||||
|
mediaClient.getLabelForDepiction(id, Locale.getDefault().language).blockingGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
14
app/src/main/java/fr/free/nrw/commons/media/IdAndLabel.kt
Normal file
14
app/src/main/java/fr/free/nrw/commons/media/IdAndLabel.kt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package fr.free.nrw.commons.media
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import org.wikipedia.wikidata.Entities
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class IdAndLabel(val entityId: String, val entityLabel: String) : Parcelable {
|
||||||
|
constructor(entityId: String, entities: MutableMap<String, Entities.Entity>) : this(
|
||||||
|
entityId,
|
||||||
|
entities.values.first().labels().values.first().value()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -2,23 +2,23 @@ package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
import fr.free.nrw.commons.Media;
|
import org.wikipedia.wikidata.Entities;
|
||||||
import fr.free.nrw.commons.utils.CommonsDateUtil;
|
import org.wikipedia.wikidata.Entities.Entity;
|
||||||
import io.reactivex.Observable;
|
import org.wikipedia.wikidata.Entities.Label;
|
||||||
import io.reactivex.Single;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -28,13 +28,17 @@ import timber.log.Timber;
|
||||||
public class MediaClient {
|
public class MediaClient {
|
||||||
|
|
||||||
private final MediaInterface mediaInterface;
|
private final MediaInterface mediaInterface;
|
||||||
|
private final MediaDetailInterface mediaDetailInterface;
|
||||||
|
|
||||||
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
//OkHttpJsonApiClient used JsonKvStore for this. I don't know why.
|
||||||
private Map<String, Map<String, String>> continuationStore;
|
private Map<String, Map<String, String>> continuationStore;
|
||||||
|
public static final String NO_CAPTION = "No caption";
|
||||||
|
private static final String NO_DEPICTION = "No depiction";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MediaClient(MediaInterface mediaInterface) {
|
public MediaClient(MediaInterface mediaInterface, MediaDetailInterface mediaDetailInterface) {
|
||||||
this.mediaInterface = mediaInterface;
|
this.mediaInterface = mediaInterface;
|
||||||
|
this.mediaDetailInterface = mediaDetailInterface;
|
||||||
this.continuationStore = new HashMap<>();
|
this.continuationStore = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +92,7 @@ public class MediaClient {
|
||||||
*/
|
*/
|
||||||
public Single<List<Media>> getMediaListFromSearch(String keyword) {
|
public Single<List<Media>> getMediaListFromSearch(String keyword) {
|
||||||
return responseToMediaList(
|
return responseToMediaList(
|
||||||
continuationStore.containsKey("search_" + keyword) ?
|
continuationStore.containsKey("search_" + keyword) && (continuationStore.get("search_" + keyword) != null) ?
|
||||||
mediaInterface.getMediaListFromSearch(keyword, 10, continuationStore.get("search_" + keyword)) : //if true
|
mediaInterface.getMediaListFromSearch(keyword, 10, continuationStore.get("search_" + keyword)) : //if true
|
||||||
mediaInterface.getMediaListFromSearch(keyword, 10, Collections.emptyMap()), //if false
|
mediaInterface.getMediaListFromSearch(keyword, 10, Collections.emptyMap()), //if false
|
||||||
"search_" + keyword);
|
"search_" + keyword);
|
||||||
|
|
@ -152,6 +156,7 @@ public class MediaClient {
|
||||||
.single(Media.EMPTY);
|
.single(Media.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Single<String> getPageHtml(String title){
|
public Single<String> getPageHtml(String title){
|
||||||
return mediaInterface.getPageHtml(title)
|
return mediaInterface.getPageHtml(title)
|
||||||
|
|
@ -160,4 +165,62 @@ public class MediaClient {
|
||||||
.map(MwParseResult::text)
|
.map(MwParseResult::text)
|
||||||
.first("");
|
.first("");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return caption for image using wikibaseIdentifier
|
||||||
|
*/
|
||||||
|
public Single<String> getCaptionByWikibaseIdentifier(String wikibaseIdentifier) {
|
||||||
|
return mediaDetailInterface.getCaptionForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
|
||||||
|
.map(mediaDetailResponse -> {
|
||||||
|
if (isSuccess(mediaDetailResponse)) {
|
||||||
|
for (Entity wikibaseItem : mediaDetailResponse.entities().values()) {
|
||||||
|
for (Label label : wikibaseItem.labels().values()) {
|
||||||
|
return label.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NO_CAPTION;
|
||||||
|
})
|
||||||
|
.singleOrError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSuccess(Entities response) {
|
||||||
|
return response != null && response.getSuccess() == 1 && response.entities() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches Structured data from API
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* @return a map containing caption and depictions (empty string in the map if no caption/depictions)
|
||||||
|
*/
|
||||||
|
public Single<Depictions> getDepictions(String filename) {
|
||||||
|
return mediaDetailInterface.fetchEntitiesByFileName(Locale.getDefault().getLanguage(), filename)
|
||||||
|
.map(entities -> Depictions.from(entities, this))
|
||||||
|
.singleOrError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets labels for Depictions using Entity Id from MediaWikiAPI
|
||||||
|
*
|
||||||
|
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||||
|
* @return label
|
||||||
|
*/
|
||||||
|
public Single<String> getLabelForDepiction(String entityId, String language) {
|
||||||
|
return mediaDetailInterface.getEntity(entityId, language)
|
||||||
|
.map(entities -> {
|
||||||
|
if (isSuccess(entities)) {
|
||||||
|
for (Entity entity : entities.entities().values()) {
|
||||||
|
for (Label label : entity.labels().values()) {
|
||||||
|
return label.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("failed getEntities");
|
||||||
|
})
|
||||||
|
.singleOrError();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
package fr.free.nrw.commons.media;
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.graphics.drawable.Animatable;
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
|
@ -22,28 +25,17 @@ import android.widget.ScrollView;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
|
||||||
import com.facebook.drawee.interfaces.DraweeController;
|
|
||||||
import com.facebook.drawee.controller.BaseControllerListener;
|
|
||||||
import com.facebook.drawee.controller.ControllerListener;
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
|
||||||
import com.facebook.imagepipeline.image.ImageInfo;
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.wikipedia.util.DateUtil;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import androidx.annotation.Nullable;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.drawee.controller.BaseControllerListener;
|
||||||
|
import com.facebook.drawee.controller.ControllerListener;
|
||||||
|
import com.facebook.drawee.interfaces.DraweeController;
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
import com.facebook.imagepipeline.image.ImageInfo;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.MediaDataExtractor;
|
import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
@ -53,19 +45,24 @@ import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||||
import fr.free.nrw.commons.delete.DeleteHelper;
|
import fr.free.nrw.commons.delete.DeleteHelper;
|
||||||
import fr.free.nrw.commons.delete.ReasonBuilder;
|
import fr.free.nrw.commons.delete.ReasonBuilder;
|
||||||
|
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.ui.widget.CompatTextView;
|
import fr.free.nrw.commons.ui.widget.CompatTextView;
|
||||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.util.DateUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
|
|
||||||
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private boolean editable;
|
private boolean editable;
|
||||||
|
|
@ -108,6 +105,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
LinearLayout imageSpacer;
|
LinearLayout imageSpacer;
|
||||||
@BindView(R.id.mediaDetailTitle)
|
@BindView(R.id.mediaDetailTitle)
|
||||||
TextView title;
|
TextView title;
|
||||||
|
@BindView(R.id.caption_layout)
|
||||||
|
LinearLayout captionLayout;
|
||||||
|
@BindView(R.id.depicts_layout)
|
||||||
|
LinearLayout depictsLayout;
|
||||||
|
@BindView(R.id.media_detail_caption)
|
||||||
|
TextView mediaCaption;
|
||||||
@BindView(R.id.mediaDetailDesc)
|
@BindView(R.id.mediaDetailDesc)
|
||||||
HtmlTextView desc;
|
HtmlTextView desc;
|
||||||
@BindView(R.id.mediaDetailAuthor)
|
@BindView(R.id.mediaDetailAuthor)
|
||||||
|
|
@ -126,6 +129,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
LinearLayout nominatedForDeletion;
|
LinearLayout nominatedForDeletion;
|
||||||
@BindView(R.id.mediaDetailCategoryContainer)
|
@BindView(R.id.mediaDetailCategoryContainer)
|
||||||
LinearLayout categoryContainer;
|
LinearLayout categoryContainer;
|
||||||
|
@BindView(R.id.media_detail_depiction_container)
|
||||||
|
LinearLayout depictionContainer;
|
||||||
@BindView(R.id.authorLinearLayout)
|
@BindView(R.id.authorLinearLayout)
|
||||||
LinearLayout authorLayout;
|
LinearLayout authorLayout;
|
||||||
@BindView(R.id.nominateDeletion)
|
@BindView(R.id.nominateDeletion)
|
||||||
|
|
@ -134,8 +139,15 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
ScrollView scrollView;
|
ScrollView scrollView;
|
||||||
|
|
||||||
private ArrayList<String> categoryNames;
|
private ArrayList<String> categoryNames;
|
||||||
|
/**
|
||||||
|
* Depicts is a feature part of Structured data. Multiple Depictions can be added for an image just like categories.
|
||||||
|
* However unlike categories depictions is multi-lingual
|
||||||
|
* Ex: key: en value: monument
|
||||||
|
*/
|
||||||
|
private Depictions depictions;
|
||||||
private boolean categoriesLoaded = false;
|
private boolean categoriesLoaded = false;
|
||||||
private boolean categoriesPresent = false;
|
private boolean categoriesPresent = false;
|
||||||
|
private boolean depictionLoaded = false;
|
||||||
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
|
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
|
||||||
private ViewTreeObserver.OnScrollChangedListener scrollListener;
|
private ViewTreeObserver.OnScrollChangedListener scrollListener;
|
||||||
|
|
||||||
|
|
@ -243,7 +255,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
desc.setHtmlText(media.getDescription());
|
desc.setHtmlText(media.getDescription());
|
||||||
license.setText(media.getLicense());
|
license.setText(media.getLicense());
|
||||||
|
|
||||||
Disposable disposable = mediaDataExtractor.fetchMediaDetails(media.getFilename())
|
Disposable disposable = mediaDataExtractor.fetchMediaDetails(media.getFilename(), media.getPageId())
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(this::setTextFields);
|
.subscribe(this::setTextFields);
|
||||||
|
|
@ -318,18 +330,32 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
coordinates.setText(prettyCoordinates(media));
|
coordinates.setText(prettyCoordinates(media));
|
||||||
uploadedDate.setText(prettyUploadedDate(media));
|
uploadedDate.setText(prettyUploadedDate(media));
|
||||||
mediaDiscussion.setText(prettyDiscussion(media));
|
mediaDiscussion.setText(prettyDiscussion(media));
|
||||||
|
if (prettyCaption(media).equals(getContext().getString(R.string.detail_caption_empty))) {
|
||||||
|
captionLayout.setVisibility(GONE);
|
||||||
|
} else mediaCaption.setText(prettyCaption(media));
|
||||||
|
|
||||||
|
|
||||||
categoryNames.clear();
|
categoryNames.clear();
|
||||||
categoryNames.addAll(media.getCategories());
|
categoryNames.addAll(media.getCategories());
|
||||||
|
|
||||||
|
depictions=media.getDepiction();
|
||||||
|
|
||||||
|
depictionLoaded = true;
|
||||||
|
|
||||||
categoriesLoaded = true;
|
categoriesLoaded = true;
|
||||||
categoriesPresent = (categoryNames.size() > 0);
|
categoriesPresent = (categoryNames.size() > 0);
|
||||||
if (!categoriesPresent) {
|
if (!categoriesPresent) {
|
||||||
// Stick in a filler element.
|
// Stick in a filler element.
|
||||||
categoryNames.add(getString(R.string.detail_panel_cats_none));
|
categoryNames.add(getString(R.string.detail_panel_cats_none));
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildCatList();
|
rebuildCatList();
|
||||||
|
|
||||||
|
if(depictions != null) {
|
||||||
|
rebuildDepictionList();
|
||||||
|
}
|
||||||
|
else depictsLayout.setVisibility(GONE);
|
||||||
|
|
||||||
if (media.getCreator() == null || media.getCreator().equals("")) {
|
if (media.getCreator() == null || media.getCreator().equals("")) {
|
||||||
authorLayout.setVisibility(GONE);
|
authorLayout.setVisibility(GONE);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -339,6 +365,21 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
checkDeletion(media);
|
checkDeletion(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates media details fragment with depiction list
|
||||||
|
*/
|
||||||
|
private void rebuildDepictionList() {
|
||||||
|
depictionContainer.removeAllViews();
|
||||||
|
for (IdAndLabel depiction : depictions.getDepictions()) {
|
||||||
|
depictionContainer.addView(
|
||||||
|
buildDepictLabel(
|
||||||
|
depiction.getEntityLabel(),
|
||||||
|
depiction.getEntityId(),
|
||||||
|
depictionContainer
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OnClick(R.id.mediaDetailLicense)
|
@OnClick(R.id.mediaDetailLicense)
|
||||||
public void onMediaDetailLicenceClicked(){
|
public void onMediaDetailLicenceClicked(){
|
||||||
String url = media.getLicenseUrl();
|
String url = media.getLicenseUrl();
|
||||||
|
|
@ -505,6 +546,26 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add view to depictions obtained also tapping on depictions should open the url
|
||||||
|
*/
|
||||||
|
private View buildDepictLabel(String depictionName, String entityId, LinearLayout depictionContainer) {
|
||||||
|
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, depictionContainer, false);
|
||||||
|
final CompatTextView textView = item.findViewById(R.id.mediaDetailCategoryItemText);
|
||||||
|
|
||||||
|
textView.setText(depictionName);
|
||||||
|
if (depictionLoaded) {
|
||||||
|
item.setOnClickListener(view -> {
|
||||||
|
DepictedItem depictedItem = new DepictedItem(depictionName, "", "", false, entityId);
|
||||||
|
Intent intent = new Intent(getContext(), WikidataItemDetailsActivity.class);
|
||||||
|
intent.putExtra("wikidataItemName", depictedItem.getName());
|
||||||
|
intent.putExtra("entityId", depictedItem.getId());
|
||||||
|
getContext().startActivity(intent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
|
private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
|
||||||
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
|
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
|
||||||
final CompatTextView textView = item.findViewById(R.id.mediaDetailCategoryItemText);
|
final CompatTextView textView = item.findViewById(R.id.mediaDetailCategoryItemText);
|
||||||
|
|
@ -534,9 +595,24 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
image.setAlpha(1.0f - scrollPercentage);
|
image.setAlpha(1.0f - scrollPercentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns captions for media details
|
||||||
|
*
|
||||||
|
* @param media object of class media
|
||||||
|
* @return caption as string
|
||||||
|
*/
|
||||||
|
private String prettyCaption(Media media) {
|
||||||
|
String caption = media.getCaption().trim();
|
||||||
|
if (caption.equals("")) {
|
||||||
|
return getString(R.string.detail_caption_empty);
|
||||||
|
} else {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String prettyDescription(Media media) {
|
private String prettyDescription(Media media) {
|
||||||
// @todo use UI language when multilingual descs are available
|
// @todo use UI language when multilingual descs are available
|
||||||
String desc = media.getDescription(locale.getLanguage()).trim();
|
String desc = media.getDescription();
|
||||||
if (desc.equals("")) {
|
if (desc.equals("")) {
|
||||||
return getString(R.string.detail_description_empty);
|
return getString(R.string.detail_description_empty);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -582,7 +658,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDeletion(Media media){
|
private void checkDeletion(Media media){
|
||||||
if (media.getRequestedDeletion()){
|
if (media.isRequestedDeletion()){
|
||||||
delete.setVisibility(GONE);
|
delete.setVisibility(GONE);
|
||||||
nominatedForDeletion.setVisibility(VISIBLE);
|
nominatedForDeletion.setVisibility(VISIBLE);
|
||||||
} else if (!isCategoryImage) {
|
} else if (!isCategoryImage) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import org.wikipedia.wikidata.Entities;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for interacting with Commons Structured Data related APIs
|
||||||
|
*/
|
||||||
|
public interface MediaDetailInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches entity using file name
|
||||||
|
*
|
||||||
|
* @param filename name of the file to be used for fetching captions
|
||||||
|
*/
|
||||||
|
@GET("w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
|
||||||
|
Observable<Entities> fetchEntitiesByFileName(@Query("languages") String language, @Query("titles") String filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets labels for Depictions using Entity Id from MediaWikiAPI
|
||||||
|
*
|
||||||
|
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||||
|
* @param language user's locale
|
||||||
|
*/
|
||||||
|
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
||||||
|
Observable<Entities> getEntity(@Query("ids") String entityId, @Query("languages") String language);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches caption using wikibaseIdentifier
|
||||||
|
*
|
||||||
|
* @param wikibaseIdentifier pageId for the media
|
||||||
|
*/
|
||||||
|
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
|
||||||
|
Observable<Entities> getCaptionForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.depictions.models.DepictionResponse;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
@ -83,4 +84,23 @@ public interface MediaInterface {
|
||||||
|
|
||||||
@GET("w/api.php?format=json&action=parse&prop=text")
|
@GET("w/api.php?format=json&action=parse&prop=text")
|
||||||
Observable<MwParseResponse> getPageHtml(@Query("page") String title);
|
Observable<MwParseResponse> getPageHtml(@Query("page") String title);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches caption using file name
|
||||||
|
*
|
||||||
|
* @param filename name of the file to be used for fetching captions
|
||||||
|
* */
|
||||||
|
@GET("w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1")
|
||||||
|
Observable<MwQueryResponse> fetchCaptionByFilename(@Query("language") String language, @Query("titles") String filename);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches list of images from a depiction entity
|
||||||
|
*
|
||||||
|
* @param query depictionEntityId
|
||||||
|
* @param sroffset number od depictions already fetched, this is useful in implementing pagination
|
||||||
|
*/
|
||||||
|
|
||||||
|
@GET("w/api.php?action=query&list=search&format=json&srnamespace=6")
|
||||||
|
Observable<DepictionResponse> fetchImagesForDepictedItem(@Query("srsearch") String query, @Query("sroffset") String sroffset);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,34 @@
|
||||||
package fr.free.nrw.commons.mwapi;
|
package fr.free.nrw.commons.mwapi;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.achievements.FeaturedImages;
|
import fr.free.nrw.commons.achievements.FeaturedImages;
|
||||||
import fr.free.nrw.commons.achievements.FeedbackResponse;
|
import fr.free.nrw.commons.achievements.FeedbackResponse;
|
||||||
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
|
||||||
|
import fr.free.nrw.commons.depictions.subClass.models.SparqlResponse;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.nearby.model.NearbyResponse;
|
import fr.free.nrw.commons.nearby.model.NearbyResponse;
|
||||||
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
|
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
|
||||||
import fr.free.nrw.commons.upload.FileUtils;
|
import fr.free.nrw.commons.upload.FileUtils;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||||
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse;
|
import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,208 +36,227 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class OkHttpJsonApiClient {
|
public class OkHttpJsonApiClient {
|
||||||
private static final String THUMB_SIZE = "640";
|
|
||||||
|
|
||||||
private final OkHttpClient okHttpClient;
|
private final OkHttpClient okHttpClient;
|
||||||
private final HttpUrl wikiMediaToolforgeUrl;
|
private final HttpUrl wikiMediaToolforgeUrl;
|
||||||
private final String sparqlQueryUrl;
|
private final String sparqlQueryUrl;
|
||||||
private final String campaignsUrl;
|
private final String campaignsUrl;
|
||||||
private final String commonsBaseUrl;
|
private final Gson gson;
|
||||||
private Gson gson;
|
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||||
HttpUrl wikiMediaToolforgeUrl,
|
HttpUrl wikiMediaToolforgeUrl,
|
||||||
String sparqlQueryUrl,
|
String sparqlQueryUrl,
|
||||||
String campaignsUrl,
|
String campaignsUrl,
|
||||||
String commonsBaseUrl,
|
Gson gson) {
|
||||||
Gson gson) {
|
this.okHttpClient = okHttpClient;
|
||||||
this.okHttpClient = okHttpClient;
|
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
this.campaignsUrl = campaignsUrl;
|
||||||
this.campaignsUrl = campaignsUrl;
|
this.gson = gson;
|
||||||
this.commonsBaseUrl = commonsBaseUrl;
|
}
|
||||||
this.gson = gson;
|
|
||||||
|
@NonNull
|
||||||
|
public Single<Integer> getUploadCount(String userName) {
|
||||||
|
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
||||||
|
urlBuilder
|
||||||
|
.addPathSegments("uploadsbyuser.py")
|
||||||
|
.addQueryParameter("user", userName);
|
||||||
|
|
||||||
|
if (ConfigUtils.isBetaFlavour()) {
|
||||||
|
urlBuilder.addQueryParameter("labs", "commonswiki");
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
Request request = new Request.Builder()
|
||||||
public Single<Integer> getUploadCount(String userName) {
|
.url(urlBuilder.build())
|
||||||
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
.build();
|
||||||
urlBuilder
|
|
||||||
.addPathSegments("uploadsbyuser.py")
|
|
||||||
.addQueryParameter("user", userName);
|
|
||||||
|
|
||||||
if (ConfigUtils.isBetaFlavour()) {
|
return Single.fromCallable(() -> {
|
||||||
urlBuilder.addQueryParameter("labs", "commonswiki");
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.isSuccessful()) {
|
||||||
|
ResponseBody responseBody = response.body();
|
||||||
|
if (null != responseBody) {
|
||||||
|
String responseBodyString = responseBody.string().trim();
|
||||||
|
if (!TextUtils.isEmpty(responseBodyString)) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(responseBodyString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Single<Integer> getWikidataEdits(String userName) {
|
||||||
|
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
||||||
|
urlBuilder
|
||||||
|
.addPathSegments("wikidataedits.py")
|
||||||
|
.addQueryParameter("user", userName);
|
||||||
|
|
||||||
|
if (ConfigUtils.isBetaFlavour()) {
|
||||||
|
urlBuilder.addQueryParameter("labs", "commonswiki");
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null &&
|
||||||
|
response.isSuccessful() && response.body() != null) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
GetWikidataEditCountResponse countResponse = gson
|
||||||
|
.fromJson(json, GetWikidataEditCountResponse.class);
|
||||||
|
if (null != countResponse) {
|
||||||
|
return countResponse.getWikidataEditCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This takes userName as input, which is then used to fetch the feedback/achievements statistics
|
||||||
|
* using OkHttp and JavaRx. This function return JSONObject
|
||||||
|
*
|
||||||
|
* @param userName MediaWiki user name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Single<FeedbackResponse> getAchievements(String userName) {
|
||||||
|
final String fetchAchievementUrlTemplate =
|
||||||
|
wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki"
|
||||||
|
: "/feedback.py");
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
String url = String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
fetchAchievementUrlTemplate,
|
||||||
|
userName);
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||||
|
urlBuilder.addQueryParameter("user", userName);
|
||||||
|
Timber.i("Url %s", urlBuilder.toString());
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.toString())
|
||||||
|
.build();
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Timber.d("Response for achievements is %s", json);
|
||||||
|
try {
|
||||||
|
return gson.fromJson(json, FeedbackResponse.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(urlBuilder.build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return Single.fromCallable(() -> {
|
}
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
return null;
|
||||||
if (response != null && response.isSuccessful()) {
|
});
|
||||||
ResponseBody responseBody = response.body();
|
}
|
||||||
if (null != responseBody) {
|
|
||||||
String responseBodyString = responseBody.string().trim();
|
|
||||||
if (!TextUtils.isEmpty(responseBodyString)) {
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(responseBodyString);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Timber.e(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
public Observable<List<Place>> getNearbyPlaces(LatLng cur, String language, double radius) throws IOException {
|
||||||
public Single<Integer> getWikidataEdits(String userName) {
|
|
||||||
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
|
||||||
urlBuilder
|
|
||||||
.addPathSegments("wikidataedits.py")
|
|
||||||
.addQueryParameter("user", userName);
|
|
||||||
|
|
||||||
if (ConfigUtils.isBetaFlavour()) {
|
|
||||||
urlBuilder.addQueryParameter("labs", "commonswiki");
|
|
||||||
}
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(urlBuilder.build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return Single.fromCallable(() -> {
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (response != null &&
|
|
||||||
response.isSuccessful() && response.body() != null) {
|
|
||||||
String json = response.body().string();
|
|
||||||
if (json == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
GetWikidataEditCountResponse countResponse = gson.fromJson(json, GetWikidataEditCountResponse.class);
|
|
||||||
if (null != countResponse) {
|
|
||||||
return countResponse.getWikidataEditCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This takes userName as input, which is then used to fetch the feedback/achievements
|
|
||||||
* statistics using OkHttp and JavaRx. This function return JSONObject
|
|
||||||
*
|
|
||||||
* @param userName MediaWiki user name
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Single<FeedbackResponse> getAchievements(String userName) {
|
|
||||||
final String fetchAchievementUrlTemplate =
|
|
||||||
wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki" : "/feedback.py");
|
|
||||||
return Single.fromCallable(() -> {
|
|
||||||
String url = String.format(
|
|
||||||
Locale.ENGLISH,
|
|
||||||
fetchAchievementUrlTemplate,
|
|
||||||
userName);
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
|
||||||
urlBuilder.addQueryParameter("user", userName);
|
|
||||||
Timber.i("Url %s", urlBuilder.toString());
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(urlBuilder.toString())
|
|
||||||
.build();
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
|
||||||
String json = response.body().string();
|
|
||||||
if (json == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Timber.d("Response for achievements is %s", json);
|
|
||||||
try {
|
|
||||||
return gson.fromJson(json, FeedbackResponse.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<List<Place>> getNearbyPlaces(LatLng cur, String lang, double radius) throws IOException {
|
|
||||||
String wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
|
String wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
|
||||||
String query = wikidataQuery
|
String query = wikidataQuery
|
||||||
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
||||||
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
|
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
|
||||||
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
|
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
|
||||||
.replace("${LANG}", lang);
|
.replace("${LANG}", language);
|
||||||
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl
|
HttpUrl.Builder urlBuilder = HttpUrl
|
||||||
.parse(sparqlQueryUrl)
|
.parse(sparqlQueryUrl)
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("query", query)
|
.addQueryParameter("query", query)
|
||||||
.addQueryParameter("format", "json");
|
.addQueryParameter("format", "json");
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(urlBuilder.build())
|
.url(urlBuilder.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Observable.fromCallable(() -> {
|
return Observable.fromCallable(() -> {
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
|
||||||
NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
|
|
||||||
List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
|
|
||||||
List<Place> places = new ArrayList<>();
|
|
||||||
for (NearbyResultItem item : bindings) {
|
|
||||||
places.add(Place.from(item));
|
|
||||||
}
|
|
||||||
return places;
|
|
||||||
}
|
|
||||||
return new ArrayList<>();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Single<CampaignResponseDTO> getCampaigns() {
|
|
||||||
return Single.fromCallable(() -> {
|
|
||||||
Request request = new Request.Builder().url(campaignsUrl)
|
|
||||||
.build();
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
|
||||||
String json = response.body().string();
|
|
||||||
if (json == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return gson.fromJson(json, CampaignResponseDTO.class);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whenever imageInfo is fetched, these common properties can be specified for the API call
|
|
||||||
* https://www.mediawiki.org/wiki/API:Imageinfo
|
|
||||||
*
|
|
||||||
* @param builder
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private HttpUrl.Builder appendMediaProperties(HttpUrl.Builder builder) {
|
|
||||||
builder.addQueryParameter("prop", "imageinfo")
|
|
||||||
.addQueryParameter("iiprop", "url|extmetadata")
|
|
||||||
.addQueryParameter("iiurlwidth", THUMB_SIZE)
|
|
||||||
.addQueryParameter("iiextmetadatafilter", "DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl");
|
|
||||||
|
|
||||||
String language = Locale.getDefault().getLanguage();
|
|
||||||
if (!StringUtils.isBlank(language)) {
|
|
||||||
builder.addQueryParameter("iiextmetadatalanguage", language);
|
|
||||||
}
|
}
|
||||||
|
NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
|
||||||
|
List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
|
||||||
|
List<Place> places = new ArrayList<>();
|
||||||
|
for (NearbyResultItem item : bindings) {
|
||||||
|
places.add(Place.from(item));
|
||||||
|
}
|
||||||
|
return places;
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return builder;
|
/**
|
||||||
}
|
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||||
|
* bridge -> suspended bridge, aqueduct, etc
|
||||||
|
*/
|
||||||
|
public Observable<List<DepictedItem>> getChildQIDs(String qid) throws IOException {
|
||||||
|
return depictedItemsFrom(sparqlQuery(qid, "/queries/subclasses_query.rq"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||||
|
* bridge -> suspended bridge, aqueduct, etc
|
||||||
|
*/
|
||||||
|
public Observable<List<DepictedItem>> getParentQIDs(String qid) throws IOException {
|
||||||
|
return depictedItemsFrom(sparqlQuery(qid, "/queries/parentclasses_query.rq"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Observable<List<DepictedItem>> depictedItemsFrom(Request request) {
|
||||||
|
return Observable.fromCallable(() -> {
|
||||||
|
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
||||||
|
return gson.fromJson(body.string(), SparqlResponse.class).toDepictedItems();
|
||||||
|
}catch (Exception e) {
|
||||||
|
Timber.e(e);
|
||||||
|
return new ArrayList<DepictedItem>();
|
||||||
|
}
|
||||||
|
}).doOnError(Timber::e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private Request sparqlQuery(String qid, String fileName) throws IOException {
|
||||||
|
String query = FileUtils.readFromResource(fileName).
|
||||||
|
replace("${QID}", qid)
|
||||||
|
.replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"");
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl
|
||||||
|
.parse(sparqlQueryUrl)
|
||||||
|
.newBuilder()
|
||||||
|
.addQueryParameter("query", query)
|
||||||
|
.addQueryParameter("format", "json");
|
||||||
|
return new Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<CampaignResponseDTO> getCampaigns() {
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
Request request = new Request.Builder().url(campaignsUrl)
|
||||||
|
.build();
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return gson.fromJson(json, CampaignResponseDTO.class);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
import fr.free.nrw.commons.upload.UploadController;
|
import fr.free.nrw.commons.upload.UploadController;
|
||||||
import fr.free.nrw.commons.upload.UploadModel;
|
import fr.free.nrw.commons.upload.UploadModel;
|
||||||
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
|
@ -34,16 +36,17 @@ public class UploadRemoteDataSource {
|
||||||
private UploadModel uploadModel;
|
private UploadModel uploadModel;
|
||||||
private UploadController uploadController;
|
private UploadController uploadController;
|
||||||
private CategoriesModel categoriesModel;
|
private CategoriesModel categoriesModel;
|
||||||
|
private DepictModel depictModel;
|
||||||
private NearbyPlaces nearbyPlaces;
|
private NearbyPlaces nearbyPlaces;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UploadRemoteDataSource(UploadModel uploadModel, UploadController uploadController,
|
public UploadRemoteDataSource(UploadModel uploadModel, UploadController uploadController,
|
||||||
CategoriesModel categoriesModel,
|
CategoriesModel categoriesModel, NearbyPlaces nearbyPlaces, DepictModel depictModel) {
|
||||||
NearbyPlaces nearbyPlaces) {
|
|
||||||
this.uploadModel = uploadModel;
|
this.uploadModel = uploadModel;
|
||||||
this.uploadController = uploadController;
|
this.uploadController = uploadController;
|
||||||
this.categoriesModel = categoriesModel;
|
this.categoriesModel = categoriesModel;
|
||||||
this.nearbyPlaces = nearbyPlaces;
|
this.nearbyPlaces = nearbyPlaces;
|
||||||
|
this.depictModel = depictModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -80,19 +83,13 @@ public class UploadRemoteDataSource {
|
||||||
uploadController.prepareService();
|
uploadController.prepareService();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean up the UploadController
|
|
||||||
*/
|
|
||||||
public void cleanup() {
|
|
||||||
uploadController.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clean up the selected categories
|
* Clean up the selected categories
|
||||||
*/
|
*/
|
||||||
public void clearSelectedCategories(){
|
public void cleanUp(){
|
||||||
//This needs further refactoring, this should not be here, right now the structure wont suppoort rhis
|
//This needs further refactoring, this should not be here, right now the structure wont suppoort rhis
|
||||||
categoriesModel.cleanUp();
|
categoriesModel.cleanUp();
|
||||||
|
depictModel.cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -167,13 +164,12 @@ public class UploadRemoteDataSource {
|
||||||
*
|
*
|
||||||
* @param uploadableFile
|
* @param uploadableFile
|
||||||
* @param place
|
* @param place
|
||||||
* @param source
|
|
||||||
* @param similarImageInterface
|
* @param similarImageInterface
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
||||||
String source, SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface) {
|
||||||
return uploadModel.preProcessImage(uploadableFile, place, source, similarImageInterface);
|
return uploadModel.preProcessImage(uploadableFile, place, similarImageInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -204,7 +200,33 @@ public class UploadRemoteDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
/**
|
||||||
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
* handles category selection/unselection
|
||||||
}
|
* @param depictedItem
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void onDepictedItemClicked(DepictedItem depictedItem) {
|
||||||
|
uploadModel.onDepictItemClicked(depictedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the list of selected depictions
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
public List<DepictedItem> getSelectedDepictions() {
|
||||||
|
return uploadModel.getSelectedDepictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get all depictions
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Observable<DepictedItem> searchAllEntities(String query) {
|
||||||
|
return depictModel.searchAllEntities(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
||||||
|
uploadModel.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
|
@ -71,7 +73,7 @@ public class UploadRepository {
|
||||||
*/
|
*/
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
localDataSource.cleanUp();
|
localDataSource.cleanUp();
|
||||||
remoteDataSource.clearSelectedCategories();
|
remoteDataSource.cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -174,14 +176,12 @@ public class UploadRepository {
|
||||||
*
|
*
|
||||||
* @param uploadableFile
|
* @param uploadableFile
|
||||||
* @param place
|
* @param place
|
||||||
* @param source
|
|
||||||
* @param similarImageInterface
|
* @param similarImageInterface
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
||||||
String source, SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface) {
|
||||||
return remoteDataSource
|
return remoteDataSource.preProcessImage(uploadableFile, place, similarImageInterface);
|
||||||
.preProcessImage(uploadableFile, place, source, similarImageInterface);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -263,6 +263,31 @@ public class UploadRepository {
|
||||||
localDataSource.setSelectedLicense(licenseName);
|
localDataSource.setSelectedLicense(licenseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onDepictItemClicked(DepictedItem depictedItem) {
|
||||||
|
remoteDataSource.onDepictedItemClicked(depictedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns the selected depictions for the current upload
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
public List<DepictedItem> getSelectedDepictions() {
|
||||||
|
return remoteDataSource.getSelectedDepictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search all depictions from
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Observable<DepictedItem> searchAllEntities(String query) {
|
||||||
|
return remoteDataSource.searchAllEntities(query);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns nearest place matching the passed latitude and longitude
|
* Returns nearest place matching the passed latitude and longitude
|
||||||
* @param decLatitude
|
* @param decLatitude
|
||||||
|
|
@ -273,7 +298,7 @@ public class UploadRepository {
|
||||||
return remoteDataSource.getNearbyPlaces(decLatitude, decLongitude);
|
return remoteDataSource.getNearbyPlaces(decLatitude, decLongitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
||||||
remoteDataSource.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
remoteDataSource.useSimilarPictureCoordinates(imageCoordinates, uploadItemIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,39 @@ import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.caching.CacheController
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||||
import fr.free.nrw.commons.mwapi.CategoryApi
|
import fr.free.nrw.commons.mwapi.CategoryApi
|
||||||
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||||
import fr.free.nrw.commons.settings.Prefs
|
import fr.free.nrw.commons.settings.Prefs
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictModel
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
private const val DEFAULT_SUGGESTION_RADIUS_IN_METRES = 100
|
||||||
|
private const val MAX_SUGGESTION_RADIUS_IN_METRES = 1000
|
||||||
|
private const val RADIUS_STEP_SIZE_IN_METRES = 100
|
||||||
|
private const val MIN_NEARBY_RESULTS = 5
|
||||||
|
|
||||||
class FileProcessor @Inject constructor(
|
class FileProcessor @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val contentResolver: ContentResolver,
|
private val contentResolver: ContentResolver,
|
||||||
private val cacheController: CacheController,
|
|
||||||
private val gpsCategoryModel: GpsCategoryModel,
|
private val gpsCategoryModel: GpsCategoryModel,
|
||||||
|
private val depictsModel: DepictModel,
|
||||||
@param:Named("default_preferences") private val defaultKvStore: JsonKvStore,
|
@param:Named("default_preferences") private val defaultKvStore: JsonKvStore,
|
||||||
private val apiCall: CategoryApi
|
private val apiCall: CategoryApi,
|
||||||
|
private val okHttpJsonApiClient: OkHttpJsonApiClient
|
||||||
) {
|
) {
|
||||||
private val compositeDisposable = CompositeDisposable()
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
|
|
@ -57,7 +67,7 @@ class FileProcessor @Inject constructor(
|
||||||
similarImageInterface
|
similarImageInterface
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
useImageCoords(originalImageCoordinates)
|
prePopulateCategoriesAndDepictionsBy(originalImageCoordinates)
|
||||||
}
|
}
|
||||||
return originalImageCoordinates
|
return originalImageCoordinates
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +156,7 @@ class FileProcessor @Inject constructor(
|
||||||
|
|
||||||
private fun readImageCoordinates(file: File) =
|
private fun readImageCoordinates(file: File) =
|
||||||
try {
|
try {
|
||||||
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file)))
|
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
try {
|
try {
|
||||||
|
|
@ -163,29 +173,44 @@ class FileProcessor @Inject constructor(
|
||||||
*
|
*
|
||||||
* @param imageCoordinates
|
* @param imageCoordinates
|
||||||
*/
|
*/
|
||||||
fun useImageCoords(imageCoordinates: ImageCoordinates) {
|
fun prePopulateCategoriesAndDepictionsBy(imageCoordinates: ImageCoordinates) {
|
||||||
requireNotNull(imageCoordinates.decimalCoords)
|
requireNotNull(imageCoordinates.decimalCoords)
|
||||||
cacheController.setQtPoint(imageCoordinates.decLongitude, imageCoordinates.decLatitude)
|
compositeDisposable.add(
|
||||||
val displayCatList = cacheController.findCategory()
|
apiCall.request(imageCoordinates.decimalCoords)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.subscribe(
|
||||||
|
{ gpsCategoryModel.categoryList = it },
|
||||||
|
{
|
||||||
|
Timber.e(it)
|
||||||
|
gpsCategoryModel.clear()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
compositeDisposable.add(
|
||||||
if (displayCatList.isEmpty()) {
|
suggestNearbyDepictions(imageCoordinates)
|
||||||
compositeDisposable.add(
|
)
|
||||||
apiCall.request(imageCoordinates.decimalCoords)
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(Schedulers.io())
|
private val radiiProgressionInMetres =
|
||||||
.subscribe(
|
(DEFAULT_SUGGESTION_RADIUS_IN_METRES..MAX_SUGGESTION_RADIUS_IN_METRES step RADIUS_STEP_SIZE_IN_METRES)
|
||||||
{ gpsCategoryModel.categoryList = it },
|
|
||||||
{
|
private fun suggestNearbyDepictions(imageCoordinates: ImageCoordinates): Disposable {
|
||||||
Timber.e(it)
|
return Observable.fromIterable(radiiProgressionInMetres.map { it / 1000.0 })
|
||||||
gpsCategoryModel.clear()
|
.concatMap {
|
||||||
}
|
okHttpJsonApiClient.getNearbyPlaces(
|
||||||
)
|
imageCoordinates.latLng,
|
||||||
|
Locale.getDefault().language,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.filter { it.size >= MIN_NEARBY_RESULTS }
|
||||||
|
.take(1)
|
||||||
|
.subscribe(
|
||||||
|
{ depictsModel.nearbyPlaces = it },
|
||||||
|
{ Timber.e(it) }
|
||||||
)
|
)
|
||||||
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList)
|
|
||||||
} else {
|
|
||||||
Timber.d("Cache found, setting categoryList in model to %s", displayCatList)
|
|
||||||
gpsCategoryModel.categoryList = displayCatList
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,4 @@ public class GpsCategoryModel {
|
||||||
clear();
|
clear();
|
||||||
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
|
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(String categoryString) {
|
|
||||||
categorySet.add(categoryString);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import fr.free.nrw.commons.location.LatLng
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
@ -22,7 +23,6 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
||||||
* Construct from a stream.
|
* Construct from a stream.
|
||||||
*/
|
*/
|
||||||
internal constructor(stream: InputStream) : this(ExifInterface(stream))
|
internal constructor(stream: InputStream) : this(ExifInterface(stream))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct from the file path of the image.
|
* Construct from the file path of the image.
|
||||||
* @param path file path of the image
|
* @param path file path of the image
|
||||||
|
|
@ -30,8 +30,6 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
internal constructor(path: String) : this(ExifInterface(path))
|
internal constructor(path: String) : this(ExifInterface(path))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||||
//Always return null as a temporary fix for #1599
|
//Always return null as a temporary fix for #1599
|
||||||
|
|
@ -55,6 +53,8 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val latLng: LatLng? get() = LatLng(decLatitude, decLongitude, -1.0f)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a string to an accurate Degree
|
* Convert a string to an accurate Degree
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.List;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
@ -39,6 +40,7 @@ public class ImageProcessingService {
|
||||||
this.mediaClient = mediaClient;
|
this.mediaClient = mediaClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||||
* geolocation for image - check for valid title
|
* geolocation for image - check for valid title
|
||||||
|
|
@ -88,18 +90,18 @@ public class ImageProcessingService {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks item title
|
* Checks item caption
|
||||||
* - empty title
|
* - empty caption
|
||||||
* - existing title
|
* - existing caption
|
||||||
*
|
*
|
||||||
* @param uploadItem
|
* @param uploadItem
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Single<Integer> validateItemTitle(UploadModel.UploadItem uploadItem) {
|
private Single<Integer> validateItemTitle(UploadModel.UploadItem uploadItem) {
|
||||||
Timber.d("Checking for image title %s", uploadItem.getTitle());
|
Timber.d("Checking for image title %s", uploadItem.getUploadMediaDetails());
|
||||||
Title title = uploadItem.getTitle();
|
List<UploadMediaDetail> captions = uploadItem.getUploadMediaDetails();
|
||||||
if (title.isEmpty()) {
|
if (captions.isEmpty()) {
|
||||||
return Single.just(EMPTY_TITLE);
|
return Single.just(EMPTY_CAPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
|
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile.DateTimeWithSource;
|
||||||
|
import fr.free.nrw.commons.settings.Prefs.Licenses;
|
||||||
|
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
class PageContentsCreator {
|
||||||
|
|
||||||
|
//{{According to Exif data|2009-01-09}}
|
||||||
|
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
|
||||||
|
|
||||||
|
//2009-01-09 → 9 January 2009
|
||||||
|
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PageContentsCreator(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createFrom(Contribution contribution) {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
buffer
|
||||||
|
.append("== {{int:filedesc}} ==\n")
|
||||||
|
.append("{{Information\n")
|
||||||
|
.append("|description=").append(contribution.getDescription()).append("\n")
|
||||||
|
.append("|source=").append("{{own}}\n")
|
||||||
|
.append("|author=[[User:").append(contribution.getCreator()).append("|")
|
||||||
|
.append(contribution.getCreator()).append("]]\n");
|
||||||
|
|
||||||
|
String templatizedCreatedDate = getTemplatizedCreatedDate(
|
||||||
|
contribution.getDateCreated(), contribution.getDateCreatedSource());
|
||||||
|
if (!StringUtils.isBlank(templatizedCreatedDate)) {
|
||||||
|
buffer.append("|date=").append(templatizedCreatedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append("}}").append("\n");
|
||||||
|
|
||||||
|
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
|
||||||
|
final String decimalCoords = contribution.getDecimalCoords();
|
||||||
|
if (decimalCoords != null) {
|
||||||
|
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append("== {{int:license-header}} ==\n")
|
||||||
|
.append(licenseTemplateFor(contribution.getLicense())).append("\n\n")
|
||||||
|
.append("{{Uploaded from Mobile|platform=Android|version=")
|
||||||
|
.append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
|
||||||
|
final List<String> categories = contribution.getCategories();
|
||||||
|
if (categories != null && categories.size() != 0) {
|
||||||
|
for (int i = 0; i < categories.size(); i++) {
|
||||||
|
buffer.append("\n[[Category:").append(categories.get(i)).append("]]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.append("{{subst:unc}}");
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
|
||||||
|
*
|
||||||
|
* @param dateCreated
|
||||||
|
* @param dateCreatedSource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String getTemplatizedCreatedDate(Date dateCreated, String dateCreatedSource) {
|
||||||
|
if (dateCreated != null) {
|
||||||
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
return String.format(Locale.ENGLISH,
|
||||||
|
isExif(dateCreatedSource) ? TEMPLATE_DATE_ACC_TO_EXIF : TEMPLATE_DATA_OTHER_SOURCE,
|
||||||
|
dateFormat.format(dateCreated)
|
||||||
|
) + "\n";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExif(String dateCreatedSource) {
|
||||||
|
return DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String licenseTemplateFor(String license) {
|
||||||
|
switch (license) {
|
||||||
|
case Licenses.CC_BY_3:
|
||||||
|
return "{{self|cc-by-3.0}}";
|
||||||
|
case Licenses.CC_BY_4:
|
||||||
|
return "{{self|cc-by-4.0}}";
|
||||||
|
case Licenses.CC_BY_SA_3:
|
||||||
|
return "{{self|cc-by-sa-3.0}}";
|
||||||
|
case Licenses.CC_BY_SA_4:
|
||||||
|
return "{{self|cc-by-sa-4.0}}";
|
||||||
|
case Licenses.CC0:
|
||||||
|
return "{{self|cc-zero}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Unrecognized license value: " + license);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload
|
|
||||||
|
|
||||||
import android.text.TextUtils
|
|
||||||
|
|
||||||
class Title {
|
|
||||||
private var titleText: String? = null
|
|
||||||
var isSet = false
|
|
||||||
override fun toString(): String {
|
|
||||||
if (titleText == null) {
|
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return titleText!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTitleText(titleText: String?) {
|
|
||||||
this.titleText=titleText?.trim()
|
|
||||||
if (!TextUtils.isEmpty(titleText)) {
|
|
||||||
isSet = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val isEmpty: Boolean
|
|
||||||
get() = titleText == null || titleText!!.isEmpty()
|
|
||||||
|
|
||||||
fun getTitleText(): String? {
|
|
||||||
return titleText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
|
||||||
|
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||||
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
|
@ -10,7 +14,6 @@ import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.cardview.widget.CardView;
|
import androidx.cardview.widget.CardView;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
@ -20,14 +23,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
|
@ -36,7 +31,6 @@ import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.category.CategoriesModel;
|
import fr.free.nrw.commons.category.CategoriesModel;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionController;
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
|
@ -44,6 +38,7 @@ import fr.free.nrw.commons.mwapi.UserClient;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
||||||
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
|
||||||
|
|
@ -52,12 +47,13 @@ import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
|
|
||||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
|
||||||
|
|
||||||
public class UploadActivity extends BaseActivity implements UploadContract.View, UploadBaseFragment.Callback {
|
public class UploadActivity extends BaseActivity implements UploadContract.View, UploadBaseFragment.Callback {
|
||||||
@Inject
|
@Inject
|
||||||
ContributionController contributionController;
|
ContributionController contributionController;
|
||||||
|
|
@ -102,11 +98,10 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
private UploadImageAdapter uploadImagesAdapter;
|
private UploadImageAdapter uploadImagesAdapter;
|
||||||
private List<Fragment> fragments;
|
private List<Fragment> fragments;
|
||||||
private UploadCategoriesFragment uploadCategoriesFragment;
|
private UploadCategoriesFragment uploadCategoriesFragment;
|
||||||
|
private DepictsFragment depictsFragment;
|
||||||
private MediaLicenseFragment mediaLicenseFragment;
|
private MediaLicenseFragment mediaLicenseFragment;
|
||||||
private ThumbnailsAdapter thumbnailsAdapter;
|
private ThumbnailsAdapter thumbnailsAdapter;
|
||||||
|
|
||||||
|
|
||||||
private String source;
|
|
||||||
private Place place;
|
private Place place;
|
||||||
private List<UploadableFile> uploadableFiles = Collections.emptyList();
|
private List<UploadableFile> uploadableFiles = Collections.emptyList();
|
||||||
private int currentSelectedPosition = 0;
|
private int currentSelectedPosition = 0;
|
||||||
|
|
@ -288,6 +283,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
@ -321,7 +317,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
fragments = new ArrayList<>();
|
fragments = new ArrayList<>();
|
||||||
for (UploadableFile uploadableFile : uploadableFiles) {
|
for (UploadableFile uploadableFile : uploadableFiles) {
|
||||||
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
|
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
|
||||||
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, source, place);
|
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place);
|
||||||
uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() {
|
uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void deletePictureAtIndex(int index) {
|
public void deletePictureAtIndex(int index) {
|
||||||
|
|
@ -359,10 +355,13 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
uploadCategoriesFragment = new UploadCategoriesFragment();
|
uploadCategoriesFragment = new UploadCategoriesFragment();
|
||||||
uploadCategoriesFragment.setCallback(this);
|
uploadCategoriesFragment.setCallback(this);
|
||||||
|
|
||||||
|
depictsFragment = new DepictsFragment();
|
||||||
|
depictsFragment.setCallback(this);
|
||||||
|
|
||||||
mediaLicenseFragment = new MediaLicenseFragment();
|
mediaLicenseFragment = new MediaLicenseFragment();
|
||||||
mediaLicenseFragment.setCallback(this);
|
mediaLicenseFragment.setCallback(this);
|
||||||
|
|
||||||
|
fragments.add(depictsFragment);
|
||||||
fragments.add(uploadCategoriesFragment);
|
fragments.add(uploadCategoriesFragment);
|
||||||
fragments.add(mediaLicenseFragment);
|
fragments.add(mediaLicenseFragment);
|
||||||
|
|
||||||
|
|
@ -378,16 +377,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
private void receiveInternalSharedItems() {
|
private void receiveInternalSharedItems() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|
||||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
Timber.d("Received intent %s with action %s", intent.toString(), intent.getAction());
|
||||||
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
|
||||||
} else {
|
|
||||||
source = Contribution.SOURCE_EXTERNAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timber.d("Received intent %s with action %s and from source %s",
|
|
||||||
intent.toString(),
|
|
||||||
intent.getAction(),
|
|
||||||
source);
|
|
||||||
|
|
||||||
uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES);
|
uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES);
|
||||||
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,36 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import org.wikipedia.csrf.CsrfTokenClient;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener;
|
import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
import java.io.File;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
import org.wikipedia.csrf.CsrfTokenClient;
|
||||||
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF;
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UploadClient {
|
public class UploadClient {
|
||||||
|
|
||||||
private final UploadInterface uploadInterface;
|
private final UploadInterface uploadInterface;
|
||||||
private final CsrfTokenClient csrfTokenClient;
|
private final CsrfTokenClient csrfTokenClient;
|
||||||
|
private final PageContentsCreator pageContentsCreator;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UploadClient(UploadInterface uploadInterface, @Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
|
public UploadClient(UploadInterface uploadInterface,
|
||||||
|
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient,
|
||||||
|
PageContentsCreator pageContentsCreator) {
|
||||||
this.uploadInterface = uploadInterface;
|
this.uploadInterface = uploadInterface;
|
||||||
this.csrfTokenClient = csrfTokenClient;
|
this.csrfTokenClient = csrfTokenClient;
|
||||||
|
this.pageContentsCreator = pageContentsCreator;
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable<UploadResult> uploadFileToStash(Context context, String filename, File file,
|
Observable<UploadResult> uploadFileToStash(Context context, String filename, File file,
|
||||||
|
|
@ -61,8 +62,8 @@ public class UploadClient {
|
||||||
try {
|
try {
|
||||||
return uploadInterface
|
return uploadInterface
|
||||||
.uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
|
.uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
|
||||||
contribution.getPageContents(context),
|
pageContentsCreator.createFrom(contribution),
|
||||||
contribution.getEditSummary(),
|
CommonsApplication.DEFAULT_EDIT_SUMMARY,
|
||||||
uniqueFileName,
|
uniqueFileName,
|
||||||
fileKey).map(uploadResponse -> uploadResponse.getUpload());
|
fileKey).map(uploadResponse -> uploadResponse.getUpload());
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,8 @@ import android.net.Uri;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
|
@ -34,23 +25,26 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Date;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UploadController {
|
public class UploadController {
|
||||||
private UploadService uploadService;
|
private UploadService uploadService;
|
||||||
private SessionManager sessionManager;
|
private final SessionManager sessionManager;
|
||||||
private Context context;
|
private final Context context;
|
||||||
private JsonKvStore store;
|
private final JsonKvStore store;
|
||||||
|
|
||||||
public interface ContributionUploadProgress {
|
|
||||||
void onUploadStarted(Contribution contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UploadController(SessionManager sessionManager,
|
public UploadController(final SessionManager sessionManager,
|
||||||
Context context,
|
final Context context,
|
||||||
JsonKvStore store) {
|
final JsonKvStore store) {
|
||||||
this.sessionManager = sessionManager;
|
this.sessionManager = sessionManager;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
|
|
@ -59,13 +53,13 @@ public class UploadController {
|
||||||
private boolean isUploadServiceConnected;
|
private boolean isUploadServiceConnected;
|
||||||
public ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
public ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
public void onServiceConnected(final ComponentName componentName, final IBinder binder) {
|
||||||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
|
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
|
||||||
isUploadServiceConnected = true;
|
isUploadServiceConnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onServiceDisconnected(ComponentName componentName) {
|
public void onServiceDisconnected(final ComponentName componentName) {
|
||||||
// this should never happen
|
// this should never happen
|
||||||
isUploadServiceConnected = false;
|
isUploadServiceConnected = false;
|
||||||
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
||||||
|
|
@ -76,7 +70,7 @@ public class UploadController {
|
||||||
* Prepares the upload service.
|
* Prepares the upload service.
|
||||||
*/
|
*/
|
||||||
public void prepareService() {
|
public void prepareService() {
|
||||||
Intent uploadServiceIntent = new Intent(context, UploadService.class);
|
final Intent uploadServiceIntent = new Intent(context, UploadService.class);
|
||||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||||
context.startService(uploadServiceIntent);
|
context.startService(uploadServiceIntent);
|
||||||
context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
|
@ -96,28 +90,18 @@ public class UploadController {
|
||||||
*
|
*
|
||||||
* @param contribution the contribution object
|
* @param contribution the contribution object
|
||||||
*/
|
*/
|
||||||
public void startUpload(Contribution contribution) {
|
|
||||||
startUpload(contribution, c -> {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a new upload task.
|
|
||||||
*
|
|
||||||
* @param contribution the contribution object
|
|
||||||
* @param onComplete the progress tracker
|
|
||||||
*/
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
|
public void startUpload(final Contribution contribution) {
|
||||||
//Set creator, desc, and license
|
//Set creator, desc, and license
|
||||||
|
|
||||||
// If author name is enabled and set, use it
|
// If author name is enabled and set, use it
|
||||||
if (store.getBoolean("useAuthorName", false)) {
|
if (store.getBoolean("useAuthorName", false)) {
|
||||||
String authorName = store.getString("authorName", "");
|
final String authorName = store.getString("authorName", "");
|
||||||
contribution.setCreator(authorName);
|
contribution.setCreator(authorName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextUtils.isEmpty(contribution.getCreator())) {
|
if (TextUtils.isEmpty(contribution.getCreator())) {
|
||||||
Account currentAccount = sessionManager.getCurrentAccount();
|
final Account currentAccount = sessionManager.getCurrentAccount();
|
||||||
if (currentAccount == null) {
|
if (currentAccount == null) {
|
||||||
Timber.d("Current account is null");
|
Timber.d("Current account is null");
|
||||||
ViewUtil.showLongToast(context, context.getString(R.string.user_not_logged_in));
|
ViewUtil.showLongToast(context, context.getString(R.string.user_not_logged_in));
|
||||||
|
|
@ -131,23 +115,23 @@ public class UploadController {
|
||||||
contribution.setDescription("");
|
contribution.setDescription("");
|
||||||
}
|
}
|
||||||
|
|
||||||
String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||||
contribution.setLicense(license);
|
contribution.setLicense(license);
|
||||||
|
|
||||||
uploadTask(contribution, onComplete);
|
uploadTask(contribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates the upload task
|
* Initiates the upload task
|
||||||
* @param contribution
|
* @param contribution
|
||||||
* @param onComplete
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Disposable uploadTask(Contribution contribution, ContributionUploadProgress onComplete) {
|
private Disposable uploadTask(final Contribution contribution) {
|
||||||
return Single.fromCallable(() -> makeUpload(contribution))
|
return Single.just(contribution)
|
||||||
|
.map(this::buildUpload)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(finalContribution -> onUploadCompleted(finalContribution, onComplete));
|
.subscribe(this::upload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,71 +139,76 @@ public class UploadController {
|
||||||
* @param contribution
|
* @param contribution
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private Contribution makeUpload(Contribution contribution) {
|
private Contribution buildUpload(final Contribution contribution) {
|
||||||
long length;
|
final ContentResolver contentResolver = context.getContentResolver();
|
||||||
ContentResolver contentResolver = context.getContentResolver();
|
|
||||||
|
contribution.setDataLength(resolveDataLength(contentResolver, contribution));
|
||||||
|
|
||||||
|
final String mimeType = resolveMimeType(contentResolver, contribution);
|
||||||
|
|
||||||
|
if (mimeType != null) {
|
||||||
|
Timber.d("MimeType is: %s", mimeType);
|
||||||
|
contribution.setMimeType(mimeType);
|
||||||
|
if(mimeType.startsWith("image/") && contribution.getDateCreated() == null){
|
||||||
|
contribution.setDateCreated(resolveDateTakenOrNow(contentResolver, contribution));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contribution;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveMimeType(final ContentResolver contentResolver, final Contribution contribution) {
|
||||||
|
final String mimeType = contribution.getMimeType();
|
||||||
|
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
|
||||||
|
return contentResolver.getType(contribution.getLocalUri());
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long resolveDataLength(final ContentResolver contentResolver, final Media contribution) {
|
||||||
try {
|
try {
|
||||||
if (contribution.getDataLength() <= 0) {
|
if (contribution.getDataLength() <= 0) {
|
||||||
Timber.d("UploadController/doInBackground, contribution.getLocalUri():%s", contribution.getLocalUri());
|
Timber.d("UploadController/doInBackground, contribution.getLocalUri():%s", contribution.getLocalUri());
|
||||||
AssetFileDescriptor assetFileDescriptor = contentResolver
|
final AssetFileDescriptor assetFileDescriptor = contentResolver
|
||||||
.openAssetFileDescriptor(Uri.fromFile(new File(contribution.getLocalUri().getPath())), "r");
|
.openAssetFileDescriptor(Uri.fromFile(new File(contribution.getLocalUri().getPath())), "r");
|
||||||
if (assetFileDescriptor != null) {
|
if (assetFileDescriptor != null) {
|
||||||
length = assetFileDescriptor.getLength();
|
final long length = assetFileDescriptor.getLength();
|
||||||
if (length == -1) {
|
return length != -1 ? length
|
||||||
// Let us find out the long way!
|
: countBytes(contentResolver.openInputStream(contribution.getLocalUri()));
|
||||||
length = countBytes(contentResolver
|
|
||||||
.openInputStream(contribution.getLocalUri()));
|
|
||||||
}
|
|
||||||
contribution.setDataLength(length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException | NullPointerException | SecurityException e) {
|
} catch (final IOException | NullPointerException | SecurityException e) {
|
||||||
Timber.e(e, "Exception occurred while uploading image");
|
Timber.e(e, "Exception occurred while uploading image");
|
||||||
}
|
}
|
||||||
|
return contribution.getDataLength();
|
||||||
|
}
|
||||||
|
|
||||||
String mimeType = (String) contribution.getTag("mimeType");
|
private Date resolveDateTakenOrNow(final ContentResolver contentResolver, final Media contribution) {
|
||||||
boolean imagePrefix = false;
|
Timber.d("local uri %s", contribution.getLocalUri());
|
||||||
|
try(final Cursor cursor = dateTakenCursor(contentResolver, contribution)) {
|
||||||
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
|
|
||||||
mimeType = contentResolver.getType(contribution.getLocalUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mimeType != null) {
|
|
||||||
contribution.setTag("mimeType", mimeType);
|
|
||||||
imagePrefix = mimeType.startsWith("image/");
|
|
||||||
Timber.d("MimeType is: %s", mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imagePrefix && contribution.getDateCreated() == null) {
|
|
||||||
Timber.d("local uri %s", contribution.getLocalUri());
|
|
||||||
Cursor cursor = contentResolver.query(contribution.getLocalUri(),
|
|
||||||
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
|
|
||||||
if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
|
if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
Date dateCreated = new Date(cursor.getLong(0));
|
final Date dateCreated = new Date(cursor.getLong(0));
|
||||||
Date epochStart = new Date(0);
|
if (dateCreated.after(new Date(0))) {
|
||||||
if (dateCreated.equals(epochStart) || dateCreated.before(epochStart)) {
|
return dateCreated;
|
||||||
// If date is incorrect (1st second of unix time) then set it to the current date
|
|
||||||
dateCreated = new Date();
|
|
||||||
}
|
}
|
||||||
contribution.setDateCreated(dateCreated);
|
|
||||||
cursor.close();
|
|
||||||
} else {
|
|
||||||
contribution.setDateCreated(new Date());
|
|
||||||
}
|
}
|
||||||
|
return new Date();
|
||||||
}
|
}
|
||||||
return contribution;
|
}
|
||||||
|
|
||||||
|
private Cursor dateTakenCursor(final ContentResolver contentResolver, final Media contribution) {
|
||||||
|
return contentResolver.query(contribution.getLocalUri(),
|
||||||
|
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the contribution object is completely formed, the item is queued to the upload service
|
* When the contribution object is completely formed, the item is queued to the upload service
|
||||||
* @param contribution
|
* @param contribution
|
||||||
* @param onComplete
|
|
||||||
*/
|
*/
|
||||||
private void onUploadCompleted(Contribution contribution, ContributionUploadProgress onComplete) {
|
private void upload(final Contribution contribution) {
|
||||||
//Starts the upload. If commented out, user can proceed to next Fragment but upload doesn't happen
|
//Starts the upload. If commented out, user can proceed to next Fragment but upload doesn't happen
|
||||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
|
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
|
||||||
onComplete.onUploadStarted(contribution);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -230,9 +219,9 @@ public class UploadController {
|
||||||
* @return the number of bytes in {@code stream}
|
* @return the number of bytes in {@code stream}
|
||||||
* @throws IOException if an I/O error occurs
|
* @throws IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
private long countBytes(InputStream stream) throws IOException {
|
private long countBytes(final InputStream stream) throws IOException {
|
||||||
long count = 0;
|
long count = 0;
|
||||||
BufferedInputStream bis = new BufferedInputStream(stream);
|
final BufferedInputStream bis = new BufferedInputStream(stream);
|
||||||
while (bis.read() != -1) {
|
while (bis.read() != -1) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import com.pedrogomez.renderers.RendererBuilder;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.UploadDepictsCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter Factory for DepictsClicked Listener
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class UploadDepictsAdapterFactory {
|
||||||
|
private final UploadDepictsCallback listener;
|
||||||
|
|
||||||
|
public UploadDepictsAdapterFactory(UploadDepictsCallback listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RVRendererAdapter<DepictedItem> create(List<DepictedItem> itemList) {
|
||||||
|
RendererBuilder<DepictedItem> builder = new RendererBuilder<DepictedItem>()
|
||||||
|
.bind(DepictedItem.class, new UploadDepictsRenderer(listener));
|
||||||
|
ListAdapteeCollection<DepictedItem> collection = new ListAdapteeCollection<>(
|
||||||
|
itemList != null ? itemList : Collections.emptyList());
|
||||||
|
return new RVRendererAdapter<>(builder, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
|
import com.facebook.common.references.CloseableReference;
|
||||||
|
import com.facebook.datasource.DataSource;
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||||
|
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
|
||||||
|
import com.facebook.imagepipeline.image.CloseableImage;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.UploadDepictsCallback;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depicts Renderer for setting up inflating layout,
|
||||||
|
* and setting views for the layout of each depicted Item
|
||||||
|
*/
|
||||||
|
public class UploadDepictsRenderer extends Renderer<DepictedItem> {
|
||||||
|
private final UploadDepictsCallback listener;
|
||||||
|
@BindView(R.id.depict_checkbox)
|
||||||
|
CheckBox checkedView;
|
||||||
|
@BindView(R.id.depicts_label)
|
||||||
|
TextView depictsLabel;
|
||||||
|
@BindView(R.id.description) TextView description;
|
||||||
|
@BindView(R.id.depicted_image)
|
||||||
|
ImageView imageView;
|
||||||
|
private final static String NO_IMAGE_FOR_DEPICTION="No Image for Depiction";
|
||||||
|
|
||||||
|
public UploadDepictsRenderer(UploadDepictsCallback listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUpView(View rootView) {
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup OnClicklisteners on the views
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void hookListeners(View rootView) {
|
||||||
|
rootView.setOnClickListener(v -> {
|
||||||
|
DepictedItem item = getContent();
|
||||||
|
item.setSelected(!item.isSelected());
|
||||||
|
checkedView.setChecked(item.isSelected());
|
||||||
|
if (listener != null) {
|
||||||
|
listener.depictsClicked(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
checkedView.setOnClickListener(v -> {
|
||||||
|
DepictedItem item = getContent();
|
||||||
|
item.setSelected(!item.isSelected());
|
||||||
|
checkedView.setChecked(item.isSelected());
|
||||||
|
if (listener != null) {
|
||||||
|
listener.depictsClicked(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View inflate(LayoutInflater inflater, ViewGroup parent) {
|
||||||
|
return inflater.inflate(R.layout.layout_upload_depicts_item, parent, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialise views for every item in the adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
DepictedItem item = getContent();
|
||||||
|
checkedView.setChecked(item.isSelected());
|
||||||
|
depictsLabel.setText(item.getName());
|
||||||
|
description.setText(item.getDescription());
|
||||||
|
if (!TextUtils.isEmpty(item.getImageUrl())) {
|
||||||
|
if (!item.getImageUrl().equals(NO_IMAGE_FOR_DEPICTION))
|
||||||
|
setImageView(Uri.parse(item.getImageUrl()), imageView);
|
||||||
|
}else{
|
||||||
|
listener.fetchThumbnailUrlForEntity(item.getId(),item.getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set thumbnail for the depicted item
|
||||||
|
*/
|
||||||
|
private void setImageView(Uri imageUrl, ImageView imageView) {
|
||||||
|
ImageRequest imageRequest = ImageRequestBuilder
|
||||||
|
.newBuilderWithSource(imageUrl)
|
||||||
|
.setAutoRotateEnabled(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||||
|
final DataSource<CloseableReference<CloseableImage>>
|
||||||
|
dataSource = imagePipeline.fetchDecodedImage(imageRequest, getContext());
|
||||||
|
|
||||||
|
dataSource.subscribe(new BaseBitmapDataSubscriber() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||||
|
if (dataSource.isFinished() && bitmap != null) {
|
||||||
|
Timber.d("Bitmap loaded from url %s", imageUrl.toString());
|
||||||
|
imageView.post(() -> imageView.setImageBitmap(Bitmap.createBitmap(bitmap)));
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailureImpl(DataSource dataSource) {
|
||||||
|
Timber.d("Error getting bitmap from image url %s", imageUrl.toString());
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, CallerThreadExecutor.getInstance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.nearby.Place
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds a description of an item being uploaded by [UploadActivity]
|
||||||
|
*/
|
||||||
|
data class UploadMediaDetail constructor(
|
||||||
|
/**
|
||||||
|
* @return The language code ie. "en" or "fr"
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param languageCode The language code ie. "en" or "fr"
|
||||||
|
*/
|
||||||
|
var languageCode: String? = null,
|
||||||
|
var descriptionText: String = "",
|
||||||
|
var captionText: String = ""
|
||||||
|
) {
|
||||||
|
constructor(place: Place) : this(
|
||||||
|
Locale.getDefault().language,
|
||||||
|
place.longDescription,
|
||||||
|
place.name
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* @return the index of the language selected in a spinner with [SpinnerLanguagesAdapter]
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param selectedLanguageIndex the index of the language selected in a spinner with [SpinnerLanguagesAdapter]
|
||||||
|
*/
|
||||||
|
var selectedLanguageIndex: Int = -1
|
||||||
|
/**
|
||||||
|
* returns if the description was added manually (by the user, or we have added it programaticallly)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* sets to true if the description was manually added by the user
|
||||||
|
* @param manuallyAdded
|
||||||
|
*/
|
||||||
|
var isManuallyAdded: Boolean = false
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Formatting captions to the Wikibase format for sending labels
|
||||||
|
* @param uploadMediaDetails list of media Details
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
|
||||||
|
uploadMediaDetails.associate { it.languageCode to it.captionText }.filter { it.value.isNotBlank() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the list of descriptions into the format Commons requires for uploads.
|
||||||
|
*
|
||||||
|
* @param descriptions the list of descriptions, description is ignored if text is null.
|
||||||
|
* @return a string with the pattern of {{en|1=descriptionText}}
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun formatList(descriptions: List<UploadMediaDetail>) =
|
||||||
|
descriptions.filter { it.descriptionText.isNotEmpty() }
|
||||||
|
.joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,33 +10,31 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemSelectedListener;
|
import android.widget.AdapterView.OnItemSelectedListener;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatEditText;
|
import androidx.appcompat.widget.AppCompatEditText;
|
||||||
import androidx.appcompat.widget.AppCompatSpinner;
|
import androidx.appcompat.widget.AppCompatSpinner;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.utils.AbstractTextWatcher;
|
import fr.free.nrw.commons.utils.AbstractTextWatcher;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewHolder> {
|
public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDetailAdapter.ViewHolder> {
|
||||||
|
|
||||||
private List<Description> descriptions;
|
private List<UploadMediaDetail> uploadMediaDetails;
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
private EventListener eventListener;
|
||||||
|
|
||||||
private HashMap<AdapterView, String> selectedLanguages;
|
private HashMap<AdapterView, String> selectedLanguages;
|
||||||
private String savedLanguageValue;
|
private final String savedLanguageValue;
|
||||||
|
|
||||||
public DescriptionsAdapter(String savedLanguageValue) {
|
public UploadMediaDetailAdapter(String savedLanguageValue) {
|
||||||
descriptions = new ArrayList<>();
|
uploadMediaDetails = new ArrayList<>();
|
||||||
selectedLanguages = new HashMap<>();
|
selectedLanguages = new HashMap<>();
|
||||||
this.savedLanguageValue = savedLanguageValue;
|
this.savedLanguageValue = savedLanguageValue;
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +43,12 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Description> descriptions) {
|
public void setEventListener(EventListener eventListener) {
|
||||||
this.descriptions = descriptions;
|
this.eventListener = eventListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<UploadMediaDetail> uploadMediaDetails) {
|
||||||
|
this.uploadMediaDetails = uploadMediaDetails;
|
||||||
selectedLanguages = new HashMap<>();
|
selectedLanguages = new HashMap<>();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
@ -60,12 +62,12 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
holder.init(position);
|
holder.bind(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return descriptions.size();
|
return uploadMediaDetails.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -73,13 +75,13 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
*
|
*
|
||||||
* @return List of descriptions
|
* @return List of descriptions
|
||||||
*/
|
*/
|
||||||
public List<Description> getDescriptions() {
|
public List<UploadMediaDetail> getUploadMediaDetails() {
|
||||||
return descriptions;
|
return uploadMediaDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDescription(Description description) {
|
public void addDescription(UploadMediaDetail uploadMediaDetail) {
|
||||||
this.descriptions.add(description);
|
this.uploadMediaDetails.add(uploadMediaDetail);
|
||||||
notifyItemInserted(descriptions.size());
|
notifyItemInserted(uploadMediaDetails.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
@ -91,21 +93,43 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
@BindView(R.id.description_item_edit_text)
|
@BindView(R.id.description_item_edit_text)
|
||||||
AppCompatEditText descItemEditText;
|
AppCompatEditText descItemEditText;
|
||||||
|
|
||||||
|
@BindView(R.id.caption_item_edit_text)
|
||||||
|
AppCompatEditText captionItemEditText;
|
||||||
|
|
||||||
public ViewHolder(View itemView) {
|
public ViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
Timber.i("descItemEditText:" + descItemEditText);
|
Timber.i("descItemEditText:" + descItemEditText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(int position) {
|
public void bind(int position) {
|
||||||
Description description = descriptions.get(position);
|
UploadMediaDetail uploadMediaDetail = uploadMediaDetails.get(position);
|
||||||
Timber.d("Description is " + description);
|
Timber.d("UploadMediaDetail is " + uploadMediaDetail);
|
||||||
if (!TextUtils.isEmpty(description.getDescriptionText())) {
|
captionItemEditText.setText(uploadMediaDetail.getCaptionText());
|
||||||
descItemEditText.setText(description.getDescriptionText());
|
descItemEditText.setText(uploadMediaDetail.getDescriptionText());
|
||||||
} else {
|
|
||||||
descItemEditText.setText("");
|
captionItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
||||||
}
|
value -> {
|
||||||
|
if (position == 0) {
|
||||||
|
eventListener.onPrimaryCaptionTextChange(value.length() != 0);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
|
captionItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(),
|
||||||
|
null);
|
||||||
|
captionItemEditText.setOnTouchListener((v, event) -> {
|
||||||
|
//2 is for drawable right
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP && (event.getRawX() >= (captionItemEditText.getRight() - captionItemEditText.getCompoundDrawables()[2].getBounds().width()))) {
|
||||||
|
if (getAdapterPosition() == 0) {
|
||||||
|
callback.showAlert(R.string.media_detail_caption,
|
||||||
|
R.string.caption_info);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(),
|
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(),
|
||||||
null);
|
null);
|
||||||
descItemEditText.setOnTouchListener((v, event) -> {
|
descItemEditText.setOnTouchListener((v, event) -> {
|
||||||
|
|
@ -122,18 +146,23 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
captionItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||||
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captionItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
||||||
|
captionText -> uploadMediaDetails.get(position).setCaptionText(captionText)));
|
||||||
|
initLanguageSpinner(position, uploadMediaDetail);
|
||||||
|
|
||||||
descItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
descItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
||||||
descriptionText -> descriptions.get(position).setDescriptionText(descriptionText)));
|
descriptionText -> uploadMediaDetails.get(position).setDescriptionText(descriptionText)));
|
||||||
initLanguageSpinner(position, description);
|
initLanguageSpinner(position, uploadMediaDetail);
|
||||||
|
|
||||||
//If the description was manually added by the user, it deserves focus, if not, let the user decide
|
//If the description was manually added by the user, it deserves focus, if not, let the user decide
|
||||||
if (description.isManuallyAdded()) {
|
if (uploadMediaDetail.isManuallyAdded()) {
|
||||||
descItemEditText.requestFocus();
|
captionItemEditText.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
descItemEditText.clearFocus();
|
captionItemEditText.clearFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +171,7 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
* @param position
|
* @param position
|
||||||
* @param description
|
* @param description
|
||||||
*/
|
*/
|
||||||
private void initLanguageSpinner(int position, Description description) {
|
private void initLanguageSpinner(int position, UploadMediaDetail description) {
|
||||||
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(
|
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(
|
||||||
spinnerDescriptionLanguages.getContext(),
|
spinnerDescriptionLanguages.getContext(),
|
||||||
selectedLanguages
|
selectedLanguages
|
||||||
|
|
@ -205,6 +234,10 @@ public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapte
|
||||||
void showAlert(int mediaDetailDescription, int descriptionInfo);
|
void showAlert(int mediaDetailDescription, int descriptionInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface EventListener {
|
||||||
|
void onPrimaryCaptionTextChange(boolean isNotEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts dp to pixel
|
* converts dp to pixel
|
||||||
* @param dp
|
* @param dp
|
||||||
|
|
@ -4,21 +4,20 @@ import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper;
|
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.subjects.BehaviorSubject;
|
import io.reactivex.subjects.BehaviorSubject;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -26,6 +25,7 @@ import java.util.Map;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
@ -36,22 +36,23 @@ public class UploadModel {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private String license;
|
private String license;
|
||||||
private final Map<String, String> licensesByName;
|
private final Map<String, String> licensesByName;
|
||||||
private List<UploadItem> items = new ArrayList<>();
|
private final List<UploadItem> items = new ArrayList<>();
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
private SessionManager sessionManager;
|
private final SessionManager sessionManager;
|
||||||
private FileProcessor fileProcessor;
|
private final FileProcessor fileProcessor;
|
||||||
private final ImageProcessingService imageProcessingService;
|
private final ImageProcessingService imageProcessingService;
|
||||||
private List<String> selectedCategories;
|
private List<String> selectedCategories = new ArrayList<>();
|
||||||
|
private List<DepictedItem> selectedDepictions = new ArrayList<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UploadModel(@Named("licenses") List<String> licenses,
|
UploadModel(@Named("licenses") final List<String> licenses,
|
||||||
@Named("default_preferences") JsonKvStore store,
|
@Named("default_preferences") final JsonKvStore store,
|
||||||
@Named("licenses_by_name") Map<String, String> licensesByName,
|
@Named("licenses_by_name") final Map<String, String> licensesByName,
|
||||||
Context context,
|
final Context context,
|
||||||
SessionManager sessionManager,
|
final SessionManager sessionManager,
|
||||||
FileProcessor fileProcessor,
|
final FileProcessor fileProcessor,
|
||||||
ImageProcessingService imageProcessingService) {
|
final ImageProcessingService imageProcessingService) {
|
||||||
this.licenses = licenses;
|
this.licenses = licenses;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||||
|
|
@ -68,39 +69,33 @@ public class UploadModel {
|
||||||
public void cleanUp() {
|
public void cleanUp() {
|
||||||
compositeDisposable.clear();
|
compositeDisposable.clear();
|
||||||
fileProcessor.cleanup();
|
fileProcessor.cleanup();
|
||||||
this.items.clear();
|
items.clear();
|
||||||
if (this.selectedCategories != null) {
|
selectedCategories.clear();
|
||||||
this.selectedCategories.clear();
|
selectedDepictions.clear();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedCategories(List<String> selectedCategories) {
|
public void setSelectedCategories(List<String> selectedCategories) {
|
||||||
if (null == selectedCategories) {
|
|
||||||
selectedCategories = new ArrayList<>();
|
|
||||||
}
|
|
||||||
this.selectedCategories = selectedCategories;
|
this.selectedCategories = selectedCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pre process a one item at a time
|
* pre process a one item at a time
|
||||||
*/
|
*/
|
||||||
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile,
|
public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
|
||||||
Place place,
|
final Place place,
|
||||||
String source,
|
final SimilarImageInterface similarImageInterface) {
|
||||||
SimilarImageInterface similarImageInterface) {
|
return Observable.just(
|
||||||
return Observable.just(getUploadItem(uploadableFile, place, source, similarImageInterface));
|
createAndAddUploadItem(uploadableFile, place, similarImageInterface));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> getImageQuality(UploadItem uploadItem) {
|
public Single<Integer> getImageQuality(final UploadItem uploadItem) {
|
||||||
return imageProcessingService.validateImage(uploadItem);
|
return imageProcessingService.validateImage(uploadItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UploadItem getUploadItem(UploadableFile uploadableFile,
|
private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
|
||||||
Place place,
|
final Place place,
|
||||||
String source,
|
final SimilarImageInterface similarImageInterface) {
|
||||||
SimilarImageInterface similarImageInterface) {
|
final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
||||||
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
|
||||||
.getFileCreatedDate(context);
|
.getFileCreatedDate(context);
|
||||||
long fileCreatedDate = -1;
|
long fileCreatedDate = -1;
|
||||||
String createdTimestampSource = "";
|
String createdTimestampSource = "";
|
||||||
|
|
@ -109,19 +104,14 @@ public class UploadModel {
|
||||||
createdTimestampSource = dateTimeWithSource.getSource();
|
createdTimestampSource = dateTimeWithSource.getSource();
|
||||||
}
|
}
|
||||||
Timber.d("File created date is %d", fileCreatedDate);
|
Timber.d("File created date is %d", fileCreatedDate);
|
||||||
ImageCoordinates imageCoordinates = fileProcessor
|
final ImageCoordinates imageCoordinates = fileProcessor
|
||||||
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
|
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
|
||||||
UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
|
final UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
|
||||||
Uri.parse(uploadableFile.getFilePath()),
|
Uri.parse(uploadableFile.getFilePath()),
|
||||||
uploadableFile.getMimeType(context), source, imageCoordinates, place, fileCreatedDate,
|
uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate,
|
||||||
createdTimestampSource);
|
createdTimestampSource);
|
||||||
if (place != null) {
|
if (place != null) {
|
||||||
uploadItem.title.setTitleText(place.name);
|
uploadItem.getUploadMediaDetails().set(0, new UploadMediaDetail(place));
|
||||||
if(uploadItem.descriptions.isEmpty()) {
|
|
||||||
uploadItem.descriptions.add(new Description());
|
|
||||||
}
|
|
||||||
uploadItem.descriptions.get(0).setDescriptionText(place.getLongDescription());
|
|
||||||
uploadItem.descriptions.get(0).setLanguageCode("en");
|
|
||||||
}
|
}
|
||||||
if (!items.contains(uploadItem)) {
|
if (!items.contains(uploadItem)) {
|
||||||
items.add(uploadItem);
|
items.add(uploadItem);
|
||||||
|
|
@ -145,7 +135,7 @@ public class UploadModel {
|
||||||
return license;
|
return license;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedLicense(String licenseName) {
|
public void setSelectedLicense(final String licenseName) {
|
||||||
this.license = licensesByName.get(licenseName);
|
this.license = licensesByName.get(licenseName);
|
||||||
store.putString(Prefs.DEFAULT_LICENSE, license);
|
store.putString(Prefs.DEFAULT_LICENSE, license);
|
||||||
}
|
}
|
||||||
|
|
@ -153,26 +143,8 @@ public class UploadModel {
|
||||||
public Observable<Contribution> buildContributions() {
|
public Observable<Contribution> buildContributions() {
|
||||||
return Observable.fromIterable(items).map(item ->
|
return Observable.fromIterable(items).map(item ->
|
||||||
{
|
{
|
||||||
Contribution contribution = new Contribution(item.mediaUri, null,
|
final Contribution contribution = new Contribution(
|
||||||
item.getFileName(),
|
item, sessionManager, newListOf(selectedDepictions), newListOf(selectedCategories));
|
||||||
Description.formatList(item.descriptions), -1,
|
|
||||||
null, null, sessionManager.getAuthorName(),
|
|
||||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getDecimalCoords());
|
|
||||||
if (item.place != null) {
|
|
||||||
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
|
|
||||||
contribution.setWikiItemName(item.place.getName());
|
|
||||||
// If item already has an image, we need to know it. We don't want to override existing image later
|
|
||||||
contribution.setP18Value(item.place.pic);
|
|
||||||
}
|
|
||||||
if (null == selectedCategories) {//Just a fail safe, this should never be null
|
|
||||||
selectedCategories = new ArrayList<>();
|
|
||||||
}
|
|
||||||
contribution.setCategories(selectedCategories);
|
|
||||||
contribution.setTag("mimeType", item.mimeType);
|
|
||||||
contribution.setSource(item.source);
|
|
||||||
contribution.setContentProviderUri(item.mediaUri);
|
|
||||||
contribution.setDateUploaded(new Date());
|
|
||||||
|
|
||||||
Timber.d("Created timestamp while building contribution is %s, %s",
|
Timber.d("Created timestamp while building contribution is %s, %s",
|
||||||
item.getCreatedTimestamp(),
|
item.getCreatedTimestamp(),
|
||||||
new Date(item.getCreatedTimestamp()));
|
new Date(item.getCreatedTimestamp()));
|
||||||
|
|
@ -185,8 +157,8 @@ public class UploadModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deletePicture(String filePath) {
|
public void deletePicture(final String filePath) {
|
||||||
Iterator<UploadItem> iterator = items.iterator();
|
final Iterator<UploadItem> iterator = items.iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
if (iterator.next().mediaUri.toString().contains(filePath)) {
|
if (iterator.next().mediaUri.toString().contains(filePath)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
|
@ -202,51 +174,58 @@ public class UploadModel {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateUploadItem(int index, UploadItem uploadItem) {
|
public void updateUploadItem(final int index, final UploadItem uploadItem) {
|
||||||
UploadItem uploadItem1 = items.get(index);
|
final UploadItem uploadItem1 = items.get(index);
|
||||||
uploadItem1.setDescriptions(uploadItem.descriptions);
|
uploadItem1.setMediaDetails(uploadItem.uploadMediaDetails);
|
||||||
uploadItem1.setTitle(uploadItem.title);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex) {
|
public void onDepictItemClicked(DepictedItem depictedItem) {
|
||||||
fileProcessor.useImageCoords(imageCoordinates);
|
if (depictedItem.isSelected()) {
|
||||||
|
selectedDepictions.add(depictedItem);
|
||||||
|
} else {
|
||||||
|
selectedDepictions.remove(depictedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private <T> List<T> newListOf(final List<T> items) {
|
||||||
|
return items != null ? new ArrayList<>(items) : new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void useSimilarPictureCoordinates(final ImageCoordinates imageCoordinates, final int uploadItemIndex) {
|
||||||
|
fileProcessor.prePopulateCategoriesAndDepictionsBy(imageCoordinates);
|
||||||
items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
|
items.get(uploadItemIndex).setGpsCoords(imageCoordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<DepictedItem> getSelectedDepictions() {
|
||||||
|
return selectedDepictions;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public static class UploadItem {
|
public static class UploadItem {
|
||||||
|
|
||||||
private final Uri originalContentUri;
|
private final Uri originalContentUri;
|
||||||
private final Uri mediaUri;
|
private final Uri mediaUri;
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
private final String source;
|
|
||||||
private ImageCoordinates gpsCoords;
|
private ImageCoordinates gpsCoords;
|
||||||
|
private List<UploadMediaDetail> uploadMediaDetails;
|
||||||
public void setGpsCoords(ImageCoordinates gpsCoords) {
|
private final Place place;
|
||||||
this.gpsCoords = gpsCoords;
|
private final long createdTimestamp;
|
||||||
}
|
private final String createdTimestampSource;
|
||||||
|
private final BehaviorSubject<Integer> imageQuality;
|
||||||
private Title title;
|
|
||||||
private List<Description> descriptions;
|
|
||||||
private Place place;
|
|
||||||
private long createdTimestamp;
|
|
||||||
private String createdTimestampSource;
|
|
||||||
private BehaviorSubject<Integer> imageQuality;
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
UploadItem(Uri originalContentUri,
|
UploadItem(final Uri originalContentUri,
|
||||||
Uri mediaUri, String mimeType, String source, ImageCoordinates gpsCoords,
|
final Uri mediaUri, final String mimeType,
|
||||||
Place place,
|
final ImageCoordinates gpsCoords,
|
||||||
long createdTimestamp,
|
final Place place,
|
||||||
String createdTimestampSource) {
|
final long createdTimestamp,
|
||||||
|
final String createdTimestampSource) {
|
||||||
this.originalContentUri = originalContentUri;
|
this.originalContentUri = originalContentUri;
|
||||||
this.createdTimestampSource = createdTimestampSource;
|
this.createdTimestampSource = createdTimestampSource;
|
||||||
title = new Title();
|
uploadMediaDetails = new ArrayList<>(Arrays.asList(new UploadMediaDetail()));
|
||||||
descriptions = new ArrayList<>();
|
|
||||||
this.place = place;
|
this.place = place;
|
||||||
this.mediaUri = mediaUri;
|
this.mediaUri = mediaUri;
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.source = source;
|
|
||||||
this.gpsCoords = gpsCoords;
|
this.gpsCoords = gpsCoords;
|
||||||
this.createdTimestamp = createdTimestamp;
|
this.createdTimestamp = createdTimestamp;
|
||||||
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
||||||
|
|
@ -256,26 +235,18 @@ public class UploadModel {
|
||||||
return createdTimestampSource;
|
return createdTimestampSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSource() {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageCoordinates getGpsCoords() {
|
public ImageCoordinates getGpsCoords() {
|
||||||
return gpsCoords;
|
return gpsCoords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Description> getDescriptions() {
|
public List<UploadMediaDetail> getUploadMediaDetails() {
|
||||||
return descriptions;
|
return uploadMediaDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getCreatedTimestamp() {
|
public long getCreatedTimestamp() {
|
||||||
return createdTimestamp;
|
return createdTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Title getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getMediaUri() {
|
public Uri getMediaUri() {
|
||||||
return mediaUri;
|
return mediaUri;
|
||||||
}
|
}
|
||||||
|
|
@ -284,29 +255,16 @@ public class UploadModel {
|
||||||
return this.imageQuality.getValue();
|
return this.imageQuality.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageQuality(int imageQuality) {
|
public void setImageQuality(final int imageQuality) {
|
||||||
this.imageQuality.onNext(imageQuality);
|
this.imageQuality.onNext(imageQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileExt() {
|
|
||||||
return MimeTypeMapWrapper.getExtensionFromMimeType(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFileName() {
|
|
||||||
return title
|
|
||||||
!= null ? Utils.fixExtension(title.toString(), getFileExt()) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Place getPlace() {
|
public Place getPlace() {
|
||||||
return place;
|
return place;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTitle(Title title) {
|
public void setMediaDetails(final List<UploadMediaDetail> uploadMediaDetails) {
|
||||||
this.title = title;
|
this.uploadMediaDetails = uploadMediaDetails;
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescriptions(List<Description> descriptions) {
|
|
||||||
this.descriptions = descriptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getContentUri() {
|
public Uri getContentUri() {
|
||||||
|
|
@ -314,7 +272,7 @@ public class UploadModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable final Object obj) {
|
||||||
if (!(obj instanceof UploadItem)) {
|
if (!(obj instanceof UploadItem)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -326,6 +284,21 @@ public class UploadModel {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return mediaUri.hashCode();
|
return mediaUri.hashCode();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose a filename for the media.
|
||||||
|
* Currently, the caption is used as a filename. If several languages have been entered, the first language is used.
|
||||||
|
*/
|
||||||
|
public String getFileName() {
|
||||||
|
return uploadMediaDetails.get(0).getCaptionText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGpsCoords(final ImageCoordinates gpsCoords) {
|
||||||
|
this.gpsCoords = gpsCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import fr.free.nrw.commons.upload.categories.CategoriesContract;
|
import fr.free.nrw.commons.upload.categories.CategoriesContract;
|
||||||
import fr.free.nrw.commons.upload.categories.CategoriesPresenter;
|
import fr.free.nrw.commons.upload.categories.CategoriesPresenter;
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsContract;
|
||||||
|
import fr.free.nrw.commons.upload.depicts.DepictsPresenter;
|
||||||
import fr.free.nrw.commons.upload.license.MediaLicenseContract;
|
import fr.free.nrw.commons.upload.license.MediaLicenseContract;
|
||||||
import fr.free.nrw.commons.upload.license.MediaLicensePresenter;
|
import fr.free.nrw.commons.upload.license.MediaLicensePresenter;
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract;
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract;
|
||||||
|
|
@ -33,4 +35,9 @@ public abstract class UploadModule {
|
||||||
UploadMediaPresenter
|
UploadMediaPresenter
|
||||||
presenter);
|
presenter);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract DepictsContract.UserActionListener bindsDepictsPresenter(
|
||||||
|
DepictsPresenter
|
||||||
|
presenter
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.upload;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
@ -89,7 +90,6 @@ public class UploadPresenter implements UploadContract.UserActionListener {
|
||||||
if (index == uploadableFiles.size() - 1) {//If the next fragment to be shown is not one of the MediaDetailsFragment, lets hide the top card
|
if (index == uploadableFiles.size() - 1) {//If the next fragment to be shown is not one of the MediaDetailsFragment, lets hide the top card
|
||||||
view.showHideTopCard(false);
|
view.showHideTopCard(false);
|
||||||
}
|
}
|
||||||
//Ask the repository to delete the picture
|
|
||||||
repository.deletePicture(uploadableFiles.get(index).getFilePath());
|
repository.deletePicture(uploadableFiles.get(index).getFilePath());
|
||||||
if (uploadableFiles.size() == 1) {
|
if (uploadableFiles.size() == 1) {
|
||||||
view.showMessage(R.string.upload_cancelled);
|
view.showMessage(R.string.upload_cancelled);
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,16 @@ package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
import org.wikipedia.gallery.ImageInfo
|
import org.wikipedia.gallery.ImageInfo
|
||||||
|
|
||||||
class UploadResult(val result: String, val filekey: String, val filename: String, val sessionkey: String, val imageinfo: ImageInfo)
|
private const val RESULT_SUCCESS = "Success"
|
||||||
|
|
||||||
|
data class UploadResult(
|
||||||
|
val result: String,
|
||||||
|
val filekey: String,
|
||||||
|
val filename: String,
|
||||||
|
val sessionkey: String,
|
||||||
|
val imageinfo: ImageInfo
|
||||||
|
) {
|
||||||
|
fun isSuccessful(): Boolean = result == RESULT_SUCCESS
|
||||||
|
|
||||||
|
fun createCanonicalFileName() = "File:$filename"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,8 @@ import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
|
|
@ -35,10 +23,17 @@ import fr.free.nrw.commons.utils.CommonsDateUtil;
|
||||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Scheduler;
|
||||||
import io.reactivex.SingleObserver;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadService extends HandlerService<Contribution> {
|
public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
@ -48,7 +43,6 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
public static final int ACTION_UPLOAD_FILE = 1;
|
public static final int ACTION_UPLOAD_FILE = 1;
|
||||||
|
|
||||||
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
||||||
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
|
|
||||||
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
|
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
|
||||||
@Inject WikidataEditService wikidataEditService;
|
@Inject WikidataEditService wikidataEditService;
|
||||||
@Inject SessionManager sessionManager;
|
@Inject SessionManager sessionManager;
|
||||||
|
|
@ -152,7 +146,6 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queue(int what, Contribution contribution) {
|
public void queue(int what, Contribution contribution) {
|
||||||
Timber.d("Upload service queue has contribution with wiki data entity id as %s", contribution.getWikiDataEntityId());
|
|
||||||
switch (what) {
|
switch (what) {
|
||||||
case ACTION_UPLOAD_FILE:
|
case ACTION_UPLOAD_FILE:
|
||||||
|
|
||||||
|
|
@ -169,7 +162,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
.subscribeOn(ioThreadScheduler)
|
.subscribeOn(ioThreadScheduler)
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(aLong->{
|
.subscribe(aLong->{
|
||||||
contribution._id = aLong;
|
contribution.set_id(aLong);
|
||||||
UploadService.super.queue(what, contribution);
|
UploadService.super.queue(what, contribution);
|
||||||
}, Throwable::printStackTrace));
|
}, Throwable::printStackTrace));
|
||||||
break;
|
break;
|
||||||
|
|
@ -252,55 +245,68 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
||||||
Timber.d("Stash upload response 1 is %s", uploadStash.toString());
|
Timber.d("Stash upload response 1 is %s", uploadStash.toString());
|
||||||
|
|
||||||
String resultStatus = uploadStash.getResult();
|
if (uploadStash.isSuccessful()) {
|
||||||
if (!resultStatus.equals("Success")) {
|
|
||||||
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
|
|
||||||
showFailedNotification(contribution);
|
|
||||||
return Observable.never();
|
|
||||||
} else {
|
|
||||||
Timber.d("making sure of uniqueness of name: %s", filename);
|
Timber.d("making sure of uniqueness of name: %s", filename);
|
||||||
String uniqueFilename = findUniqueFilename(filename);
|
String uniqueFilename = findUniqueFilename(filename);
|
||||||
unfinishedUploads.add(uniqueFilename);
|
unfinishedUploads.add(uniqueFilename);
|
||||||
return uploadClient.uploadFileFromStash(
|
return uploadClient.uploadFileFromStash(
|
||||||
getApplicationContext(),
|
getApplicationContext(),
|
||||||
contribution,
|
contribution,
|
||||||
uniqueFilename,
|
uniqueFilename,
|
||||||
uploadStash.getFilekey());
|
uploadStash.getFilekey());
|
||||||
}
|
} else {
|
||||||
})
|
|
||||||
.subscribe(uploadResult -> {
|
|
||||||
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
|
|
||||||
|
|
||||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
|
||||||
|
|
||||||
String resultStatus = uploadResult.getResult();
|
|
||||||
if (!resultStatus.equals("Success")) {
|
|
||||||
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
|
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
|
||||||
showFailedNotification(contribution);
|
showFailedNotification(contribution);
|
||||||
} else {
|
return Observable.never();
|
||||||
String canonicalFilename = "File:" + uploadResult.getFilename();
|
|
||||||
Timber.d("Contribution upload success. Initiating Wikidata edit for"
|
|
||||||
+ " entity id %s if necessary (if P18 is null). P18 value is %s",
|
|
||||||
contribution.getWikiDataEntityId(), contribution.getP18Value());
|
|
||||||
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), contribution.getWikiItemName(), canonicalFilename, contribution.getP18Value());
|
|
||||||
contribution.setFilename(canonicalFilename);
|
|
||||||
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
|
|
||||||
contribution.setState(Contribution.STATE_COMPLETED);
|
|
||||||
contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp()
|
|
||||||
.parse(uploadResult.getImageinfo().getTimestamp()));
|
|
||||||
compositeDisposable.add(contributionDao
|
|
||||||
.save(contribution)
|
|
||||||
.subscribeOn(ioThreadScheduler)
|
|
||||||
.observeOn(mainThreadScheduler)
|
|
||||||
.subscribe());
|
|
||||||
}
|
}
|
||||||
}, throwable -> {
|
})
|
||||||
|
.subscribe(
|
||||||
|
uploadResult -> onUpload(contribution, notificationTag, uploadResult),
|
||||||
|
throwable -> {
|
||||||
Timber.w(throwable, "Exception during upload");
|
Timber.w(throwable, "Exception during upload");
|
||||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||||
showFailedNotification(contribution);
|
showFailedNotification(contribution);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onUpload(Contribution contribution, String notificationTag,
|
||||||
|
UploadResult uploadResult) throws ParseException {
|
||||||
|
Timber.d("Stash upload response 2 is %s", uploadResult.toString());
|
||||||
|
|
||||||
|
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||||
|
|
||||||
|
if (uploadResult.isSuccessful()) {
|
||||||
|
onSuccessfulUpload(contribution, uploadResult);
|
||||||
|
} else {
|
||||||
|
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
|
||||||
|
showFailedNotification(contribution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult)
|
||||||
|
throws ParseException {
|
||||||
|
compositeDisposable
|
||||||
|
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
|
||||||
|
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
|
||||||
|
if (wikidataPlace != null && wikidataPlace.getImageValue() == null) {
|
||||||
|
wikidataEditService.createImageClaim(wikidataPlace, uploadResult);
|
||||||
|
}
|
||||||
|
saveCompletedContribution(contribution, uploadResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) throws ParseException {
|
||||||
|
contribution.setFilename(uploadResult.createCanonicalFileName());
|
||||||
|
contribution.setImageUrl(uploadResult.getImageinfo().getOriginalUrl());
|
||||||
|
contribution.setState(Contribution.STATE_COMPLETED);
|
||||||
|
contribution.setDateUploaded(CommonsDateUtil.getIso8601DateFormatTimestamp()
|
||||||
|
.parse(uploadResult.getImageinfo().getTimestamp()));
|
||||||
|
compositeDisposable.add(contributionDao
|
||||||
|
.save(contribution)
|
||||||
|
.subscribeOn(ioThreadScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe());
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
@SuppressLint("StringFormatInvalid")
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private void showFailedNotification(Contribution contribution) {
|
private void showFailedNotification(Contribution contribution) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwPostResponse;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
import retrofit2.http.Field;
|
||||||
|
import retrofit2.http.FormUrlEncoded;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Headers;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrofit calls for managing responses network calls of entity ids required for uploading depictions
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface WikiBaseInterface {
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=wbeditentity")
|
||||||
|
Observable<MwPostResponse> postEditEntity(@NonNull @Field("id") String fileEntityId,
|
||||||
|
@NonNull @Field("token") String editToken,
|
||||||
|
@NonNull @Field("data") String data);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=info")
|
||||||
|
Observable<MwQueryResponse> getFileEntityId(@Query("titles") String fileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload Captions for the image when upload is successful
|
||||||
|
*
|
||||||
|
* @param fileEntityId enityId for the uploaded file
|
||||||
|
* @param editToken editToken for the file
|
||||||
|
* @param captionValue value of the caption to be uploaded
|
||||||
|
*/
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=wbsetlabel")
|
||||||
|
Observable<MwPostResponse> addLabelstoWikidata(@Field("id") String fileEntityId,
|
||||||
|
@Field("token") String editToken,
|
||||||
|
@Field("language") String language,
|
||||||
|
@Field("value") String captionValue);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
interface WikidataItem {
|
||||||
|
val id:String
|
||||||
|
val name:String
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import fr.free.nrw.commons.nearby.Place
|
||||||
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
internal data class WikidataPlace(override val id: String, override val name: String, val imageValue: String?) :
|
||||||
|
WikidataItem,Parcelable {
|
||||||
|
constructor(place: Place) : this(
|
||||||
|
place.wikiDataEntityId!!,
|
||||||
|
place.name,
|
||||||
|
place.pic.takeIf { it.isNotBlank() })
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun from(place: Place?): WikidataPlace? {
|
||||||
|
return place?.let { WikidataPlace(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import fr.free.nrw.commons.category.CategoryItem;
|
||||||
*/
|
*/
|
||||||
public interface CategoriesContract {
|
public interface CategoriesContract {
|
||||||
|
|
||||||
public interface View {
|
interface View {
|
||||||
|
|
||||||
void showProgress(boolean shouldShow);
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
|
|
@ -20,16 +20,13 @@ public interface CategoriesContract {
|
||||||
|
|
||||||
void setCategories(List<CategoryItem> categories);
|
void setCategories(List<CategoryItem> categories);
|
||||||
|
|
||||||
void addCategory(CategoryItem category);
|
|
||||||
|
|
||||||
void goToNextScreen();
|
void goToNextScreen();
|
||||||
|
|
||||||
void showNoCategorySelected();
|
void showNoCategorySelected();
|
||||||
|
|
||||||
void setSelectedCategories(List<CategoryItem> selectedCategories);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface UserActionListener extends BasePresenter<View> {
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
void searchForCategories(String query);
|
void searchForCategories(String query);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
package fr.free.nrw.commons.upload.categories;
|
package fr.free.nrw.commons.upload.categories;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.category.CategoryItem;
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
import fr.free.nrw.commons.repository.UploadRepository;
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
|
|
@ -18,11 +12,14 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.Scheduler;
|
import io.reactivex.Scheduler;
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The presenter class for UploadCategoriesFragment
|
* The presenter class for UploadCategoriesFragment
|
||||||
*/
|
*/
|
||||||
|
|
@ -86,9 +83,10 @@ public class CategoriesPresenter implements CategoriesContract.UserActionListene
|
||||||
)
|
)
|
||||||
.filter(categoryItem -> !repository.containsYear(categoryItem.getName()))
|
.filter(categoryItem -> !repository.containsYear(categoryItem.getName()))
|
||||||
.distinct();
|
.distinct();
|
||||||
if(!TextUtils.isEmpty(query)) {
|
|
||||||
distinctCategoriesObservable=distinctCategoriesObservable.sorted(repository.sortBySimilarity(query));
|
if(!TextUtils.isEmpty(query)) {
|
||||||
}
|
distinctCategoriesObservable=distinctCategoriesObservable.sorted(repository.sortBySimilarity(query));
|
||||||
|
}
|
||||||
Disposable searchCategoriesDisposable = distinctCategoriesObservable
|
Disposable searchCategoriesDisposable = distinctCategoriesObservable
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
|
|
@ -114,8 +112,9 @@ public class CategoriesPresenter implements CategoriesContract.UserActionListene
|
||||||
private List<String> getImageTitleList() {
|
private List<String> getImageTitleList() {
|
||||||
List<String> titleList = new ArrayList<>();
|
List<String> titleList = new ArrayList<>();
|
||||||
for (UploadItem item : repository.getUploads()) {
|
for (UploadItem item : repository.getUploads()) {
|
||||||
if (item.getTitle().isSet()) {
|
final String captionText = item.getUploadMediaDetails().get(0).getCaptionText();
|
||||||
titleList.add(item.getTitle().toString());
|
if (!TextUtils.isEmpty(captionText)) {
|
||||||
|
titleList.add(captionText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return titleList;
|
return titleList;
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,18 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.jakewharton.rxbinding2.view.RxView;
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
import com.jakewharton.rxbinding2.widget.RxTextView;
|
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.category.CategoryClickedListener;
|
import fr.free.nrw.commons.category.CategoryClickedListener;
|
||||||
import fr.free.nrw.commons.category.CategoryItem;
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
|
|
@ -35,6 +26,10 @@ import fr.free.nrw.commons.upload.UploadCategoriesAdapterFactory;
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadCategoriesFragment extends UploadBaseFragment implements CategoriesContract.View,
|
public class UploadCategoriesFragment extends UploadBaseFragment implements CategoriesContract.View,
|
||||||
|
|
@ -54,7 +49,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
@Inject
|
@Inject
|
||||||
CategoriesContract.UserActionListener presenter;
|
CategoriesContract.UserActionListener presenter;
|
||||||
private RVRendererAdapter<CategoryItem> adapter;
|
private RVRendererAdapter<CategoryItem> adapter;
|
||||||
private List<String> mediaTitleList=new ArrayList<>();
|
|
||||||
private Disposable subscribe;
|
private Disposable subscribe;
|
||||||
private List<CategoryItem> categories;
|
private List<CategoryItem> categories;
|
||||||
private boolean isVisible;
|
private boolean isVisible;
|
||||||
|
|
@ -64,10 +58,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMediaTitleList(List<String> mediaTitleList) {
|
|
||||||
this.mediaTitleList = mediaTitleList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
|
@ -151,12 +141,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addCategory(CategoryItem category) {
|
|
||||||
adapter.add(category);
|
|
||||||
adapter.notifyItemInserted(adapter.getItemCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void goToNextScreen() {
|
public void goToNextScreen() {
|
||||||
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
|
@ -174,11 +158,6 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSelectedCategories(List<CategoryItem> selectedCategories) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.btn_next)
|
@OnClick(R.id.btn_next)
|
||||||
public void onNextButtonClicked() {
|
public void onNextButtonClicked() {
|
||||||
presenter.verifyCategories();
|
presenter.verifyCategories();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package fr.free.nrw.commons.upload.depicts;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract with which DepictsFragment and its presenter would talk to each other
|
||||||
|
*/
|
||||||
|
public interface DepictsContract {
|
||||||
|
|
||||||
|
interface View {
|
||||||
|
/**
|
||||||
|
* Go to category screen
|
||||||
|
*/
|
||||||
|
void goToNextScreen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to media detail screen
|
||||||
|
*/
|
||||||
|
void goToPreviousScreen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show error in case of no depiction selected
|
||||||
|
*/
|
||||||
|
void noDepictionSelected();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show progress/Hide progress depending on the boolean value
|
||||||
|
*/
|
||||||
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decides whether to show error values or not depending on the boolean value
|
||||||
|
*/
|
||||||
|
void showError(Boolean value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add depictions to list
|
||||||
|
*/
|
||||||
|
void setDepictsList(List<DepictedItem> depictedItemList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set thumbnail image for depicted item
|
||||||
|
*/
|
||||||
|
void onImageUrlFetched(String response, int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes to previous screen
|
||||||
|
*/
|
||||||
|
void onPreviousButtonClicked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for the depicted items selected from the list
|
||||||
|
*/
|
||||||
|
void onDepictItemClicked(DepictedItem depictedItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the repository to fetch depictions for the query
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
void searchForDepictions(String query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if depictions were selected
|
||||||
|
* from the depiction list
|
||||||
|
*/
|
||||||
|
void verifyDepictions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch thumbnail for the Wikidata Item
|
||||||
|
* @param entityId entityId of the item
|
||||||
|
* @param position position of the item
|
||||||
|
*/
|
||||||
|
void fetchThumbnailForEntityId(String entityId, int position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package fr.free.nrw.commons.upload.depicts;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
|
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||||
|
import fr.free.nrw.commons.upload.UploadDepictsAdapterFactory;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.UploadDepictsCallback;
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for showing depicted items list in Upload activity after media details
|
||||||
|
*/
|
||||||
|
public class DepictsFragment extends UploadBaseFragment implements DepictsContract.View, UploadDepictsCallback {
|
||||||
|
|
||||||
|
@BindView(R.id.depicts_title)
|
||||||
|
TextView depictsTitle;
|
||||||
|
@BindView(R.id.depicts_search_container)
|
||||||
|
TextInputLayout depictsSearchContainer;
|
||||||
|
@BindView(R.id.depicts_search)
|
||||||
|
TextInputEditText depictsSearch;
|
||||||
|
@BindView(R.id.depictsSearchInProgress)
|
||||||
|
ProgressBar depictsSearchInProgress;
|
||||||
|
@BindView(R.id.depicts_recycler_view)
|
||||||
|
RecyclerView depictsRecyclerView;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DepictsContract.UserActionListener presenter;
|
||||||
|
private RVRendererAdapter<DepictedItem> adapter;
|
||||||
|
private Disposable subscribe;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public android.view.View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.upload_depicts_fragment, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull android.view.View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize presenter and views
|
||||||
|
*/
|
||||||
|
private void init() {
|
||||||
|
depictsTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
|
||||||
|
callback.getTotalNumberOfSteps()));
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
initRecyclerView();
|
||||||
|
addTextChangeListenerToSearchBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise recyclerView and set adapter
|
||||||
|
*/
|
||||||
|
private void initRecyclerView() {
|
||||||
|
adapter = new UploadDepictsAdapterFactory(this)
|
||||||
|
.create(new ArrayList<>());
|
||||||
|
depictsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
depictsRecyclerView.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void goToNextScreen() {
|
||||||
|
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void goToPreviousScreen() {
|
||||||
|
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noDepictionSelected() {
|
||||||
|
DialogUtil.showAlertDialog(getActivity(),
|
||||||
|
getString(R.string.no_depictions_selected),
|
||||||
|
getString(R.string.no_depictions_selected_warning_desc),
|
||||||
|
getString(R.string.yes_submit),
|
||||||
|
getString(R.string.no_go_back),
|
||||||
|
this::goToNextScreen,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
presenter.onDetachView();
|
||||||
|
subscribe.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showProgress(boolean shouldShow) {
|
||||||
|
depictsSearchInProgress.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showError(Boolean value) {
|
||||||
|
if (value)
|
||||||
|
depictsSearchContainer.setError(getString(R.string.no_depiction_found));
|
||||||
|
else depictsSearchContainer.setErrorEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDepictsList(List<DepictedItem> depictedItemList) {
|
||||||
|
adapter.clear();
|
||||||
|
if (depictedItemList != null) {
|
||||||
|
adapter.addAll(depictedItemList);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set thumbnail image for depicted item
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onImageUrlFetched(String response, int position) {
|
||||||
|
adapter.getItem(position).setImageUrl(response);
|
||||||
|
adapter.notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.depicts_next)
|
||||||
|
public void onNextButtonClicked() {
|
||||||
|
presenter.verifyDepictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.depicts_previous)
|
||||||
|
public void onPreviousButtonClicked() {
|
||||||
|
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void depictsClicked(DepictedItem item) {
|
||||||
|
presenter.onDepictItemClicked(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch thumbnail for the given entityId at the given position
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fetchThumbnailUrlForEntity(String entityId, int position) {
|
||||||
|
presenter.fetchThumbnailForEntityId(entityId,position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text change listener for the edit text view of depicts
|
||||||
|
*/
|
||||||
|
private void addTextChangeListenerToSearchBox() {
|
||||||
|
subscribe = RxTextView.textChanges(depictsSearch)
|
||||||
|
.doOnEach(v -> depictsSearchContainer.setError(null))
|
||||||
|
.takeUntil(RxView.detaches(depictsSearch))
|
||||||
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(filter -> searchForDepictions(filter.toString()), Timber::e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for depictions for the following query
|
||||||
|
*
|
||||||
|
* @param query query string
|
||||||
|
*/
|
||||||
|
private void searchForDepictions(String query) {
|
||||||
|
presenter.searchForDepictions(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package fr.free.nrw.commons.upload.depicts;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.wikidata.model.DepictSearchResponse;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import org.wikipedia.wikidata.ClaimsResponse;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manges retrofit calls for Searching of depicts from DepictsFragment
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface DepictsInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for depictions using the wbsearchentities API
|
||||||
|
* @param query search for depictions based on user query
|
||||||
|
* @param limit number of depictions to be retrieved
|
||||||
|
* @param language current locale of the phone
|
||||||
|
* @param uselang current locale of the phone
|
||||||
|
* @param offset number of depictions already fetched useful in implementing pagination
|
||||||
|
*/
|
||||||
|
@GET("/w/api.php?action=wbsearchentities&format=json&type=item&uselang=en")
|
||||||
|
Observable<DepictSearchResponse> searchForDepicts(@Query("search") String query, @Query("limit") String limit, @Query("language") String language, @Query("uselang") String uselang, @Query("continue") String offset);
|
||||||
|
|
||||||
|
@GET("/w/api.php?action=wbgetclaims&format=json&property=P18")
|
||||||
|
Observable<ClaimsResponse> getImageForEntity(@Query("entity") String entityId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
package fr.free.nrw.commons.upload.depicts;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.explore.depictions.DepictsClient;
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
|
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* presenter for DepictsFragment
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class DepictsPresenter implements DepictsContract.UserActionListener {
|
||||||
|
|
||||||
|
private static final DepictsContract.View DUMMY = (DepictsContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
DepictsContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{DepictsContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
|
||||||
|
|
||||||
|
private final Scheduler ioScheduler;
|
||||||
|
private final Scheduler mainThreadScheduler;
|
||||||
|
private DepictsContract.View view = DUMMY;
|
||||||
|
private UploadRepository repository;
|
||||||
|
private DepictsClient depictsClient;
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
|
||||||
|
private CompositeDisposable compositeDisposable;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DepictsPresenter(UploadRepository uploadRepository, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||||
|
@Named(MAIN_THREAD) Scheduler mainThreadScheduler, DepictsClient depictsClient) {
|
||||||
|
this.repository = uploadRepository;
|
||||||
|
this.ioScheduler = ioScheduler;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
this.depictsClient = depictsClient;
|
||||||
|
compositeDisposable = new CompositeDisposable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachView(DepictsContract.View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreviousButtonClicked() {
|
||||||
|
view.goToPreviousScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDepictItemClicked(DepictedItem depictedItem) {
|
||||||
|
repository.onDepictItemClicked(depictedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the repository to fetch depictions for the query
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void searchForDepictions(String query) {
|
||||||
|
List<DepictedItem> depictedItemList = new ArrayList<>();
|
||||||
|
Observable<DepictedItem> distinctDepictsObservable = Observable
|
||||||
|
.fromIterable(repository.getSelectedDepictions())
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.doOnSubscribe(disposable -> {
|
||||||
|
view.showProgress(true);
|
||||||
|
view.setDepictsList(null);
|
||||||
|
})
|
||||||
|
.observeOn(ioScheduler)
|
||||||
|
.concatWith(
|
||||||
|
repository.searchAllEntities(query)
|
||||||
|
)
|
||||||
|
.distinct();
|
||||||
|
|
||||||
|
Disposable searchDepictsDisposable = distinctDepictsObservable
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(
|
||||||
|
e -> {
|
||||||
|
depictedItemList.add(e);
|
||||||
|
},
|
||||||
|
t -> {
|
||||||
|
view.showProgress(false);
|
||||||
|
view.showError(true);
|
||||||
|
Timber.e(t);
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
view.showProgress(false);
|
||||||
|
|
||||||
|
if (depictedItemList.isEmpty()) {
|
||||||
|
view.showError(true);
|
||||||
|
} else {
|
||||||
|
view.showError(false);
|
||||||
|
view.setDepictsList(depictedItemList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
compositeDisposable.add(searchDepictsDisposable);
|
||||||
|
view.setDepictsList(depictedItemList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if depictions were selected
|
||||||
|
* from the depiction list
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void verifyDepictions() {
|
||||||
|
List<DepictedItem> selectedDepictions = repository.getSelectedDepictions();
|
||||||
|
if (selectedDepictions != null && !selectedDepictions.isEmpty()) {
|
||||||
|
view.goToNextScreen();
|
||||||
|
} else {
|
||||||
|
view.noDepictionSelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch thumbnail for the Wikidata Item
|
||||||
|
* @param entityId entityId of the item
|
||||||
|
* @param position position of the item
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fetchThumbnailForEntityId(String entityId, int position) {
|
||||||
|
compositeDisposable.add(depictsClient.getP18ForItem(entityId)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(response -> {
|
||||||
|
view.onImageUrlFetched(response,position);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,15 +3,10 @@ package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
@ -24,7 +19,6 @@ import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import com.github.chrisbanes.photoview.PhotoView;
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
import com.jakewharton.rxbinding2.widget.RxTextView;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
|
@ -32,18 +26,16 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.upload.Description;
|
|
||||||
import fr.free.nrw.commons.upload.DescriptionsAdapter;
|
|
||||||
import fr.free.nrw.commons.upload.ImageCoordinates;
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
|
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
|
||||||
import fr.free.nrw.commons.upload.Title;
|
|
||||||
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||||
|
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||||
|
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter;
|
||||||
import fr.free.nrw.commons.upload.UploadModel;
|
import fr.free.nrw.commons.upload.UploadModel;
|
||||||
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -53,8 +45,10 @@ import javax.inject.Named;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
//import fr.free.nrw.commons.upload.DescriptionsAdapter;
|
||||||
|
|
||||||
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
UploadMediaDetailsContract.View {
|
UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener {
|
||||||
|
|
||||||
@BindView(R.id.tv_title)
|
@BindView(R.id.tv_title)
|
||||||
TextView tvTitle;
|
TextView tvTitle;
|
||||||
|
|
@ -64,8 +58,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
AppCompatImageButton ibExpandCollapse;
|
AppCompatImageButton ibExpandCollapse;
|
||||||
@BindView(R.id.ll_container_media_detail)
|
@BindView(R.id.ll_container_media_detail)
|
||||||
LinearLayout llContainerMediaDetail;
|
LinearLayout llContainerMediaDetail;
|
||||||
@BindView(R.id.et_title)
|
|
||||||
EditText etTitle;
|
|
||||||
@BindView(R.id.rv_descriptions)
|
@BindView(R.id.rv_descriptions)
|
||||||
RecyclerView rvDescriptions;
|
RecyclerView rvDescriptions;
|
||||||
@BindView(R.id.backgroundImage)
|
@BindView(R.id.backgroundImage)
|
||||||
|
|
@ -74,12 +66,12 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
AppCompatButton btnNext;
|
AppCompatButton btnNext;
|
||||||
@BindView(R.id.btn_previous)
|
@BindView(R.id.btn_previous)
|
||||||
AppCompatButton btnPrevious;
|
AppCompatButton btnPrevious;
|
||||||
private DescriptionsAdapter descriptionsAdapter;
|
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
|
||||||
@BindView(R.id.btn_copy_prev_title_desc)
|
@BindView(R.id.btn_copy_prev_title_desc)
|
||||||
AppCompatButton btnCopyPreviousTitleDesc;
|
AppCompatButton btnCopyPreviousTitleDesc;
|
||||||
|
|
||||||
private UploadModel.UploadItem uploadItem;
|
private UploadModel.UploadItem uploadItem;
|
||||||
private List<Description> descriptions;
|
private List<UploadMediaDetail> descriptions;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UploadMediaDetailsContract.UserActionListener presenter;
|
UploadMediaDetailsContract.UserActionListener presenter;
|
||||||
|
|
@ -89,10 +81,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
JsonKvStore defaultKvStore;
|
JsonKvStore defaultKvStore;
|
||||||
|
|
||||||
private UploadableFile uploadableFile;
|
private UploadableFile uploadableFile;
|
||||||
private String source;
|
|
||||||
private Place place;
|
private Place place;
|
||||||
|
|
||||||
private Title title;
|
|
||||||
private boolean isExpanded = true;
|
private boolean isExpanded = true;
|
||||||
|
|
||||||
private UploadMediaDetailFragmentCallback callback;
|
private UploadMediaDetailFragmentCallback callback;
|
||||||
|
|
@ -106,9 +96,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageTobeUploaded(UploadableFile uploadableFile, String source, Place place) {
|
public void setImageTobeUploaded(UploadableFile uploadableFile, Place place) {
|
||||||
this.uploadableFile = uploadableFile;
|
this.uploadableFile = uploadableFile;
|
||||||
this.source = source;
|
|
||||||
this.place = place;
|
this.place = place;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,25 +118,9 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
private void init() {
|
private void init() {
|
||||||
tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
|
tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
|
||||||
callback.getTotalNumberOfSteps()));
|
callback.getTotalNumberOfSteps()));
|
||||||
title = new Title();
|
|
||||||
initRecyclerView();
|
initRecyclerView();
|
||||||
initPresenter();
|
initPresenter();
|
||||||
Disposable disposable = RxTextView.textChanges(etTitle)
|
presenter.receiveImage(uploadableFile, place);
|
||||||
.subscribe(text -> {
|
|
||||||
if (!TextUtils.isEmpty(text)) {
|
|
||||||
btnNext.setEnabled(true);
|
|
||||||
btnNext.setClickable(true);
|
|
||||||
btnNext.setAlpha(1.0f);
|
|
||||||
title.setTitleText(text.toString());
|
|
||||||
uploadItem.setTitle(title);
|
|
||||||
} else {
|
|
||||||
btnNext.setAlpha(0.5f);
|
|
||||||
btnNext.setEnabled(false);
|
|
||||||
btnNext.setClickable(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
compositeDisposable.add(disposable);
|
|
||||||
presenter.receiveImage(uploadableFile, source, place);
|
|
||||||
|
|
||||||
if (callback.getIndexInViewFlipper(this) == 0) {
|
if (callback.getIndexInViewFlipper(this) == 0) {
|
||||||
btnPrevious.setEnabled(false);
|
btnPrevious.setEnabled(false);
|
||||||
|
|
@ -166,36 +139,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
||||||
attachImageViewScaleChangeListener();
|
attachImageViewScaleChangeListener();
|
||||||
|
|
||||||
addEtTitleTouchListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the drawable click listener for Edit Text
|
|
||||||
*/
|
|
||||||
private void addEtTitleTouchListener() {
|
|
||||||
etTitle.setOnTouchListener((v, event) -> {
|
|
||||||
//2 is for drawable right
|
|
||||||
float twelveDpInPixels = convertDpToPixel(12, getContext());
|
|
||||||
if (event.getAction() == MotionEvent.ACTION_UP && etTitle.getCompoundDrawables() != null
|
|
||||||
&& etTitle.getCompoundDrawables().length > 2 && etTitle
|
|
||||||
.getCompoundDrawables()[2].getBounds()
|
|
||||||
.contains((int) (etTitle.getWidth() - (event.getX() + twelveDpInPixels)),
|
|
||||||
(int) (event.getY() - twelveDpInPixels))) {
|
|
||||||
showInfoAlert(R.string.media_detail_title, R.string.title_info);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* converts dp to pixel
|
|
||||||
* @param dp
|
|
||||||
* @param context
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private float convertDpToPixel(float dp, Context context) {
|
|
||||||
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -217,13 +160,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init the recycler veiw
|
* init the description recycler veiw and caption recyclerview
|
||||||
*/
|
*/
|
||||||
private void initRecyclerView() {
|
private void initRecyclerView() {
|
||||||
descriptionsAdapter = new DescriptionsAdapter(defaultKvStore.getString(Prefs.KEY_LANGUAGE_VALUE, ""));
|
uploadMediaDetailAdapter = new UploadMediaDetailAdapter(defaultKvStore.getString(Prefs.KEY_LANGUAGE_VALUE, ""));
|
||||||
descriptionsAdapter.setCallback(this::showInfoAlert);
|
uploadMediaDetailAdapter.setCallback(this::showInfoAlert);
|
||||||
|
uploadMediaDetailAdapter.setEventListener(this);
|
||||||
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
rvDescriptions.setAdapter(uploadMediaDetailAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -237,7 +181,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
||||||
@OnClick(R.id.btn_next)
|
@OnClick(R.id.btn_next)
|
||||||
public void onNextButtonClicked() {
|
public void onNextButtonClicked() {
|
||||||
uploadItem.setDescriptions(descriptionsAdapter.getDescriptions());
|
uploadItem.setMediaDetails(uploadMediaDetailAdapter.getUploadMediaDetails());
|
||||||
presenter.verifyImageQuality(uploadItem);
|
presenter.verifyImageQuality(uploadItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,9 +192,9 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
||||||
@OnClick(R.id.btn_add_description)
|
@OnClick(R.id.btn_add_description)
|
||||||
public void onButtonAddDescriptionClicked() {
|
public void onButtonAddDescriptionClicked() {
|
||||||
Description description = new Description();
|
UploadMediaDetail uploadMediaDetail = new UploadMediaDetail();
|
||||||
description.setManuallyAdded(true);//This was manually added by the user
|
uploadMediaDetail.setManuallyAdded(true);//This was manually added by the user
|
||||||
descriptionsAdapter.addDescription(description);
|
uploadMediaDetailAdapter.addDescription(uploadMediaDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -279,11 +223,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
@Override
|
@Override
|
||||||
public void onImageProcessed(UploadItem uploadItem, Place place) {
|
public void onImageProcessed(UploadItem uploadItem, Place place) {
|
||||||
this.uploadItem = uploadItem;
|
this.uploadItem = uploadItem;
|
||||||
if (uploadItem.getTitle() != null) {
|
descriptions = uploadItem.getUploadMediaDetails();
|
||||||
etTitle.setText(uploadItem.getTitle().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptions = uploadItem.getDescriptions();
|
|
||||||
photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri());
|
photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri());
|
||||||
setDescriptionsInAdapter(descriptions);
|
setDescriptionsInAdapter(descriptions);
|
||||||
}
|
}
|
||||||
|
|
@ -302,11 +242,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
getString(R.string.upload_nearby_place_found_description),
|
getString(R.string.upload_nearby_place_found_description),
|
||||||
place.getName()),
|
place.getName()),
|
||||||
() -> {
|
() -> {
|
||||||
etTitle.setText(place.getName());
|
descriptions = new ArrayList<>(Arrays.asList(new UploadMediaDetail(place)));
|
||||||
Description description = new Description();
|
|
||||||
description.setLanguageCode("en");
|
|
||||||
description.setDescriptionText(place.getLongDescription());
|
|
||||||
descriptions = Arrays.asList(description);
|
|
||||||
setDescriptionsInAdapter(descriptions);
|
setDescriptionsInAdapter(descriptions);
|
||||||
},
|
},
|
||||||
() -> {
|
() -> {
|
||||||
|
|
@ -376,9 +312,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitleAndDescription(String title, List<Description> descriptions) {
|
public void setCaptionsAndDescriptions(List<UploadMediaDetail> uploadMediaDetails) {
|
||||||
etTitle.setText(title);
|
setDescriptionsInAdapter(uploadMediaDetails);
|
||||||
setDescriptionsInAdapter(descriptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteThisPicture() {
|
private void deleteThisPicture() {
|
||||||
|
|
@ -412,6 +347,13 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
uploadItem.getGpsCoords().getDecLongitude(), 0.0f));
|
uploadItem.getGpsCoords().getDecLongitude(), 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrimaryCaptionTextChange(boolean isNotEmpty) {
|
||||||
|
btnNext.setEnabled(isNotEmpty);
|
||||||
|
btnNext.setClickable(isNotEmpty);
|
||||||
|
btnNext.setAlpha(isNotEmpty ? 1.0f: 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public interface UploadMediaDetailFragmentCallback extends Callback {
|
public interface UploadMediaDetailFragmentCallback extends Callback {
|
||||||
|
|
||||||
|
|
@ -424,15 +366,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
presenter.fetchPreviousTitleAndDescription(callback.getIndexInViewFlipper(this));
|
presenter.fetchPreviousTitleAndDescription(callback.getIndexInViewFlipper(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDescriptionsInAdapter(List<Description> descriptions) {
|
private void setDescriptionsInAdapter(List<UploadMediaDetail> uploadMediaDetails){
|
||||||
if (descriptions == null) {
|
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
|
||||||
descriptions = new ArrayList<>();
|
|
||||||
}
|
|
||||||
if (descriptions.size() == 0) {
|
|
||||||
descriptionsAdapter.addDescription(new Description());
|
|
||||||
} else {
|
|
||||||
descriptionsAdapter.setItems(descriptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
package fr.free.nrw.commons.upload.mediaDetails;
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
import fr.free.nrw.commons.upload.ImageCoordinates;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.BasePresenter;
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.upload.Description;
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
|
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||||
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contract with with UploadMediaDetails and its presenter would talk to each other
|
* The contract with with UploadMediaDetails and its presenter would talk to each other
|
||||||
|
|
@ -36,13 +34,12 @@ public interface UploadMediaDetailsContract {
|
||||||
|
|
||||||
void showMapWithImageCoordinates(boolean shouldShow);
|
void showMapWithImageCoordinates(boolean shouldShow);
|
||||||
|
|
||||||
void setTitleAndDescription(String title, List<Description> descriptions);
|
void setCaptionsAndDescriptions(List<UploadMediaDetail> uploadMediaDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActionListener extends BasePresenter<View> {
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
void receiveImage(UploadableFile uploadableFile, @Contribution.FileSource String source,
|
void receiveImage(UploadableFile uploadableFile, Place place);
|
||||||
Place place);
|
|
||||||
|
|
||||||
void verifyImageQuality(UploadItem uploadItem);
|
void verifyImageQuality(UploadItem uploadItem);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
|
|
@ -64,16 +64,14 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receives the corresponding uploadable file, processes it and return the view with and uplaod item
|
* Receives the corresponding uploadable file, processes it and return the view with and uplaod item
|
||||||
*
|
* @param uploadableFile
|
||||||
* @param uploadableFile
|
|
||||||
* @param source
|
|
||||||
* @param place
|
* @param place
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receiveImage(UploadableFile uploadableFile, String source, Place place) {
|
public void receiveImage(UploadableFile uploadableFile, Place place) {
|
||||||
view.showProgress(true);
|
view.showProgress(true);
|
||||||
Disposable uploadItemDisposable = repository
|
Disposable uploadItemDisposable = repository
|
||||||
.preProcessImage(uploadableFile, place, source, this)
|
.preProcessImage(uploadableFile, place, this)
|
||||||
.subscribeOn(ioScheduler)
|
.subscribeOn(ioScheduler)
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(uploadItem ->
|
.subscribe(uploadItem ->
|
||||||
|
|
@ -143,7 +141,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches and sets the title and desctiption of the previous item
|
* Fetches and sets the caption and desctiption of the previous item
|
||||||
*
|
*
|
||||||
* @param indexInViewFlipper
|
* @param indexInViewFlipper
|
||||||
*/
|
*/
|
||||||
|
|
@ -151,7 +149,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
public void fetchPreviousTitleAndDescription(int indexInViewFlipper) {
|
public void fetchPreviousTitleAndDescription(int indexInViewFlipper) {
|
||||||
UploadItem previousUploadItem = repository.getPreviousUploadItem(indexInViewFlipper);
|
UploadItem previousUploadItem = repository.getPreviousUploadItem(indexInViewFlipper);
|
||||||
if (null != previousUploadItem) {
|
if (null != previousUploadItem) {
|
||||||
view.setTitleAndDescription(previousUploadItem.getTitle().getTitleText(), previousUploadItem.getDescriptions());
|
view.setCaptionsAndDescriptions(previousUploadItem.getUploadMediaDetails());
|
||||||
} else {
|
} else {
|
||||||
view.showMessage(R.string.previous_image_title_description_not_found, R.color.color_error);
|
view.showMessage(R.string.previous_image_title_description_not_found, R.color.color_error);
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +174,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle images, say empty title, duplicate file name, bad picture(in all other cases)
|
* Handle images, say empty caption, duplicate file name, bad picture(in all other cases)
|
||||||
*
|
*
|
||||||
* @param errorCode
|
* @param errorCode
|
||||||
*/
|
*/
|
||||||
|
|
@ -188,9 +186,9 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case EMPTY_TITLE:
|
case EMPTY_CAPTION:
|
||||||
Timber.d("Title is empty. Showing toast");
|
Timber.d("Captions are empty. Showing toast");
|
||||||
view.showMessage(R.string.add_title_toast, R.color.color_error);
|
view.showMessage(R.string.add_caption_toast, R.color.color_error);
|
||||||
break;
|
break;
|
||||||
case FILE_NAME_EXISTS:
|
case FILE_NAME_EXISTS:
|
||||||
Timber.d("Trying to show duplicate picture popup");
|
Timber.d("Trying to show duplicate picture popup");
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue