diff --git a/.travis.yml b/.travis.yml
index 7c7f76eaa..20c5bfaee 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,14 +17,17 @@ jdk:
android:
components:
- - platform-tools
- tools
- - build-tools-26.0.1
+ - platform-tools
+ - build-tools-26.0.2
- extra-google-m2repository
- extra-android-m2repository
- ${ANDROID_TARGET}
- android-25
+ - android-26
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
+ licenses:
+ - 'android-sdk-license-.+'
before_script:
- echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI
@@ -32,14 +35,16 @@ before_script:
- android-wait-for-emulator
script:
- - ./gradlew clean check connectedCheck jacocoTestReport --stacktrace
+ - ./gradlew clean check connectedCheck jacocoTestReport
after_success:
- bash <(curl -s https://codecov.io/bash)
after_failure:
- - echo '*** Connected Test Rsults ***'
- - w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/*Test.html
+ - echo '*** Debug Unit Test Results ***'
+ - w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/tests/*/classes/*Test.html
+ - echo '*** Connected Test Results ***'
+ - w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/flavors/*/*Test.html
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1de06a4e3..035835839 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,48 @@
# Wikimedia Commons for Android
-## v2.5.0 beta
+## v2.6.7
+- Added null checks to prevent frequent crashes in ModificationsSyncAdapter
+
+## v2.6.6
+- Refactored Dagger to fix crashes encountered in production
+- Fixed "?" displaying in description of Nearby places
+- Database-related cleanup and tests
+- Optimized dimens.xml
+- Fixed issue where map opens with incorrect coordinates
+
+## v2.6.5 beta
+- Changed "send log" feature to only send logs to private Google group forum
+- Switched to using Wikimedia maps server instead of Mapbox for privacy reasons
+- Removed event logging from app for privacy reasons
+- Fixed crash caused by rapidly switching from Nearby map to list while loading
+
+## v2.6.4 beta
+- Excluded httpclient and commons-logging to fix release build errors
+- Fixed crashes caused by Fresco and Dagger
+
+## v2.6.3 beta
+- Same as 2.6.2 except with localizations added for Google Code-In
+
+## v2.6.2 beta
+- Reverted temporarily to last stable version while working on crash fix
+
+## v2.6.1 beta
+- Failed attempt to fix crashes in release build with the previous beta release
+
+## v2.6.0 beta
+- Multiple bugfixes for location updates and list/map loading in Nearby
+- Multiple fixes for various crashes and memory leaks
+- Added several unit tests
+- Modified About page to include WMF disclaimer and modified Privacy Policy link to point to our individual privacy policy
+- Added option for users to send logs to developers (has to be manually activated by user)
+- Converted PNGs to WebPs
+- Improved login screen with new design and privacy policy link
+- Improved category display, if a category has an exact name entered, it will be shown first
+- New UI for Nearby list
+- Added product flavors for production and the beta-cluster Wikimedia servers
+- Various improvements to navigation flow and backstack
+
+## v2.5.0 beta
- Added one-time popup for beta users to provide feedback on IEG renewal proposal
- Added link to Commons policies in ShareActivity
- Various string fixes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..ee7f42e06
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1 @@
+Please see our guidelines in the wiki: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
diff --git a/CREDITS b/CREDITS
index b404302fb..a4a4ae0f5 100644
--- a/CREDITS
+++ b/CREDITS
@@ -29,6 +29,7 @@ their contribution to the product.
* Jan Piotrowski
* Bruke Mekuria Mulugeta
* Paul Hawke
+* Vishan Seru
3rd party open source libraries used:
* Butterknife
@@ -38,3 +39,953 @@ their contribution to the product.
3rd party open source apps from which significant code has been reused:
* Android Wikipedia app https://github.com/wikimedia/apps-android-wikipedia
+
+===========================================================================
+
+The Wikimedia Commons Android app uses portions of MapBox.
+
+mapbox-gl-native copyright (c) 2014-2018 Mapbox.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===========================================================================
+
+Mapbox GL uses portions of Android Gesture Detectors Framework.
+
+Copyright (c) 2012, Almer Thie
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===========================================================================
+
+Mapbox GL uses portions of Android Support Library.
+
+Copyright (c) 2005-2013, The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+===========================================================================
+
+Mapbox GL uses portions of Boost.
+
+Distributed under the Boost Software License, Version 1.0.
+
+http://www.boost.org/LICENSE_1_0.txt
+
+===========================================================================
+
+Mapbox GL uses portions of Clipper.
+
+Author : Angus Johnson
+Version : 6.1.3a
+Date : 22 January 2014
+Website : http://www.angusj.com
+Copyright : Angus Johnson 2010-2014
+
+License:
+Use, modification & distribution is subject to Boost Software License Ver 1.
+http://www.boost.org/LICENSE_1_0.txt
+
+Attributions:
+The code in this library is an extension of Bala Vatti's clipping algorithm:
+"A generic solution to polygon clipping"
+Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63.
+http://portal.acm.org/citation.cfm?id=129906
+
+Computer graphics and geometric modeling: implementation and algorithms
+By Max K. Agoston
+Springer; 1 edition (January 4, 2005)
+http://books.google.com/books?q=vatti+clipping+agoston
+
+See also:
+"Polygon Offsetting by Computing Winding Numbers"
+Paper no. DETC2005-85513 pp. 565-575
+ASME 2005 International Design Engineering Technical Conferences
+and Computers and Information in Engineering Conference (IDETC/CIE2005)
+September 24-28, 2005 , Long Beach, California, USA
+http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf
+
+===========================================================================
+
+Mapbox GL uses portions of BugshotKit.
+
+The MIT License (MIT)
+
+Copyright (c) 2014 marcoarment
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+===========================================================================
+
+Mapbox GL uses portions of CSS Color Parser.
+
+(c) Dean McNamee , 2012.
+C++ port by Konstantin Käfer , 2014.
+
+https://github.com/deanm/css-color-parser-js
+https://github.com/kkaefer/css-color-parser-cpp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+===========================================================================
+
+Mapbox GL uses portions of GLFW.
+
+Copyright (c) 2002-2006 Marcus Geelnard
+Copyright (c) 2006-2010 Camilla Berglund
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would
+ be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not
+ be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+ distribution.
+
+===========================================================================
+
+Mapbox GL uses portions of libc++.
+
+The libc++ library is dual licensed under both the University of Illinois
+"BSD-Like" license and the MIT license. As a user of this code you may choose
+to use it under either license. As a contributor, you agree to allow your code
+to be used under both.
+
+Full text of the relevant licenses is included below.
+
+====
+
+University of Illinois/NCSA
+Open Source License
+
+Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT
+
+All rights reserved.
+
+Developed by:
+
+ LLVM Team
+
+ University of Illinois at Urbana-Champaign
+
+ http://llvm.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal with
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimers.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimers in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the names of the LLVM Team, University of Illinois at
+ Urbana-Champaign, nor the names of its contributors may be used to
+ endorse or promote products derived from this Software without specific
+ prior written permission.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+SOFTWARE.
+
+====
+
+Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+===========================================================================
+
+Mapbox GL uses portions of libcurl.
+
+COPYRIGHT AND PERMISSION NOTICE
+
+Copyright (c) 1996 - 2015, Daniel Stenberg, .
+
+All rights reserved.
+
+Permission to use, copy, modify, and distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright
+notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of a copyright holder shall not
+be used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization of the copyright holder.
+
+===========================================================================
+
+Mapbox GL uses portions of libjpeg-turbo.
+
+This software is based in part on the work of the Independent JPEG Group.
+
+Copyright (C)2009-2015 D. R. Commander. All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+- Neither the name of the libjpeg-turbo Project nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+TurboJPEG/LJT: this implements the TurboJPEG API using libjpeg or libjpeg-turbo
+
+===========================================================================
+
+Mapbox GL uses portions of libpng.
+
+This copy of the libpng notices is provided for your convenience. In case of
+any discrepancy between this copy and the notices in the file png.h that is
+included in the libpng distribution, the latter shall prevail.
+
+COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
+
+If you modify libpng you may insert additional notices immediately following
+this sentence.
+
+This code is released under the libpng license.
+
+libpng versions 1.0.7, July 1, 2000, through 1.6.18, July 23, 2015, are
+Copyright (c) 2000-2002, 2004, 2006-2015 Glenn Randers-Pehrson, and are
+distributed according to the same disclaimer and license as libpng-1.0.6
+with the following individuals added to the list of Contributing Authors:
+
+ Simon-Pierre Cadieux
+ Eric S. Raymond
+ Mans Rullgard
+ Cosmin Truta
+ Gilles Vollant
+ James Yu
+
+and with the following additions to the disclaimer:
+
+ There is no warranty against interference with your enjoyment of the
+ library or against infringement. There is no warranty that our
+ efforts or the library will fulfill any of your particular purposes
+ or needs. This library is provided with all faults, and the entire
+ risk of satisfactory quality, performance, accuracy, and effort is with
+ the user.
+
+libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are
+Copyright (c) 1998-2000 Glenn Randers-Pehrson, and are distributed according
+to the same disclaimer and license as libpng-0.96, with the following
+individuals added to the list of Contributing Authors:
+
+ Tom Lane
+ Glenn Randers-Pehrson
+ Willem van Schaik
+
+libpng versions 0.89, June 1996, through 0.96, May 1997, are
+Copyright (c) 1996-1997 Andreas Dilger, and are
+distributed according to the same disclaimer and license as libpng-0.88,
+with the following individuals added to the list of Contributing Authors:
+
+ John Bowler
+ Kevin Bracey
+ Sam Bushell
+ Magnus Holmgren
+ Greg Roelofs
+ Tom Tanner
+
+libpng versions 0.5, May 1995, through 0.88, January 1996, are
+Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.
+
+For the purposes of this copyright and license, "Contributing Authors"
+is defined as the following set of individuals:
+
+ Andreas Dilger
+ Dave Martindale
+ Guy Eric Schalnat
+ Paul Schmidt
+ Tim Wegner
+
+The PNG Reference Library is supplied "AS IS". The Contributing Authors
+and Group 42, Inc. disclaim all warranties, expressed or implied,
+including, without limitation, the warranties of merchantability and of
+fitness for any purpose. The Contributing Authors and Group 42, Inc.
+assume no liability for direct, indirect, incidental, special, exemplary,
+or consequential damages, which may result from the use of the PNG
+Reference Library, even if advised of the possibility of such damage.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+source code, or portions hereof, for any purpose, without fee, subject
+to the following restrictions:
+
+1. The origin of this source code must not be misrepresented.
+
+2. Altered versions must be plainly marked as such and must not
+ be misrepresented as being the original source.
+
+3. This Copyright notice may not be removed or altered from any
+ source or altered source distribution.
+
+The Contributing Authors and Group 42, Inc. specifically permit, without
+fee, and encourage the use of this source code as a component to
+supporting the PNG file format in commercial products. If you use this
+source code in a product, acknowledgment is not required but would be
+appreciated.
+
+===========================================================================
+
+Mapbox GL uses portions of libuv.
+
+libuv is part of the Node project: http://nodejs.org/
+libuv may be distributed alone under Node's license:
+
+====
+
+Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+====
+
+This license applies to all parts of libuv that are not externally
+maintained libraries.
+
+The externally maintained libraries used by libuv are:
+
+- tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license.
+
+- inet_pton and inet_ntop implementations, contained in src/inet.c, are
+ copyright the Internet Systems Consortium, Inc., and licensed under the ISC
+ license.
+
+- stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three
+ clause BSD license.
+
+- pthread-fixes.h, pthread-fixes.c, copyright Google Inc. and Sony Mobile
+ Communications AB. Three clause BSD license.
+
+- android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design
+ Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement
+ n° 289016). Three clause BSD license.
+
+===========================================================================
+
+Mapbox GL uses portions of libzip.
+
+Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner
+
+The authors can be contacted at
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+3. The names of the authors may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===========================================================================
+
+Mapbox GL uses portions of LOST.
+
+Copyright (c) 2014 Mapzen
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+===========================================================================
+
+Mapbox GL uses portions of the Mapbox iOS SDK, which was derived from the
+Route-Me open source project, including the Alpstein fork of it.
+
+The Route-Me license appears below.
+
+Copyright (c) 2008-2013, Route-Me Contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+===========================================================================
+
+Mapbox GL uses portions of nunicode.
+
+Copyright (c) 2013 Aleksey Tulinov
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+===========================================================================
+
+Mapbox GL uses portions of OkHTTP.
+
+Copyright 2014 Square, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+===========================================================================
+
+Mapbox GL uses portions of OpenSSL.
+
+LICENSE ISSUES
+==============
+
+The OpenSSL toolkit stays under a dual license, i.e. both the conditions of
+the OpenSSL License and the original SSLeay license apply to the toolkit.
+See below for the actual license texts. Actually both licenses are BSD-style
+Open Source licenses. In case of any license issues related to OpenSSL
+please contact openssl-core@openssl.org.
+
+OpenSSL License
+---------------
+
+Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+3. All advertising materials mentioning features or use of this
+ software must display the following acknowledgment:
+ "This product includes software developed by the OpenSSL Project
+ for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+
+4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ endorse or promote products derived from this software without
+ prior written permission. For written permission, please contact
+ openssl-core@openssl.org.
+
+5. Products derived from this software may not be called "OpenSSL"
+ nor may "OpenSSL" appear in their names without prior written
+ permission of the OpenSSL Project.
+
+6. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes software developed by the OpenSSL Project
+ for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+
+THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This product includes cryptographic software written by Eric Young
+(eay@cryptsoft.com). This product includes software written by Tim
+Hudson (tjh@cryptsoft.com).
+
+Original SSLeay License
+-----------------------
+
+Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+All rights reserved.
+
+This package is an SSL implementation written
+by Eric Young (eay@cryptsoft.com).
+The implementation was written so as to conform with Netscapes SSL.
+
+This library is free for commercial and non-commercial use as long as
+The following conditions are aheared to. The following conditions
+apply to all code found in this distribution, be it the RC4, RSA,
+lhash, DES, etc., code; not just the SSL code. The SSL documentation
+included with this distribution is covered by the same copyright terms
+except that the holder is Tim Hudson (tjh@cryptsoft.com).
+
+Copyright remains Eric Young's, and as such any Copyright notices in
+the code are not to be removed.
+If this package is used in a product, Eric Young should be given attribution
+as the author of the parts of the library used.
+This can be in the form of a textual message at program startup or
+in documentation (online or textual) provided with the package.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+ "This product includes cryptographic software written by
+ Eric Young (eay@cryptsoft.com)"
+ The word 'cryptographic' can be left out if the rouines from the library
+ being used are not cryptographic related :-).
+4. If you include any Windows specific code (or a derivative thereof) from
+ the apps directory (application code) you must include an acknowledgement:
+ "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+
+THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+The licence and distribution terms for any publically available version or
+derivative of this code cannot be changed. i.e. this code cannot simply be
+copied and put under another distribution licence
+[including the GNU Public Licence.]
+
+===========================================================================
+
+Mapbox GL uses portions of RapidJSON.
+
+Tencent is pleased to support the open source community by making RapidJSON
+available.
+
+Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights
+reserved.
+
+If you have downloaded a copy of the RapidJSON binary from Tencent, please note
+that the RapidJSON binary is licensed under the MIT License. If you have
+downloaded a copy of the RapidJSON source code from Tencent, please note that
+RapidJSON source code is licensed under the MIT License, except for the third-
+party components listed below which are subject to different license terms.
+Your integration of RapidJSON into your own projects may require compliance with
+the MIT License, as well as the other licenses applicable to the third-party
+components included within RapidJSON. To avoid the problematic JSON license in
+your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as
+it's the only code under the JSON license. A copy of the MIT License is included
+in this file.
+
+Other dependencies and licenses:
+
+Open Source Software Licensed Under the BSD License:
+--------------------------------------------------------------------
+
+The msinttypes r29
+Copyright (c) 2006-2013 Alexander Chemeris
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+* Neither the name of copyright holder nor the names of its contributors may be
+ used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Open Source Software Licensed Under the JSON License:
+--------------------------------------------------------------------
+
+json.org
+Copyright (c) 2002 JSON.org
+All Rights Reserved.
+
+JSON_checker
+Copyright (c) 2002 JSON.org
+All Rights Reserved.
+
+Terms of the JSON License:
+---------------------------------------------------
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Terms of the MIT License:
+--------------------------------------------------------------------
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+===========================================================================
+
+Mapbox GL uses portions of Reachability.
+
+Copyright (c) 2011, Tony Million.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+===========================================================================
+
+Mapbox GL uses portions of SQLite.
+
+2001 September 15
+
+The author disclaims copyright to this source code. In place of
+a legal notice, here is a blessing:
+
+ May you do good and not evil.
+ May you find forgiveness for yourself and forgive others.
+ May you share freely, never taking more than you give.
+
+===========================================================================
+
+Mapbox GL uses portions of SVPulsingAnnotationView.
+
+Copyright (c) 2013, Sam Vermette
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
+
+===========================================================================
+
+Mapbox GL uses portions of zlib.
+
+Acknowledgments:
+
+The deflate format used by zlib was defined by Phil Katz. The deflate and
+zlib specifications were written by L. Peter Deutsch. Thanks to all the
+people who reported problems and suggested various improvements in zlib; they
+are too numerous to cite here.
+
+Copyright notice:
+
+(C) 1995-2013 Jean-loup Gailly and Mark Adler
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+
+ Jean-loup Gailly Mark Adler
+ jloup@gzip.org madler@alumni.caltech.edu
+
+===========================================================================
+
+Mapbox GL uses portions of Realm Objective-C.
+
+Copyright 2015 Realm Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..d8ef17752
--- /dev/null
+++ b/ISSUE_TEMPLATE.md
@@ -0,0 +1,35 @@
+_Before creating an issue, please search the existing issues to see if a similar one has already been created. You can search issues by specific labels (e.g. `label:nearby `) or just by typing keywords into the search filter._
+
+**Summary:**
+
+Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
+
+**Steps to reproduce:**
+
+How can we reproduce the issue?
+
+**Add System logs:**
+
+Add logcat files here (if possible).
+
+**Expected behavior:**
+
+What did you expect the App to do?
+
+**Observed behavior:**
+
+What did you see instead? Describe your issue in detail here.
+
+**Device and Android version:**
+
+What make and model device (e.g., Samsung J7) did you encounter this on? What Android
+version (e.g., Android 4.0 Ice Cream Sandwich or Android 6.0 Marshmallow) are you running? Is it
+ the stock version from the manufacturer or a custom ROM ?
+
+ **Commons app version:**
+
+You can find this information by going to the navigation drawer in the app and tapping 'About'
+
+**Screen-shots:**
+
+Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher.
diff --git a/README.md b/README.md
index 6ac8dc892..696201029 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,7 @@
The Wikimedia Commons Android app allows users to upload pictures from their Android phone/tablet to Wikimedia Commons. Download the app [here][1], or view our [website][2].
-Initially started by the Wikimedia Foundation, this app is now maintained by volunteers. Anyone is welcome to improve it, just choose among the [open issues][3] and send us a pull request :-)
-
-We are currently applying for an [IEG renewal][10] to work on the app for the next 6 months. Feedback is very much welcomed.
+Initially started by the Wikimedia Foundation, this app is now maintained by grantees and volunteers of the Wikimedia community. Anyone is welcome to improve it, just choose among the [open issues][3] and send us a pull request :-)
diff --git a/app/build.gradle b/app/build.gradle
index 56a4694af..98693126e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,58 +1,78 @@
+apply from: '../gitutils.gradle'
apply plugin: 'com.android.application'
-apply plugin: 'me.tatarka.retrolambda'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco-android'
apply from: 'quality.gradle'
apply plugin: 'com.getkeepsafe.dexcount'
dependencies {
- compile 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
- compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
- compile 'in.yuvi:http.fluent:1.3'
- compile 'com.android.volley:volley:1.0.0'
- compile 'ch.acra:acra:4.7.0'
- compile 'org.mediawiki:api:1.3'
- compile 'commons-codec:commons-codec:1.10'
- compile 'com.github.pedrovgs:renderers:3.3.3'
- compile 'com.google.code.gson:gson:2.8.0'
- compile 'com.jakewharton.timber:timber:4.5.1'
- compile 'info.debatty:java-string-similarity:0.24'
- compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.1.0@aar'){
+ implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
+ implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
+ implementation 'in.yuvi:http.fluent:1.3'
+ implementation 'com.android.volley:volley:1.0.0'
+ implementation 'ch.acra:acra:4.7.0'
+ implementation 'org.mediawiki:api:1.3'
+ implementation 'commons-codec:commons-codec:1.10'
+ implementation 'com.github.pedrovgs:renderers:3.3.3'
+ implementation 'com.google.code.gson:gson:2.8.1'
+ implementation 'com.jakewharton.timber:timber:4.5.1'
+ implementation 'info.debatty:java-string-similarity:0.24'
+ implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.2.1@aar'){
transitive=true
}
- compile "com.android.support:support-v4:${project.supportLibVersion}"
- compile "com.android.support:appcompat-v7:${project.supportLibVersion}"
- compile "com.android.support:design:${project.supportLibVersion}"
- compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
- annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
+ implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
+ implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
+ implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
- compile 'com.squareup.okhttp3:okhttp:3.8.1'
- compile 'com.squareup.okio:okio:1.13.0'
+ implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
- compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
+ implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
+ kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
+
+ implementation 'com.squareup.okhttp3:okhttp:3.8.1'
+ implementation 'com.squareup.okio:okio:1.13.0'
+
+ implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
- compile 'io.reactivex.rxjava2:rxjava:2.1.2'
- compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
- compile 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
- compile 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
- compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
+ implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
+ implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
+ implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
+ implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
+ implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
- compile 'com.facebook.fresco:fresco:1.3.0'
- compile 'com.facebook.stetho:stetho:1.5.0'
+ implementation 'com.facebook.fresco:fresco:1.5.0'
+ implementation 'com.facebook.stetho:stetho:1.5.0'
- testCompile 'junit:junit:4.12'
- testCompile 'org.robolectric:robolectric:3.3.2'
+ implementation "com.google.dagger:dagger:$DAGGER_VERSION"
+ implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
- testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
- androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
- androidTestCompile "com.android.support:support-annotations:${project.supportLibVersion}"
- androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
+ kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
+ kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
- debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
- releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
- testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
+ testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+ androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
+
+ testImplementation 'junit:junit:4.12'
+ testImplementation 'org.robolectric:robolectric:3.4'
+ testImplementation 'org.mockito:mockito-all:1.10.19'
+
+ testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
+ androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
+ androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+
+ debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
+ releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
+ testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
+
+ implementation "com.google.dagger:dagger:$DAGGER_VERSION"
+ implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
+ kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
+ kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
}
android {
@@ -63,24 +83,38 @@ android {
defaultConfig {
applicationId 'fr.free.nrw.commons'
- versionCode 74
- versionName '2.5.0'
+ versionCode 82
+ versionName '2.6.7'
+ setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
+
minSdkVersion project.minSdkVersion
targetSdkVersion project.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
+ sourceSets {
+ // use kotlin only in tests (for now)
+ test.java.srcDirs += 'src/test/kotlin'
+
+ // use main assets and resources in test
+ test.assets.srcDirs += 'src/main/assets'
+ test.resources.srcDirs += 'src/main/resoures'
+ }
+
buildTypes {
release {
minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient.
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
debug {
+ applicationIdSuffix ".debug"
testCoverageEnabled true
+ versionNameSuffix "-debug-" + getBranchName() + "~" + getBuildVersion()
}
}
+ flavorDimensions 'tier'
productFlavors {
prod {
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
@@ -92,6 +126,7 @@ android {
buildConfigField "String", "EVENTLOG_WIKI", "\"commonswiki\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\""
+ dimension 'tier'
}
beta {
@@ -105,6 +140,7 @@ android {
buildConfigField "String", "EVENTLOG_WIKI", "\"commonswiki\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""
+ dimension 'tier'
}
}
@@ -122,5 +158,8 @@ android {
//FIXME: Temporary fix for https://github.com/commons-app/apps-android-commons/issues/709
configurations.all {
resolutionStrategy.force 'com.android.support:support-annotations:25.2.0'
+ exclude module: 'httpclient'
+ exclude module: 'commons-logging'
}
+ buildToolsVersion buildToolsVersion
}
diff --git a/app/src/androidTest/java/fr/free/nrw/commons/NearbyActivityTest.java b/app/src/androidTest/java/fr/free/nrw/commons/NearbyActivityTest.java
deleted file mode 100644
index f9ec72569..000000000
--- a/app/src/androidTest/java/fr/free/nrw/commons/NearbyActivityTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package fr.free.nrw.commons;
-
-import android.support.test.espresso.assertion.ViewAssertions;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import fr.free.nrw.commons.nearby.NearbyActivity;
-
-import static android.support.test.espresso.Espresso.onView;
-import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static android.support.test.espresso.matcher.ViewMatchers.withText;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class NearbyActivityTest {
- @Rule
- public final ActivityTestRule nearby =
- new ActivityTestRule<>(NearbyActivity.class);
-
- @Test
- public void testActivityLaunch() {
- onView(withText(R.string.title_activity_nearby))
- .check(ViewAssertions.matches(isDisplayed()));
- }
-}
diff --git a/app/src/androidTest/java/fr/free/nrw/commons/upload/FileUtilsTest.java b/app/src/androidTest/java/fr/free/nrw/commons/upload/FileUtilsTest.java
new file mode 100644
index 000000000..d2db4614f
--- /dev/null
+++ b/app/src/androidTest/java/fr/free/nrw/commons/upload/FileUtilsTest.java
@@ -0,0 +1,30 @@
+package fr.free.nrw.commons.upload;
+
+import android.net.Uri;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import fr.free.nrw.commons.BuildConfig;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(AndroidJUnit4.class)
+public class FileUtilsTest {
+ @Test
+ public void isSelfOwned() throws Exception {
+ Uri uri = Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".provider/document/1");
+ boolean selfOwned = FileUtils.isSelfOwned(InstrumentationRegistry.getTargetContext(), uri);
+ assertThat(selfOwned, is(true));
+ }
+
+ @Test
+ public void isNotSelfOwned() throws Exception {
+ Uri uri = Uri.parse("content://com.android.providers.media.documents/document/1");
+ boolean selfOwned = FileUtils.isSelfOwned(InstrumentationRegistry.getTargetContext(), uri);
+ assertThat(selfOwned, is(false));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 882e1fb13..253bdaea8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,24 +2,28 @@
package="fr.free.nrw.commons">
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
-
+
+
+
+ android:label="@string/app_name">
@@ -51,11 +51,11 @@
+
+ android:name=".upload.MultipleShareActivity"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name">
@@ -65,33 +65,38 @@
-
+ android:name=".contributions.ContributionsActivity"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" />
+
+ android:label="@string/title_activity_settings" />
+
+
+ android:label="@string/title_activity_signup" />
+
-
-
+
+
+
+
+ android:process=":auth">
@@ -102,27 +107,25 @@
+ android:name=".contributions.ContributionsSyncService"
+ android:exported="true">
-
+
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/contributions_sync_adapter" />
+ android:name=".modifications.ModificationsSyncService"
+ android:exported="true">
-
+
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/modifications_sync_adapter" />
+ android:resource="@xml/provider_paths" />
-
+ android:name=".contributions.ContributionsContentProvider"
+ android:authorities="fr.free.nrw.commons.contributions.contentprovider"
+ android:exported="false"
+ android:label="@string/provider_contributions"
+ android:syncable="true" />
-
+ android:name=".modifications.ModificationsContentProvider"
+ android:authorities="fr.free.nrw.commons.modifications.contentprovider"
+ android:exported="false"
+ android:label="@string/provider_modifications"
+ android:syncable="true" />
+
-
+ android:name=".category.CategoryContentProvider"
+ android:authorities="fr.free.nrw.commons.categories.contentprovider"
+ android:exported="false"
+ android:label="@string/provider_categories"
+ android:syncable="false" />
diff --git a/app/src/main/assets/mapstyle.json b/app/src/main/assets/mapstyle.json
new file mode 100644
index 000000000..d4291cbdc
--- /dev/null
+++ b/app/src/main/assets/mapstyle.json
@@ -0,0 +1,26 @@
+{
+ "version": 8,
+ "sources": {
+ "wikimedia-osm": {
+ "type": "raster",
+ "tiles": [
+ "https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png"
+ ],
+ "tileSize": 128
+ }
+ },
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#606060"
+ }
+ },
+ {
+ "id": "osm",
+ "type": "raster",
+ "source": "wikimedia-osm"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java
index 96c1cf200..a2f67a3bf 100644
--- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java
@@ -1,19 +1,29 @@
package fr.free.nrw.commons;
-import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
+import android.view.View;
import android.widget.TextView;
import butterknife.BindView;
import butterknife.ButterKnife;
+import butterknife.OnClick;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.ui.widget.HtmlTextView;
+/**
+ * Represents about screen of this app
+ */
public class AboutActivity extends NavigationBaseActivity {
@BindView(R.id.about_version) TextView versionText;
@BindView(R.id.about_license) HtmlTextView aboutLicenseText;
+ /**
+ * This method helps in the creation About screen
+ *
+ * @param savedInstanceState Data bundle
+ */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -21,15 +31,38 @@ public class AboutActivity extends NavigationBaseActivity {
ButterKnife.bind(this);
- String aboutText = getString(R.string.about_license, getString(R.string.trademarked_name));
+ String aboutText = getString(R.string.about_license);
aboutLicenseText.setHtmlText(aboutText);
versionText.setText(BuildConfig.VERSION_NAME);
initDrawer();
}
- public static void startYourself(Context context) {
- Intent settingsIntent = new Intent(context, AboutActivity.class);
- context.startActivity(settingsIntent);
+ @OnClick(R.id.facebook_launch_icon)
+ public void launchFacebook(View view) {
+
+ Intent intent;
+ try {
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse("fb://page/" + "1921335171459985"));
+ intent.setPackage("com.facebook.katana");
+ startActivity(intent);
+ } catch (Exception e) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.facebook.com/" + "1921335171459985")));
+ }
+
+ }
+
+ @OnClick(R.id.github_launch_icon)
+ public void launchGithub(View view) {
+
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/commons-app/apps-android-commons\\"));
+ startActivity(browserIntent);
+ }
+
+ @OnClick(R.id.website_launch_icon)
+ public void launchWebsite(View view) {
+
+ Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://commons-app.github.io/\\"));
+ startActivity(browserIntent);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
index 62f5ec1a9..ab156bab7 100644
--- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
+++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.java
@@ -1,40 +1,34 @@
package fr.free.nrw.commons;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerCallback;
-import android.accounts.AccountManagerFuture;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
-import android.preference.PreferenceManager;
-import android.support.v4.util.LruCache;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.stetho.Stetho;
import com.squareup.leakcanary.LeakCanary;
+import com.squareup.leakcanary.RefWatcher;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import java.io.File;
-import java.io.IOException;
-import fr.free.nrw.commons.auth.AccountUtil;
-import fr.free.nrw.commons.caching.CacheController;
-import fr.free.nrw.commons.contributions.Contribution;
-import fr.free.nrw.commons.data.Category;
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.category.CategoryDao;
+import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper;
-import fr.free.nrw.commons.modifications.ModifierSequence;
-import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
-import fr.free.nrw.commons.mwapi.MediaWikiApi;
-import fr.free.nrw.commons.nearby.NearbyPlaces;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
+import fr.free.nrw.commons.di.CommonsApplicationComponent;
+import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.utils.FileUtils;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
// TODO: Use ProGuard to rip out reporting when publishing
@@ -48,86 +42,43 @@ import timber.log.Timber;
)
public class CommonsApplication extends Application {
- private Account currentAccount = null; // Unlike a savings account...
+ @Inject SessionManager sessionManager;
+ @Inject DBOpenHelper dbOpenHelper;
- public static final Object[] EVENT_UPLOAD_ATTEMPT = {"MobileAppUploadAttempts", 5334329L};
- public static final Object[] EVENT_LOGIN_ATTEMPT = {"MobileAppLoginAttempts", 5257721L};
- public static final Object[] EVENT_SHARE_ATTEMPT = {"MobileAppShareAttempts", 5346170L};
- public static final Object[] EVENT_CATEGORIZATION_ATTEMPT = {"MobileAppCategorizationAttempts", 5359208L};
+ @Inject @Named("default_preferences") SharedPreferences defaultPrefs;
+ @Inject @Named("application_preferences") SharedPreferences applicationPrefs;
+ @Inject @Named("prefs") SharedPreferences otherPrefs;
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using Android Commons app";
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
+
+ public static final String LOGS_PRIVATE_EMAIL = "commons-app-android-private@googlegroups.com";
+
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
- private static CommonsApplication instance = null;
- private MediaWikiApi api = null;
- private LruCache thumbnailUrlCache = new LruCache<>(1024);
- private CacheController cacheData = null;
- private DBOpenHelper dbOpenHelper = null;
- private NearbyPlaces nearbyPlaces = null;
+ private RefWatcher refWatcher;
+
/**
- * This should not be called by ANY application code (other than the magic Android glue)
- * Use CommonsApplication.getInstance() instead to get the singleton.
+ * Used to declare and initialize various components and dependencies
*/
- public CommonsApplication() {
- CommonsApplication.instance = this;
- }
-
- public static CommonsApplication getInstance() {
- if (instance == null) {
- instance = new CommonsApplication();
- }
- return instance;
- }
-
- public MediaWikiApi getMWApi() {
- if (api == null) {
- api = new ApacheHttpClientMediaWikiApi(BuildConfig.WIKIMEDIA_API_HOST);
- }
- return api;
- }
-
- public CacheController getCacheData() {
- if (cacheData == null) {
- cacheData = new CacheController();
- }
- return cacheData;
- }
-
- public LruCache getThumbnailUrlCache() {
- return thumbnailUrlCache;
- }
-
- public synchronized DBOpenHelper getDBOpenHelper() {
- if (dbOpenHelper == null) {
- dbOpenHelper = new DBOpenHelper(this);
- }
- return dbOpenHelper;
- }
-
- public synchronized NearbyPlaces getNearbyPlaces() {
- if (nearbyPlaces == null) {
- nearbyPlaces = new NearbyPlaces();
- }
- return nearbyPlaces;
- }
-
@Override
public void onCreate() {
super.onCreate();
- if (LeakCanary.isInAnalyzerProcess(this)) {
- // This process is dedicated to LeakCanary for heap analysis.
- // You should not init your app in this process.
+
+ ApplicationlessInjection
+ .getInstance(this)
+ .getCommonsApplicationComponent()
+ .inject(this);
+
+ Fresco.initialize(this);
+ if (setupLeakCanary() == RefWatcher.DISABLED) {
return;
}
- LeakCanary.install(this);
Timber.plant(new Timber.DebugTree());
-
-
if (!BuildConfig.DEBUG) {
ACRA.init(this);
} else {
@@ -136,52 +87,36 @@ public class CommonsApplication extends Application {
// Fire progress callbacks for every 3% of uploaded content
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
+ }
- Fresco.initialize(this);
- //For caching area -> categories
- cacheData = new CacheController();
+ /**
+ * Helps in setting up LeakCanary library
+ * @return instance of LeakCanary
+ */
+ protected RefWatcher setupLeakCanary() {
+ if (LeakCanary.isInAnalyzerProcess(this)) {
+ return RefWatcher.DISABLED;
+ }
+ return LeakCanary.install(this);
+ }
+
+ /**
+ * Provides a way to get member refWatcher
+ *
+ * @param context Application context
+ * @return application member refWatcher
+ */
+ public static RefWatcher getRefWatcher(Context context) {
+ CommonsApplication application = (CommonsApplication) context.getApplicationContext();
+ return application.refWatcher;
}
/**
- * @return Account|null
+ * clears data of current application
+ * @param context Application context
+ * @param logoutListener Implementation of interface LogoutListener
*/
- public Account getCurrentAccount() {
- if (currentAccount == null) {
- AccountManager accountManager = AccountManager.get(this);
- Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
- if (allAccounts.length != 0) {
- currentAccount = allAccounts[0];
- }
- }
- return currentAccount;
- }
-
- public Boolean revalidateAuthToken() {
- AccountManager accountManager = AccountManager.get(this);
- Account curAccount = getCurrentAccount();
-
- if (curAccount == null) {
- return false; // This should never happen
- }
-
- accountManager.invalidateAuthToken(AccountUtil.accountType(), getMWApi().getAuthCookie());
- try {
- String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
- getMWApi().setAuthCookie(authCookie);
- return true;
- } catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) {
- e.printStackTrace();
- return false;
- }
- }
-
- public boolean deviceHasCamera() {
- PackageManager pm = getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
- pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
- }
-
public void clearApplicationData(Context context, LogoutListener logoutListener) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
@@ -194,70 +129,37 @@ public class CommonsApplication extends Application {
}
}
- AccountManager accountManager = AccountManager.get(this);
- Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
-
- AccountManagerCallback amCallback = new AccountManagerCallback() {
-
- private int index = 0;
-
- void setIndex(int index) {
- this.index = index;
- }
-
- int getIndex() {
- return index;
- }
-
- @Override
- public void run(AccountManagerFuture accountManagerFuture) {
- setIndex(getIndex() + 1);
-
- try {
- if (accountManagerFuture != null && accountManagerFuture.getResult()) {
- Timber.d("Account removed successfully.");
- }
- } catch (OperationCanceledException | IOException | AuthenticatorException e) {
- e.printStackTrace();
- }
-
- if (getIndex() == allAccounts.length) {
+ sessionManager.clearAllAccounts()
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> {
Timber.d("All accounts have been removed");
//TODO: fix preference manager
- PreferenceManager.getDefaultSharedPreferences(getInstance())
- .edit().clear().commit();
- SharedPreferences preferences = context
- .getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
- preferences.edit().clear().commit();
- context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
- .edit().clear().commit();
- preferences.edit().putBoolean("firstrun", false).apply();
+ defaultPrefs.edit().clear().apply();
+ applicationPrefs.edit().clear().apply();
+ applicationPrefs.edit().putBoolean("firstrun", false).apply();
+ otherPrefs.edit().clear().apply();
updateAllDatabases();
- currentAccount = null;
logoutListener.onLogoutComplete();
- }
- }
- };
-
- for (Account account : allAccounts) {
- accountManager.removeAccount(account, amCallback, null);
- }
+ });
}
/**
* Deletes all tables and re-creates them.
*/
- public void updateAllDatabases() {
- DBOpenHelper dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
+ private void updateAllDatabases() {
dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
- ModifierSequence.Table.onDelete(db);
- Category.Table.onDelete(db);
- Contribution.Table.onDelete(db);
+ ModifierSequenceDao.Table.onDelete(db);
+ CategoryDao.Table.onDelete(db);
+ ContributionDao.Table.onDelete(db);
}
+ /**
+ * Interface used to get log-out events
+ */
public interface LogoutListener {
void onLogoutComplete();
}
diff --git a/app/src/main/java/fr/free/nrw/commons/HandlerService.java b/app/src/main/java/fr/free/nrw/commons/HandlerService.java
index 61fa1f1c5..e5e1b3b1b 100644
--- a/app/src/main/java/fr/free/nrw/commons/HandlerService.java
+++ b/app/src/main/java/fr/free/nrw/commons/HandlerService.java
@@ -1,6 +1,5 @@
package fr.free.nrw.commons;
-import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
@@ -9,7 +8,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-public abstract class HandlerService extends Service {
+import fr.free.nrw.commons.di.CommonsDaggerService;
+
+public abstract class HandlerService extends CommonsDaggerService {
private volatile Looper threadLooper;
private volatile ServiceHandler threadHandler;
private String serviceName;
diff --git a/app/src/main/java/fr/free/nrw/commons/License.java b/app/src/main/java/fr/free/nrw/commons/License.java
index 797e0f50c..db893de16 100644
--- a/app/src/main/java/fr/free/nrw/commons/License.java
+++ b/app/src/main/java/fr/free/nrw/commons/License.java
@@ -2,12 +2,25 @@ package fr.free.nrw.commons;
import android.support.annotation.Nullable;
+/**
+ * represents Licence object
+ */
public class License {
private String key;
private String template;
private String url;
private String name;
+ /**
+ * Constructs a new instance of License.
+ *
+ * @param key license key
+ * @param template license template
+ * @param url license URL
+ * @param name licence name
+ *
+ * @throws RuntimeException if License.key or Licence.template is null
+ */
public License(String key, String template, String url, String name) {
if (key == null) {
throw new RuntimeException("License.key must not be null");
@@ -21,10 +34,18 @@ public class License {
this.name = name;
}
+ /**
+ * Gets the license key.
+ * @return license key as a String.
+ */
public String getKey() {
return key;
}
+ /**
+ * Gets the license template.
+ * @return license template as a String.
+ */
public String getTemplate() {
return template;
}
@@ -38,6 +59,12 @@ public class License {
}
}
+ /**
+ * Gets the license URL
+ *
+ * @param language license language
+ * @return URL
+ */
public @Nullable String getUrl(String language) {
if (url == null) {
return null;
diff --git a/app/src/main/java/fr/free/nrw/commons/LicenseList.java b/app/src/main/java/fr/free/nrw/commons/LicenseList.java
index 382ceee3f..d08e314cc 100644
--- a/app/src/main/java/fr/free/nrw/commons/LicenseList.java
+++ b/app/src/main/java/fr/free/nrw/commons/LicenseList.java
@@ -5,21 +5,31 @@ import android.content.res.Resources;
import android.support.annotation.Nullable;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+/**
+ * Represents a list of Licenses
+ */
public class LicenseList {
private Map licenses = new HashMap<>();
private Resources res;
+ /**
+ * Constructs new instance of LicenceList
+ *
+ * @param activity License activity
+ */
public LicenseList(Activity activity) {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
String namespace = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
- while (Utils.xmlFastForward(parser, namespace, "license")) {
+ while (xmlFastForward(parser, namespace, "license")) {
String id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url");
@@ -29,14 +39,28 @@ public class LicenseList {
}
}
+ /**
+ * Gets a collection of licenses
+ * @return License values
+ */
public Collection values() {
return licenses.values();
}
+ /**
+ * Gets license
+ * @param key License key
+ * @return License that matches key
+ */
public License get(String key) {
return licenses.get(key);
}
+ /**
+ * Creates a license from template
+ * @param template License template
+ * @return null
+ */
@Nullable
License licenseForTemplate(String template) {
String ucTemplate = new PageTitle(template).getDisplayText();
@@ -48,6 +72,11 @@ public class LicenseList {
return null;
}
+ /**
+ * Gets template name id
+ * @param template License template
+ * @return name id of template
+ */
private String nameIdForTemplate(String template) {
// hack :D (converts dashes and periods to underscores)
// cc-by-sa-3.0 -> cc_by_sa_3_0
@@ -55,9 +84,44 @@ public class LicenseList {
"_").replace(".", "_");
}
+ /**
+ * Gets name of given template
+ * @param template License template
+ * @return name of template
+ */
private String nameForTemplate(String template) {
int nameId = res.getIdentifier("fr.free.nrw.commons:string/"
+ nameIdForTemplate(template), null, null);
return (nameId != 0) ? res.getString(nameId) : template;
}
-}
\ No newline at end of file
+
+ /**
+ * Fast-forward an XmlPullParser to the next instance of the given element
+ * in the input stream (namespaced).
+ *
+ * @param parser
+ * @param namespace
+ * @param element
+ * @return true on match, false on failure
+ */
+ private boolean xmlFastForward(XmlPullParser parser, String namespace, String element) {
+ try {
+ while (parser.next() != XmlPullParser.END_DOCUMENT) {
+ if (parser.getEventType() == XmlPullParser.START_TAG &&
+ parser.getNamespace().equals(namespace) &&
+ parser.getName().equals(element)) {
+ // We found it!
+ return true;
+ }
+ }
+ return false;
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ return false;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/Media.java b/app/src/main/java/fr/free/nrw/commons/Media.java
index 26a1d038b..726d787f3 100644
--- a/app/src/main/java/fr/free/nrw/commons/Media.java
+++ b/app/src/main/java/fr/free/nrw/commons/Media.java
@@ -47,16 +47,35 @@ public class Media implements Parcelable {
private HashMap tags = new HashMap<>();
private @Nullable LatLng coordinates;
+ /**
+ * Provides local constructor
+ */
protected Media() {
this.categories = new ArrayList<>();
this.descriptions = new HashMap<>();
}
+ /**
+ * Provides a minimal constructor
+ *
+ * @param filename Media filename
+ */
public Media(String filename) {
this();
this.filename = filename;
}
+ /**
+ * Provide Media constructor
+ * @param localUri Media URI
+ * @param imageUrl Media image URL
+ * @param filename Media filename
+ * @param description Media description
+ * @param dataLength Media date length
+ * @param dateCreated Media creation date
+ * @param dateUploaded Media date uploaded
+ * @param creator Media creator
+ */
public Media(Uri localUri, String imageUrl, String filename, String description,
long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
this();
@@ -90,19 +109,33 @@ public class Media implements Parcelable {
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
+ /**
+ * 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, Object value) {
tags.put(key, value);
}
+ /**
+ * Gets media display title
+ * @return Media title
+ */
public String getDisplayTitle() {
if (filename == null) {
return "";
}
- // FIXME: Gross hack bercause my regex skills suck maybe or I am too lazy who knows
+ // FIXME: Gross hack because my regex skills suck maybe or I am too lazy who knows
String title = getFilePageTitle().getDisplayText().replaceFirst("^File:", "");
Matcher matcher = displayTitlePattern.matcher(title);
if (matcher.matches()) {
@@ -112,109 +145,215 @@ public class Media implements Parcelable {
}
}
+ /**
+ * Gets file page title
+ * @return New media page title
+ */
public PageTitle getFilePageTitle() {
return new PageTitle("File:" + getFilename().replaceFirst("^File:", ""));
}
+ /**
+ * Gets local URI
+ * @return Media local URI
+ */
public Uri getLocalUri() {
return localUri;
}
+ /**
+ * Gets image URL
+ * can be null.
+ * @return Image URL
+ */
+ @Nullable
public String getImageUrl() {
- if (imageUrl == null) {
+ if (imageUrl == null && this.getFilename() != null) {
imageUrl = Utils.makeThumbBaseUrl(this.getFilename());
}
return imageUrl;
}
+ /**
+ * Gets the name of the file.
+ * @return file name as a string
+ */
public String getFilename() {
return filename;
}
+ /**
+ * Sets the name of the file.
+ * @param filename the new name of the file
+ */
public void setFilename(String filename) {
this.filename = filename;
}
+ /**
+ * Gets the file description.
+ * @return file description as a string
+ */
public String getDescription() {
return description;
}
+ /**
+ * Sets the file description.
+ * @param description the new description of the file
+ */
public void setDescription(String description) {
this.description = description;
}
+ /**
+ * Gets the datalength of the file.
+ * @return file datalength as a long
+ */
public long getDataLength() {
return dataLength;
}
+ /**
+ * Sets the datalength of the file.
+ * @param dataLength as a long
+ */
public void setDataLength(long dataLength) {
this.dataLength = dataLength;
}
+ /**
+ * Gets the creation date of the file.
+ * @return creation date as a Date
+ */
public Date getDateCreated() {
return dateCreated;
}
+ /**
+ * Sets the creation date of the file.
+ * @param date creation date as a Date
+ */
public void setDateCreated(Date date) {
this.dateCreated = date;
}
+ /**
+ * Gets the upload date of the file.
+ * Can be null.
+ * @return upload date as a Date
+ */
public @Nullable
Date getDateUploaded() {
return dateUploaded;
}
+ /**
+ * Gets the name of the creator of the file.
+ * @return creator name as a String
+ */
public String getCreator() {
return creator;
}
+ /**
+ * Sets the creator name of the file.
+ * @param creator creator name as a string
+ */
public void setCreator(String 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.
+ * @return license as a String
+ */
public String getLicense() {
return license;
}
+ /**
+ * Sets the license name of the file.
+ * @param license license name as a String
+ */
public void setLicense(String license) {
this.license = license;
}
+ /**
+ * Gets the coordinates of where the file was created.
+ * @return file coordinates as a LatLng
+ */
public @Nullable
LatLng getCoordinates() {
return coordinates;
}
+ /**
+ * Sets the coordinates of where the file was created.
+ * @param coordinates file coordinates as a LatLng
+ */
public void setCoordinates(@Nullable LatLng coordinates) {
this.coordinates = coordinates;
}
+ /**
+ * Gets the categories the file falls under.
+ * @return file categories as an ArrayList of Strings
+ */
@SuppressWarnings("unchecked")
public ArrayList getCategories() {
return (ArrayList) categories.clone(); // feels dirty
}
+ /**
+ * Sets the categories the file falls under.
+ *
+ * Does not append: i.e. will clear the current categories
+ * and then add the specified ones.
+ * @param categories file categories as a list of Strings
+ */
public void setCategories(List categories) {
this.categories.clear();
this.categories.addAll(categories);
}
+ /**
+ * Modifies (or sets) media descriptions
+ * @param descriptions Media descriptions
+ */
void setDescriptions(Map descriptions) {
for (String key : this.descriptions.keySet()) {
this.descriptions.remove(key);
@@ -224,6 +363,11 @@ public class Media implements Parcelable {
}
}
+ /**
+ * 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.
@@ -240,11 +384,21 @@ public class Media implements Parcelable {
}
}
+ /**
+ * Method of Parcelable interface
+ * @return zero
+ */
@Override
public int describeContents() {
return 0;
}
+ /**
+ * Creates a way to transfer information between two or more
+ * activities.
+ * @param parcel Instance of Parcel
+ * @param flags Parcel flag
+ */
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(localUri, flags);
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
index 5e8ad7386..25e778b74 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaDataExtractor.java
@@ -11,12 +11,12 @@ import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.inject.Inject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@@ -33,46 +33,39 @@ import timber.log.Timber;
* which are not intrinsic to the media and may change due to editing.
*/
public class MediaDataExtractor {
+ private final MediaWikiApi mediaWikiApi;
private boolean fetched;
-
- private String filename;
private ArrayList categories;
private Map descriptions;
- private Date date;
private String license;
private @Nullable LatLng coordinates;
- private LicenseList licenseList;
- /**
- * @param filename of the target media object, should include 'File:' prefix
- */
- public MediaDataExtractor(String filename, LicenseList licenseList) {
- this.filename = filename;
- categories = new ArrayList<>();
- descriptions = new HashMap<>();
- fetched = false;
- this.licenseList = licenseList;
+ @Inject
+ public MediaDataExtractor(MediaWikiApi mwApi) {
+ this.categories = new ArrayList<>();
+ this.descriptions = new HashMap<>();
+ this.fetched = false;
+ this.mediaWikiApi = mwApi;
}
- /**
+ /*
* Actually fetch the data over the network.
* todo: use local caching?
*
* Warning: synchronous i/o, call on a background thread
*/
- public void fetch() throws IOException {
+ public void fetch(String filename, LicenseList licenseList) throws IOException {
if (fetched) {
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
}
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- MediaResult result = api.fetchMediaByFilename(filename);
+ MediaResult result = mediaWikiApi.fetchMediaByFilename(filename);
// In-page category links are extracted from source, as XML doesn't cover [[links]]
extractCategories(result.getWikiSource());
// Description template info is extracted from preprocessor XML
- processWikiParseTree(result.getParseTreeXmlSource());
+ processWikiParseTree(result.getParseTreeXmlSource(), licenseList);
fetched = true;
}
@@ -91,7 +84,7 @@ public class MediaDataExtractor {
}
}
- private void processWikiParseTree(String source) throws IOException {
+ private void processWikiParseTree(String source, LicenseList licenseList) throws IOException {
Document doc;
try {
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
@@ -153,7 +146,7 @@ public class MediaDataExtractor {
}
private Node findTemplate(Element parentNode, String title_) throws IOException {
- String title= new PageTitle(title_).getDisplayText();
+ String title = new PageTitle(title_).getDisplayText();
NodeList nodes = parentNode.getChildNodes();
for (int i = 0, length = nodes.getLength(); i < length; i++) {
Node node = nodes.item(i);
@@ -179,7 +172,7 @@ public class MediaDataExtractor {
}
private static abstract class TemplateChildNodeComparator {
- abstract public boolean match(Node node);
+ public abstract boolean match(Node node);
}
private Node findTemplateParameter(Node templateNode, String name) throws IOException {
@@ -290,7 +283,7 @@ public class MediaDataExtractor {
/**
* Take our metadata and inject it into a live Media object.
* Media object might contain stale or cached data, or emptiness.
- * @param media
+ * @param media Media object to inject into
*/
public void fill(Media media) {
if (!fetched) {
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java b/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java
index 5ccc80c06..a542cb363 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaThumbnailFetchTask.java
@@ -7,16 +7,17 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
class MediaThumbnailFetchTask extends AsyncTask {
protected final Media media;
+ private MediaWikiApi mediaWikiApi;
- public MediaThumbnailFetchTask(@NonNull Media media) {
+ public MediaThumbnailFetchTask(@NonNull Media media, MediaWikiApi mwApi) {
this.media = media;
+ this.mediaWikiApi = mwApi;
}
@Override
protected String doInBackground(String... params) {
try {
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- return api.findThumbnailByFilename(params[0]);
+ return mediaWikiApi.findThumbnailByFilename(params[0]);
} catch (Exception e) {
// Do something better!
}
diff --git a/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java b/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java
index 3e147f4a8..35d197782 100644
--- a/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java
+++ b/app/src/main/java/fr/free/nrw/commons/MediaWikiImageView.java
@@ -4,6 +4,7 @@ import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.graphics.drawable.VectorDrawableCompat;
+import android.support.v4.util.LruCache;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.Toast;
@@ -11,9 +12,16 @@ import android.widget.Toast;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.di.ApplicationlessInjection;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
public class MediaWikiImageView extends SimpleDraweeView {
+ @Inject MediaWikiApi mwApi;
+ @Inject LruCache thumbnailUrlCache;
+
private ThumbnailFetchTask currentThumbnailTask;
public MediaWikiImageView(Context context) {
@@ -31,19 +39,23 @@ public class MediaWikiImageView extends SimpleDraweeView {
init();
}
+ /**
+ * Sets the media. Fetches its thumbnail if necessary.
+ * @param media the new media
+ */
public void setMedia(Media media) {
if (currentThumbnailTask != null) {
currentThumbnailTask.cancel(true);
}
- if(media == null) {
+ if (media == null) {
return;
}
- if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) {
- setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()));
+ if (thumbnailUrlCache.get(media.getFilename()) != null) {
+ setImageUrl(thumbnailUrlCache.get(media.getFilename()));
} else {
setImageUrl(null);
- currentThumbnailTask = new ThumbnailFetchTask(media);
+ currentThumbnailTask = new ThumbnailFetchTask(media, mwApi);
currentThumbnailTask.execute(media.getFilename());
}
}
@@ -56,7 +68,15 @@ public class MediaWikiImageView extends SimpleDraweeView {
super.onDetachedFromWindow();
}
+ /**
+ * Initializes MediaWikiImageView.
+ */
private void init() {
+ ApplicationlessInjection
+ .getInstance(getContext()
+ .getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
@@ -66,13 +86,17 @@ public class MediaWikiImageView extends SimpleDraweeView {
.build());
}
+ /**
+ * Displays the image from the URL.
+ * @param url the URL of the image
+ */
private void setImageUrl(@Nullable String url) {
setImageURI(url);
}
private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
- ThumbnailFetchTask(@NonNull Media media) {
- super(media);
+ ThumbnailFetchTask(@NonNull Media media, @NonNull MediaWikiApi mwApi) {
+ super(media, mwApi);
}
@Override
@@ -85,7 +109,7 @@ public class MediaWikiImageView extends SimpleDraweeView {
} else {
// only cache meaningful thumbnails received from network.
try {
- CommonsApplication.getInstance().getThumbnailUrlCache().put(media.getFilename(), result);
+ thumbnailUrlCache.put(media.getFilename(), result);
} catch (NullPointerException npe) {
Timber.e("error when adding pic to cache " + npe);
diff --git a/app/src/main/java/fr/free/nrw/commons/PageTitle.java b/app/src/main/java/fr/free/nrw/commons/PageTitle.java
index 6229e7ef9..c4c52b1fc 100644
--- a/app/src/main/java/fr/free/nrw/commons/PageTitle.java
+++ b/app/src/main/java/fr/free/nrw/commons/PageTitle.java
@@ -84,6 +84,12 @@ public class PageTitle {
return titleKey;
}
+ /**
+ * Gets the canonicalized title for displaying (such as "File:My example.jpg").
+ *
+ * Essentially equivalent to getPrefixedText
+ * @return canonical title as a String
+ */
@Override
public String toString() {
return getPrefixedText();
diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java
index 0ccdaf724..817c39f24 100644
--- a/app/src/main/java/fr/free/nrw/commons/Utils.java
+++ b/app/src/main/java/fr/free/nrw/commons/Utils.java
@@ -1,99 +1,26 @@
package fr.free.nrw.commons;
import android.content.Context;
-import android.os.Build;
import android.preference.PreferenceManager;
-import android.text.Html;
-import android.text.Spanned;
+import android.support.annotation.NonNull;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
-import org.w3c.dom.Node;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import java.io.BufferedInputStream;
+import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringWriter;
+import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
-import java.math.BigInteger;
import java.net.URLEncoder;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerConfigurationException;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class Utils {
- // Get SHA1 of file from input stream
- public static String getSHA1(InputStream is) {
-
- MessageDigest digest;
- try {
- digest = MessageDigest.getInstance("SHA1");
- } catch (NoSuchAlgorithmException e) {
- Timber.e(e, "Exception while getting Digest");
- return "";
- }
-
- byte[] buffer = new byte[8192];
- int read;
- try {
- while ((read = is.read(buffer)) > 0) {
- digest.update(buffer, 0, read);
- }
- byte[] md5sum = digest.digest();
- BigInteger bigInt = new BigInteger(1, md5sum);
- String output = bigInt.toString(16);
- // Fill to 40 chars
- output = String.format("%40s", output).replace(' ', '0');
- Timber.i("File SHA1: %s", output);
-
- return output;
- } catch (IOException e) {
- Timber.e(e, "IO Exception");
- return "";
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- Timber.e(e, "Exception on closing MD5 input stream");
- }
- }
- }
-
- /**
- * Fix Html.fromHtml is deprecated problem
- *
- * @param source provided Html string
- * @return returned Spanned of appropriate method according to version check
- */
- public static Spanned fromHtml(String source) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
- } else {
- //noinspection deprecation
- return Html.fromHtml(source);
- }
- }
-
/**
* Strips localization symbols from a string.
* Removes the suffix after "@" and quotes.
@@ -110,49 +37,23 @@ public class Utils {
}
}
- public static Date parseMWDate(String mwDate) {
- SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
- try {
- return isoFormat.parse(mwDate);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
-
- public static String toMWDate(Date date) {
- SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
- isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- return isoFormat.format(date);
- }
-
- public static String makeThumbBaseUrl(String filename) {
+ /**
+ * Creates an URL for thumbnail
+ *
+ * @param filename Thumbnail file name
+ * @return URL of thumbnail
+ */
+ public static String makeThumbBaseUrl(@NonNull String filename) {
String name = new PageTitle(filename).getPrefixedText();
String sha = new String(Hex.encodeHex(DigestUtils.md5(name)));
return String.format("%s/%s/%s/%s", BuildConfig.IMAGE_URL_BASE, sha.substring(0, 1), sha.substring(0, 2), urlEncode(name));
}
- public static String getStringFromDOM(Node dom) {
- Transformer transformer = null;
- try {
- transformer = TransformerFactory.newInstance().newTransformer();
- } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- StringWriter outputStream = new StringWriter();
- DOMSource domSource = new DOMSource(dom);
- StreamResult strResult = new StreamResult(outputStream);
-
- try {
- transformer.transform(domSource, strResult);
- } catch (TransformerException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return outputStream.toString();
- }
-
+ /**
+ * URL Encode an URL in UTF-8 format
+ * @param url Unformatted URL
+ * @return Encoded URL
+ */
public static String urlEncode(String url) {
try {
return URLEncoder.encode(url, "utf-8");
@@ -161,39 +62,21 @@ public class Utils {
}
}
- public static long countBytes(InputStream stream) throws IOException {
- long count = 0;
- BufferedInputStream bis = new BufferedInputStream(stream);
- while (bis.read() != -1) {
- count++;
- }
- return count;
- }
-
+ /**
+ * Capitalizes the first character of a string.
+ *
+ * @param string String to alter
+ * @return string with capitalized first character
+ */
public static String capitalize(String string) {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
}
- public static 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}}";
- case Prefs.Licenses.CC_BY:
- return "{{self|cc-by-3.0}}";
- case Prefs.Licenses.CC_BY_SA:
- return "{{self|cc-by-sa-3.0}}";
- }
- throw new RuntimeException("Unrecognized license value: " + license);
- }
-
+ /**
+ * Generates licence name with given ID
+ * @param license License ID
+ * @return Name of license
+ */
public static int licenseNameFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
@@ -214,51 +97,12 @@ public class Utils {
throw new RuntimeException("Unrecognized license value: " + license);
}
- public static String licenseUrlFor(String license) {
- switch (license) {
- case Prefs.Licenses.CC_BY_3:
- return "https://creativecommons.org/licenses/by/3.0/";
- case Prefs.Licenses.CC_BY_4:
- return "https://creativecommons.org/licenses/by/4.0/";
- case Prefs.Licenses.CC_BY_SA_3:
- return "https://creativecommons.org/licenses/by-sa/3.0/";
- case Prefs.Licenses.CC_BY_SA_4:
- return "https://creativecommons.org/licenses/by-sa/4.0/";
- case Prefs.Licenses.CC0:
- return "https://creativecommons.org/publicdomain/zero/1.0/";
- }
- throw new RuntimeException("Unrecognized license value: " + license);
- }
-
/**
- * Fast-forward an XmlPullParser to the next instance of the given element
- * in the input stream (namespaced).
- *
- * @param parser
- * @param namespace
- * @param element
- * @return true on match, false on failure
+ * Fixing incorrect extension
+ * @param title File name
+ * @param extension Correct extension
+ * @return File with correct extension
*/
- public static boolean xmlFastForward(XmlPullParser parser, String namespace, String element) {
- try {
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- if (parser.getEventType() == XmlPullParser.START_TAG
- && parser.getNamespace().equals(namespace)
- && parser.getName().equals(element)) {
- // We found it!
- return true;
- }
- }
- return false;
- } catch (XmlPullParserException e) {
- e.printStackTrace();
- return false;
- } catch (IOException e) {
- e.printStackTrace();
- return false;
- }
- }
-
public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
@@ -274,11 +118,45 @@ public class Utils {
return title;
}
- public static boolean isNullOrWhiteSpace(String value) {
- return value == null || value.trim().isEmpty();
- }
-
+ /**
+ * Tells whether dark theme is active or not
+ * @param context Activity context
+ * @return The state of dark theme
+ */
public static boolean isDarkTheme(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme", false);
}
+
+ /**
+ * Will be used to fetch the logs generated by the app ever since the beginning of times....
+ * i.e. since the time the app started.
+ *
+ * @return String containing all the logs since the time the app started
+ */
+ public static String getAppLogs() {
+ final String processId = Integer.toString(android.os.Process.myPid());
+
+ StringBuilder stringBuilder = new StringBuilder();
+
+ try {
+ String[] command = new String[] {"logcat","-d","-v","threadtime"};
+
+ Process process = Runtime.getRuntime().exec(command);
+
+ BufferedReader bufferedReader = new BufferedReader(
+ new InputStreamReader(process.getInputStream())
+ );
+
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ if (line.contains(processId)) {
+ stringBuilder.append(line);
+ }
+ }
+ } catch (IOException ioe) {
+ Timber.e("getAppLogs failed", ioe);
+ }
+
+ return stringBuilder.toString();
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java
index f6c5999e9..7bfb22890 100644
--- a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java
@@ -18,6 +18,11 @@ public class WelcomeActivity extends BaseActivity {
private WelcomePagerAdapter adapter = new WelcomePagerAdapter();
+ /**
+ * Initialises exiting fields and dependencies
+ *
+ * @param savedInstanceState WelcomeActivity bundled data
+ */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -30,12 +35,20 @@ public class WelcomeActivity extends BaseActivity {
adapter.setCallback(this::finish);
}
+ /**
+ * References WelcomePageAdapter to null before the activity is destroyed
+ */
@Override
public void onDestroy() {
adapter.setCallback(null);
super.onDestroy();
}
+ /**
+ * Creates a way to change current activity to WelcomeActivity
+ *
+ * @param context Activity context
+ */
public static void startYourself(Context context) {
Intent welcomeIntent = new Intent(context, WelcomeActivity.class);
context.startActivity(welcomeIntent);
diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java
index 9669196e9..a346655cf 100644
--- a/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java
@@ -10,13 +10,6 @@ import butterknife.ButterKnife;
import butterknife.OnClick;
public class WelcomePagerAdapter extends PagerAdapter {
- private static final int PAGE_FINAL = 4;
- private Callback callback;
-
- public interface Callback {
- void onYesClicked();
- }
-
static final int[] PAGE_LAYOUTS = new int[]{
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
@@ -24,16 +17,34 @@ public class WelcomePagerAdapter extends PagerAdapter {
R.layout.welcome_image_details,
R.layout.welcome_final
};
+ private static final int PAGE_FINAL = 4;
+ private Callback callback;
+ /**
+ * Changes callback to provided one
+ *
+ * @param callback New callback
+ * it can be null.
+ */
public void setCallback(@Nullable Callback callback) {
this.callback = callback;
}
+ /**
+ * Gets total number of layouts
+ * @return Number of layouts
+ */
@Override
public int getCount() {
return PAGE_LAYOUTS.length;
}
+ /**
+ * Compares given view with provided object
+ * @param view Adapter view
+ * @param object Adapter object
+ * @return Equality between view and object
+ */
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
@@ -52,16 +63,29 @@ public class WelcomePagerAdapter extends PagerAdapter {
return layout;
}
+ /**
+ * Provides a way to remove an item from container
+ * @param container Adapter view group container
+ * @param position Index of item
+ * @param obj Adapter object
+ */
@Override
public void destroyItem(ViewGroup container, int position, Object obj) {
container.removeView((View) obj);
}
+ public interface Callback {
+ void onYesClicked();
+ }
+
class ViewHolder {
ViewHolder(View view) {
ButterKnife.bind(this, view);
}
+ /**
+ * Triggers on click callback on button click
+ */
@OnClick(R.id.welcomeYesButton)
void onClicked() {
if (callback != null) {
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java b/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
index 479b47444..0513280b5 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
@@ -4,21 +4,33 @@ import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.content.ContentResolver;
+import android.content.Context;
import android.os.Bundle;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.contributions.ContributionsContentProvider;
-import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import timber.log.Timber;
+import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION;
+import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
+import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
+import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
+import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY;
+
public class AccountUtil {
- public static void createAccount(@Nullable AccountAuthenticatorResponse response,
- String username, String password) {
+ public static final String ACCOUNT_TYPE = "fr.free.nrw.commons";
+ public static final String AUTH_COOKIE = "authCookie";
+ public static final String AUTH_TOKEN_TYPE = "CommonsAndroid";
+ private final Context context;
- Account account = new Account(username, accountType());
+ public AccountUtil(Context context) {
+ this.context = context;
+ }
+
+ public void createAccount(@Nullable AccountAuthenticatorResponse response,
+ String username, String password) {
+
+ Account account = new Account(username, ACCOUNT_TYPE);
boolean created = accountManager().addAccountExplicitly(account, password, null);
Timber.d("account creation " + (created ? "successful" : "failure"));
@@ -26,8 +38,8 @@ public class AccountUtil {
if (created) {
if (response != null) {
Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_ACCOUNT_NAME, username);
- bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType());
+ bundle.putString(KEY_ACCOUNT_NAME, username);
+ bundle.putString(KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
response.onResult(bundle);
@@ -35,28 +47,18 @@ public class AccountUtil {
} else {
if (response != null) {
- response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "");
+ response.onError(ERROR_CODE_REMOTE_EXCEPTION, "");
}
Timber.d("account creation failure");
}
// FIXME: If the user turns it off, it shouldn't be auto turned back on
- ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default!
- ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
+ ContentResolver.setSyncAutomatically(account, CONTRIBUTION_AUTHORITY, true); // Enable sync by default!
+ ContentResolver.setSyncAutomatically(account, MODIFICATIONS_AUTHORITY, true); // Enable sync by default!
}
- @NonNull
- public static String accountType() {
- return "fr.free.nrw.commons";
- }
-
- private static AccountManager accountManager() {
- return AccountManager.get(app());
- }
-
- @NonNull
- private static CommonsApplication app() {
- return CommonsApplication.getInstance();
+ private AccountManager accountManager() {
+ return AccountManager.get(context);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
index 5b483c328..e39528252 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/AuthenticatedActivity.java
@@ -1,84 +1,45 @@
package fr.free.nrw.commons.auth;
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerFuture;
import android.os.Bundle;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
-import io.reactivex.Single;
-import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.schedulers.Schedulers;
-import timber.log.Timber;
+
+import static fr.free.nrw.commons.auth.AccountUtil.AUTH_COOKIE;
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
- private String accountType;
- CommonsApplication app;
-
+ @Inject SessionManager sessionManager;
+ @Inject
+ MediaWikiApi mediaWikiApi;
private String authCookie;
- public AuthenticatedActivity() {
- this.accountType = AccountUtil.accountType();
- }
-
- private void getAuthCookie(Account account, AccountManager accountManager) {
- Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false))
- .subscribeOn(Schedulers.io())
- .doOnError(Timber::e)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::onAuthCookieAcquired, throwable -> onAuthFailure());
- }
-
- private void addAccount(AccountManager accountManager) {
- Single.just(accountManager.addAccount(accountType, null, null, null, AuthenticatedActivity.this, null, null))
- .subscribeOn(Schedulers.io())
- .map(AccountManagerFuture::getResult)
- .doOnEvent((bundle, throwable) -> {
- if (!bundle.containsKey(AccountManager.KEY_ACCOUNT_NAME)) {
- throw new RuntimeException("Bundle doesn't contain account-name key: "
- + AccountManager.KEY_ACCOUNT_NAME);
- }
- })
- .map(bundle -> bundle.getString(AccountManager.KEY_ACCOUNT_NAME))
- .doOnError(Timber::e)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(s -> {
- Account[] allAccounts = accountManager.getAccountsByType(accountType);
- Account curAccount = allAccounts[0];
- getAuthCookie(curAccount, accountManager);
- },
- throwable -> onAuthFailure());
- }
-
protected void requestAuthToken() {
if (authCookie != null) {
onAuthCookieAcquired(authCookie);
return;
}
- AccountManager accountManager = AccountManager.get(this);
- Account curAccount = app.getCurrentAccount();
- if (curAccount == null) {
- addAccount(accountManager);
- } else {
- getAuthCookie(curAccount, accountManager);
+ authCookie = sessionManager.getAuthCookie();
+ if (authCookie != null) {
+ onAuthCookieAcquired(authCookie);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- app = CommonsApplication.getInstance();
+
if (savedInstanceState != null) {
- authCookie = savedInstanceState.getString("authCookie");
+ authCookie = savedInstanceState.getString(AUTH_COOKIE);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putString("authCookie", authCookie);
+ outState.putString(AUTH_COOKIE, authCookie);
}
protected abstract void onAuthCookieAcquired(String authCookie);
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
index 156741865..3e90fbf5e 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
@@ -1,115 +1,118 @@
package fr.free.nrw.commons.auth;
+import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
+import android.support.design.widget.TextInputLayout;
import android.support.v4.app.NavUtils;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatDelegate;
import android.text.Editable;
import android.text.TextWatcher;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
-import android.widget.Toast;
+import java.io.IOException;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.WelcomeActivity;
-
import fr.free.nrw.commons.PageTitle;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import fr.free.nrw.commons.theme.NavigationBaseActivity;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static android.view.KeyEvent.KEYCODE_ENTER;
+import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
-
+import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
+import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
public class LoginActivity extends AccountAuthenticatorActivity {
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
- private SharedPreferences prefs = null;
+ @Inject MediaWikiApi mwApi;
+ @Inject AccountUtil accountUtil;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("application_preferences") SharedPreferences prefs;
+ @Inject @Named("default_preferences") SharedPreferences defaultPrefs;
- private Button loginButton;
- private EditText usernameEdit;
- private EditText passwordEdit;
- private EditText twoFactorEdit;
+ @BindView(R.id.loginButton) Button loginButton;
+ @BindView(R.id.signupButton) Button signupButton;
+ @BindView(R.id.loginUsername) EditText usernameEdit;
+ @BindView(R.id.loginPassword) EditText passwordEdit;
+ @BindView(R.id.loginTwoFactor) EditText twoFactorEdit;
+ @BindView(R.id.error_message_container) ViewGroup errorMessageContainer;
+ @BindView(R.id.error_message) TextView errorMessage;
+ @BindView(R.id.two_factor_container)TextInputLayout twoFactorContainer;
ProgressDialog progressDialog;
+ private AppCompatDelegate delegate;
private LoginTextWatcher textWatcher = new LoginTextWatcher();
- private CommonsApplication app;
-
@Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ setTheme(Utils.isDarkTheme(this) ? R.style.DarkAppTheme : R.style.LightAppTheme);
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
- app = CommonsApplication.getInstance();
+ super.onCreate(savedInstanceState);
+ ApplicationlessInjection
+ .getInstance(this.getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
setContentView(R.layout.activity_login);
- loginButton = (Button) findViewById(R.id.loginButton);
- Button signupButton = (Button) findViewById(R.id.signupButton);
- usernameEdit = (EditText) findViewById(R.id.loginUsername);
- passwordEdit = (EditText) findViewById(R.id.loginPassword);
- twoFactorEdit = (EditText) findViewById(R.id.loginTwoFactor);
-
- prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
+ ButterKnife.bind(this);
usernameEdit.addTextChangedListener(textWatcher);
passwordEdit.addTextChangedListener(textWatcher);
twoFactorEdit.addTextChangedListener(textWatcher);
passwordEdit.setOnEditorActionListener(newLoginInputActionListener());
- loginButton.setOnClickListener(this::performLogin);
- signupButton.setOnClickListener(this::signUp);
+ loginButton.setOnClickListener(view -> performLogin());
+ signupButton.setOnClickListener(view -> signUp());
}
- private class LoginTextWatcher implements TextWatcher {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- if (usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0 &&
- (BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE)) {
- loginButton.setEnabled(true);
- } else {
- loginButton.setEnabled(false);
- }
- }
- }
-
- private TextView.OnEditorActionListener newLoginInputActionListener() {
- return (textView, actionId, keyEvent) -> {
- if (loginButton.isEnabled()) {
- if (actionId == IME_ACTION_DONE) {
- performLogin(textView);
- return true;
- } else if ((keyEvent != null) && keyEvent.getKeyCode() == KEYCODE_ENTER) {
- performLogin(textView);
- return true;
- }
- }
- return false;
- };
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
}
+ @Override
protected void onResume() {
super.onResume();
if (prefs.getBoolean("firstrun", true)) {
WelcomeActivity.startYourself(this);
prefs.edit().putBoolean("firstrun", false).apply();
}
- if (app.getCurrentAccount() != null) {
+ if (sessionManager.getCurrentAccount() != null) {
startMainActivity();
}
}
@@ -127,22 +130,113 @@ public class LoginActivity extends AccountAuthenticatorActivity {
usernameEdit.removeTextChangedListener(textWatcher);
passwordEdit.removeTextChangedListener(textWatcher);
twoFactorEdit.removeTextChangedListener(textWatcher);
+ delegate.onDestroy();
super.onDestroy();
}
- private void performLogin(View view) {
+ private void performLogin() {
Timber.d("Login to start!");
- LoginTask task = getLoginTask();
- task.execute();
+ final String username = canonicializeUsername(usernameEdit.getText().toString());
+ final String password = passwordEdit.getText().toString();
+ String twoFactorCode = twoFactorEdit.getText().toString();
+
+ showLoggingProgressBar();
+ Observable.fromCallable(() -> login(username, password, twoFactorCode))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> handleLogin(username, password, result));
}
- private LoginTask getLoginTask() {
- return new LoginTask(
- this,
- canonicializeUsername(usernameEdit.getText().toString()),
- passwordEdit.getText().toString(),
- twoFactorEdit.getText().toString()
- );
+ private String login(String username, String password, String twoFactorCode) {
+ try {
+ if (twoFactorCode.isEmpty()) {
+ return mwApi.login(username, password);
+ } else {
+ return mwApi.login(username, password, twoFactorCode);
+ }
+ } catch (IOException e) {
+ // Do something better!
+ return "NetworkFailure";
+ }
+ }
+
+ private void handleLogin(String username, String password, String result) {
+ Timber.d("Login done!");
+ if (result.equals("PASS")) {
+ handlePassResult(username, password);
+ } else {
+ handleOtherResults(result);
+ }
+ }
+
+ private void showLoggingProgressBar() {
+ progressDialog = new ProgressDialog(this);
+ progressDialog.setIndeterminate(true);
+ progressDialog.setTitle(getString(R.string.logging_in_title));
+ progressDialog.setMessage(getString(R.string.logging_in_message));
+ progressDialog.setCanceledOnTouchOutside(false);
+ progressDialog.show();
+ }
+
+ private void handlePassResult(String username, String password) {
+ showSuccessAndDismissDialog();
+ requestAuthToken();
+ AccountAuthenticatorResponse response = null;
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ Timber.d("Bundle of extras: %s", extras);
+ response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
+ if (response != null) {
+ Bundle authResult = new Bundle();
+ authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
+ authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
+ response.onResult(authResult);
+ }
+ }
+
+ accountUtil.createAccount(response, username, password);
+ startMainActivity();
+ }
+
+ protected void requestAuthToken() {
+ AccountManager accountManager = AccountManager.get(this);
+ Account curAccount = sessionManager.getCurrentAccount();
+ if (curAccount != null) {
+ accountManager.setAuthToken(curAccount, AUTH_TOKEN_TYPE, mwApi.getAuthCookie());
+ }
+ }
+
+ /**
+ * Match known failure message codes and provide messages.
+ *
+ * @param result String
+ */
+ private void handleOtherResults(String result) {
+ if (result.equals("NetworkFailure")) {
+ // Matches NetworkFailure which is created by the doInBackground method
+ showMessageAndCancelDialog(R.string.login_failed_network);
+ } else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
+ // Matches nosuchuser, nosuchusershort, noname
+ showMessageAndCancelDialog(R.string.login_failed_username);
+ emptySensitiveEditFields();
+ } else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
+ // Matches wrongpassword, wrongpasswordempty
+ showMessageAndCancelDialog(R.string.login_failed_password);
+ emptySensitiveEditFields();
+ } else if (result.toLowerCase().contains("throttle".toLowerCase())) {
+ // Matches unknown throttle error codes
+ showMessageAndCancelDialog(R.string.login_failed_throttled);
+ } else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
+ // Matches login-userblocked
+ showMessageAndCancelDialog(R.string.login_failed_blocked);
+ } else if (result.equals("2FA")) {
+ askUserForTwoFactorAuth();
+ } else {
+ // Occurs with unhandled login failure codes
+ Timber.d("Login failed with reason: %s", result);
+ showMessageAndCancelDialog(R.string.login_failed_generic);
+ }
}
/**
@@ -154,6 +248,29 @@ public class LoginActivity extends AccountAuthenticatorActivity {
return new PageTitle(username).getText();
}
+ @Override
+ protected void onStart() {
+ super.onStart();
+ delegate.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ delegate.onStop();
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -164,36 +281,26 @@ public class LoginActivity extends AccountAuthenticatorActivity {
return super.onOptionsItemSelected(item);
}
- /**
- * Called when Sign Up button is clicked.
- * @param view View
- */
- public void signUp(View view) {
- Intent intent = new Intent(this, SignupActivity.class);
- startActivity(intent);
+ @Override
+ @NonNull
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
}
public void askUserForTwoFactorAuth() {
- if (BuildConfig.DEBUG) {
- twoFactorEdit.setVisibility(View.VISIBLE);
- showUserToastAndCancelDialog(R.string.login_failed_2fa_needed);
- } else {
- showUserToastAndCancelDialog(R.string.login_failed_2fa_not_supported);
- }
+ progressDialog.dismiss();
+ twoFactorContainer.setVisibility(VISIBLE);
+ twoFactorEdit.setVisibility(VISIBLE);
+ showMessageAndCancelDialog(R.string.login_failed_2fa_needed);
}
- public void showUserToastAndCancelDialog(int resId) {
- showUserToast(resId);
+ public void showMessageAndCancelDialog(@StringRes int resId) {
+ showMessage(resId, R.color.secondaryDarkColor);
progressDialog.cancel();
}
- private void showUserToast(int resId) {
- Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
- }
-
- public void showSuccessToastAndDismissDialog() {
- Toast successToast = Toast.makeText(this, R.string.login_success, Toast.LENGTH_SHORT);
- successToast.show();
+ public void showSuccessAndDismissDialog() {
+ showMessage(R.string.login_success, R.color.primaryDarkColor);
progressDialog.dismiss();
}
@@ -203,8 +310,57 @@ public class LoginActivity extends AccountAuthenticatorActivity {
}
public void startMainActivity() {
- ContributionsActivity.startYourself(this);
+ NavigationBaseActivity.startActivityWithFlags(this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
finish();
}
+ private void signUp() {
+ Intent intent = new Intent(this, SignupActivity.class);
+ startActivity(intent);
+ }
+
+ private TextView.OnEditorActionListener newLoginInputActionListener() {
+ return (textView, actionId, keyEvent) -> {
+ if (loginButton.isEnabled()) {
+ if (actionId == IME_ACTION_DONE) {
+ performLogin();
+ return true;
+ } else if ((keyEvent != null) && keyEvent.getKeyCode() == KEYCODE_ENTER) {
+ performLogin();
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ private void showMessage(@StringRes int resId, @ColorRes int colorResId) {
+ errorMessage.setText(getString(resId));
+ errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));
+ errorMessageContainer.setVisibility(VISIBLE);
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (delegate == null) {
+ delegate = AppCompatDelegate.create(this, null);
+ }
+ return delegate;
+ }
+
+ private class LoginTextWatcher implements TextWatcher {
+ @Override
+ public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ boolean enabled = usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0
+ && (BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != VISIBLE);
+ loginButton.setEnabled(enabled);
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java
deleted file mode 100644
index dd4f1c4e7..000000000
--- a/app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package fr.free.nrw.commons.auth;
-
-import android.accounts.AccountAuthenticatorResponse;
-import android.accounts.AccountManager;
-import android.app.ProgressDialog;
-import android.os.AsyncTask;
-import android.os.Bundle;
-
-import java.io.IOException;
-
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.mwapi.EventLog;
-import timber.log.Timber;
-
-class LoginTask extends AsyncTask {
-
- private LoginActivity loginActivity;
- private String username;
- private String password;
- private String twoFactorCode = "";
- private CommonsApplication app;
-
- public LoginTask(LoginActivity loginActivity, String username, String password, String twoFactorCode) {
- this.loginActivity = loginActivity;
- this.username = username;
- this.password = password;
- this.twoFactorCode = twoFactorCode;
- app = CommonsApplication.getInstance();
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- loginActivity.progressDialog = new ProgressDialog(loginActivity);
- loginActivity.progressDialog.setIndeterminate(true);
- loginActivity.progressDialog.setTitle(loginActivity.getString(R.string.logging_in_title));
- loginActivity.progressDialog.setMessage(loginActivity.getString(R.string.logging_in_message));
- loginActivity.progressDialog.setCanceledOnTouchOutside(false);
- loginActivity.progressDialog.show();
- }
-
- @Override
- protected String doInBackground(String... params) {
- try {
- if (twoFactorCode.isEmpty()) {
- return app.getMWApi().login(username, password);
- } else {
- return app.getMWApi().login(username, password, twoFactorCode);
- }
- } catch (IOException e) {
- // Do something better!
- return "NetworkFailure";
- }
- }
-
- @Override
- protected void onPostExecute(String result) {
- super.onPostExecute(result);
- Timber.d("Login done!");
-
- EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT)
- .param("username", username)
- .param("result", result)
- .log();
-
- if (result.equals("PASS")) {
- handlePassResult();
- } else {
- handleOtherResults(result);
- }
- }
-
- private void handlePassResult() {
- loginActivity.showSuccessToastAndDismissDialog();
-
- AccountAuthenticatorResponse response = null;
-
- Bundle extras = loginActivity.getIntent().getExtras();
- if (extras != null) {
- Timber.d("Bundle of extras: %s", extras);
- response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
- if (response != null) {
- Bundle authResult = new Bundle();
- authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
- authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType());
- response.onResult(authResult);
- }
- }
-
- AccountUtil.createAccount(response, username, password);
- loginActivity.startMainActivity();
- }
-
- /**
- * Match known failure message codes and provide messages.
- * @param result String
- */
- private void handleOtherResults(String result) {
- if (result.equals("NetworkFailure")) {
- // Matches NetworkFailure which is created by the doInBackground method
- loginActivity.showUserToastAndCancelDialog(R.string.login_failed_network);
- } else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
- // Matches nosuchuser, nosuchusershort, noname
- loginActivity.showUserToastAndCancelDialog(R.string.login_failed_username);
- loginActivity.emptySensitiveEditFields();
- } else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
- // Matches wrongpassword, wrongpasswordempty
- loginActivity.showUserToastAndCancelDialog(R.string.login_failed_password);
- loginActivity.emptySensitiveEditFields();
- } else if (result.toLowerCase().contains("throttle".toLowerCase())) {
- // Matches unknown throttle error codes
- loginActivity.showUserToastAndCancelDialog(R.string.login_failed_throttled);
- } else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
- // Matches login-userblocked
- loginActivity.showUserToastAndCancelDialog(R.string.login_failed_blocked);
- } else if (result.equals("2FA")) {
- loginActivity.askUserForTwoFactorAuth();
- } else {
- // Occurs with unhandled login failure codes
- Timber.d("Login failed with reason: %s", result);
- loginActivity.showUserToastAndCancelDialog(R.string.login_failed_generic);
- }
- }
-}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java b/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
new file mode 100644
index 000000000..a7e62c34e
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/auth/SessionManager.java
@@ -0,0 +1,85 @@
+package fr.free.nrw.commons.auth;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import io.reactivex.Completable;
+import io.reactivex.Observable;
+import timber.log.Timber;
+
+import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
+
+/**
+ * Manage the current logged in user session.
+ */
+public class SessionManager {
+ private final Context context;
+ private final MediaWikiApi mediaWikiApi;
+ private Account currentAccount; // Unlike a savings account... ;-)
+ private SharedPreferences sharedPreferences;
+
+ public SessionManager(Context context, MediaWikiApi mediaWikiApi, SharedPreferences sharedPreferences) {
+ this.context = context;
+ this.mediaWikiApi = mediaWikiApi;
+ this.currentAccount = null;
+ this.sharedPreferences = sharedPreferences;
+ }
+
+ /**
+ * @return Account|null
+ */
+ public Account getCurrentAccount() {
+ if (currentAccount == null) {
+ AccountManager accountManager = AccountManager.get(context);
+ Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
+ if (allAccounts.length != 0) {
+ currentAccount = allAccounts[0];
+ }
+ }
+ return currentAccount;
+ }
+
+ public Boolean revalidateAuthToken() {
+ AccountManager accountManager = AccountManager.get(context);
+ Account curAccount = getCurrentAccount();
+
+ if (curAccount == null) {
+ return false; // This should never happen
+ }
+
+ accountManager.invalidateAuthToken(ACCOUNT_TYPE, mediaWikiApi.getAuthCookie());
+ String authCookie = getAuthCookie();
+
+ if (authCookie == null) {
+ return false;
+ }
+ mediaWikiApi.setAuthCookie(authCookie);
+ return true;
+ }
+
+ public String getAuthCookie() {
+ boolean isLoggedIn = sharedPreferences.getBoolean("isUserLoggedIn", false);
+
+ if (!isLoggedIn) {
+ Timber.e("User is not logged in");
+ return null;
+ } else {
+ String authCookie = sharedPreferences.getString("getAuthCookie", null);
+ if (authCookie == null) {
+ Timber.e("Auth cookie is null even after login");
+ }
+ return authCookie;
+ }
+ }
+
+ public Completable clearAllAccounts() {
+ AccountManager accountManager = AccountManager.get(context);
+ Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
+ return Completable.fromObservable(Observable.fromArray(allAccounts)
+ .map(a -> accountManager.removeAccount(a, null, null).getResult()))
+ .doOnComplete(() -> currentAccount = null);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java
index 4da090531..a6b66cbf6 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/SignupActivity.java
@@ -7,7 +7,6 @@ import android.webkit.WebViewClient;
import android.widget.Toast;
import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.theme.BaseActivity;
import timber.log.Timber;
@@ -39,11 +38,8 @@ public class SignupActivity extends BaseActivity {
//Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url);
- Toast toast = Toast.makeText(
- CommonsApplication.getInstance(),
- "Account created!",
- Toast.LENGTH_LONG
- );
+ Toast toast = Toast.makeText(SignupActivity.this,
+ "Account created!", Toast.LENGTH_LONG);
toast.show();
// terminate on task completion.
finish();
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
index ea574d432..78039f6a9 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticator.java
@@ -5,51 +5,37 @@ import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import java.io.IOException;
+import fr.free.nrw.commons.contributions.ContributionsContentProvider;
+import fr.free.nrw.commons.modifications.ModificationsContentProvider;
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.mwapi.MediaWikiApi;
-
-import static android.accounts.AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION;
-import static android.accounts.AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
-import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
-import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
-import static android.accounts.AccountManager.KEY_AUTHTOKEN;
-import static android.accounts.AccountManager.KEY_BOOLEAN_RESULT;
-import static android.accounts.AccountManager.KEY_ERROR_CODE;
-import static android.accounts.AccountManager.KEY_ERROR_MESSAGE;
-import static android.accounts.AccountManager.KEY_INTENT;
-import static fr.free.nrw.commons.auth.LoginActivity.PARAM_USERNAME;
+import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
+import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
+ private static final String[] SYNC_AUTHORITIES = {ContributionsContentProvider.CONTRIBUTION_AUTHORITY, ModificationsContentProvider.MODIFICATIONS_AUTHORITY};
- private Context context;
+ @NonNull
+ private final Context context;
- WikiAccountAuthenticator(Context context) {
+ public WikiAccountAuthenticator(@NonNull Context context) {
super(context);
this.context = context;
}
- private Bundle unsupportedOperation() {
+ @Override
+ public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
Bundle bundle = new Bundle();
- bundle.putInt(KEY_ERROR_CODE, ERROR_CODE_UNSUPPORTED_OPERATION);
-
- // HACK: the docs indicate that this is a required key bit it's not displayed to the user.
- bundle.putString(KEY_ERROR_MESSAGE, "");
-
+ bundle.putString("test", "editProperties");
return bundle;
}
- private boolean supportedAccountType(@Nullable String type) {
- return AccountUtil.accountType().equals(type);
- }
-
@Override
public Bundle addAccount(@NonNull AccountAuthenticatorResponse response,
@NonNull String accountType, @Nullable String authTokenType,
@@ -57,87 +43,48 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
throws NetworkErrorException {
if (!supportedAccountType(accountType)) {
- return unsupportedOperation();
+ Bundle bundle = new Bundle();
+ bundle.putString("test", "addAccount");
+ return bundle;
}
return addAccount(response);
}
- private Bundle addAccount(AccountAuthenticatorResponse response) {
- Intent Intent = new Intent(context, LoginActivity.class);
- Intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
-
- Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_INTENT, Intent);
-
- return bundle;
- }
-
@Override
public Bundle confirmCredentials(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @Nullable Bundle options)
throws NetworkErrorException {
- return unsupportedOperation();
+ Bundle bundle = new Bundle();
+ bundle.putString("test", "confirmCredentials");
+ return bundle;
}
@Override
- public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
- return unsupportedOperation();
- }
-
- private String getAuthCookie(String username, String password) throws IOException {
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- //TODO add 2fa support here
- String result = api.login(username, password);
- if (result.equals("PASS")) {
- return api.getAuthCookie();
- } else {
- return null;
- }
- }
-
- @Override
- public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
- String authTokenType, Bundle options) throws NetworkErrorException {
- // Extract the username and password from the Account Manager, and ask
- // the server for an appropriate AuthToken.
- final AccountManager am = AccountManager.get(context);
- final String password = am.getPassword(account);
- if (password != null) {
- String authCookie;
- try {
- authCookie = getAuthCookie(account.name, password);
- } catch (IOException e) {
- // Network error!
- e.printStackTrace();
- throw new NetworkErrorException(e);
- }
- if (authCookie != null) {
- final Bundle result = new Bundle();
- result.putString(KEY_ACCOUNT_NAME, account.name);
- result.putString(KEY_ACCOUNT_TYPE, AccountUtil.accountType());
- result.putString(KEY_AUTHTOKEN, authCookie);
- return result;
- }
- }
-
- // If we get here, then we couldn't access the user's password - so we
- // need to re-prompt them for their credentials. We do that by creating
- // an intent to display our AuthenticatorActivity panel.
- final Intent intent = new Intent(context, LoginActivity.class);
- intent.putExtra(PARAM_USERNAME, account.name);
- intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
- final Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_INTENT, intent);
+ public Bundle getAuthToken(@NonNull AccountAuthenticatorResponse response,
+ @NonNull Account account, @NonNull String authTokenType,
+ @Nullable Bundle options)
+ throws NetworkErrorException {
+ Bundle bundle = new Bundle();
+ bundle.putString("test", "getAuthToken");
return bundle;
}
@Nullable
@Override
public String getAuthTokenLabel(@NonNull String authTokenType) {
- //Note: the wikipedia app actually returns a string here....
- //return supportedAccountType(authTokenType) ? context.getString(R.string.wikimedia) : null;
- return null;
+ return supportedAccountType(authTokenType) ? AUTH_TOKEN_TYPE : null;
+ }
+
+ @Nullable
+ @Override
+ public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
+ @NonNull Account account, @Nullable String authTokenType,
+ @Nullable Bundle options)
+ throws NetworkErrorException {
+ Bundle bundle = new Bundle();
+ bundle.putString("test", "updateCredentials");
+ return bundle;
}
@Nullable
@@ -146,16 +93,50 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
@NonNull Account account, @NonNull String[] features)
throws NetworkErrorException {
Bundle bundle = new Bundle();
- bundle.putBoolean(KEY_BOOLEAN_RESULT, false);
+ bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
return bundle;
}
- @Nullable
- @Override
- public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
- @NonNull Account account, @Nullable String authTokenType,
- @Nullable Bundle options) throws NetworkErrorException {
- return unsupportedOperation();
+ private boolean supportedAccountType(@Nullable String type) {
+ return ACCOUNT_TYPE.equals(type);
}
+ private Bundle addAccount(AccountAuthenticatorResponse response) {
+ Intent intent = new Intent(context, LoginActivity.class);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+
+ return bundle;
+ }
+
+ private Bundle unsupportedOperation() {
+ Bundle bundle = new Bundle();
+ bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
+
+ // HACK: the docs indicate that this is a required key bit it's not displayed to the user.
+ bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "");
+
+ return bundle;
+ }
+
+ @Override
+ public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
+ Account account) throws NetworkErrorException {
+ Bundle result = super.getAccountRemovalAllowed(response, account);
+
+ if (result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
+ && !result.containsKey(AccountManager.KEY_INTENT)) {
+ boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+
+ if (allowed) {
+ for (String auth : SYNC_AUTHORITIES) {
+ ContentResolver.cancelSync(account, auth);
+ }
+ }
+ }
+
+ return result;
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java
index 0a996b7d4..826f2ceee 100644
--- a/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java
+++ b/app/src/main/java/fr/free/nrw/commons/auth/WikiAccountAuthenticatorService.java
@@ -1,24 +1,26 @@
package fr.free.nrw.commons.auth;
-import android.accounts.AccountManager;
-import android.app.Service;
+import android.accounts.AbstractAccountAuthenticator;
import android.content.Intent;
import android.os.IBinder;
+import android.support.annotation.Nullable;
-public class WikiAccountAuthenticatorService extends Service {
+import fr.free.nrw.commons.di.CommonsDaggerService;
+
+public class WikiAccountAuthenticatorService extends CommonsDaggerService {
+
+ @Nullable
+ private AbstractAccountAuthenticator authenticator;
- private static WikiAccountAuthenticator wikiAccountAuthenticator = null;
-
@Override
- public IBinder onBind(Intent intent) {
- if (!intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
- return null;
- }
-
- if (wikiAccountAuthenticator == null) {
- wikiAccountAuthenticator = new WikiAccountAuthenticator(this);
- }
- return wikiAccountAuthenticator.getIBinder();
+ public void onCreate() {
+ super.onCreate();
+ authenticator = new WikiAccountAuthenticator(this);
}
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return authenticator == null ? null : authenticator.getIBinder();
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
index 1a70bc57c..7c2e910c4 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategorizationFragment.java
@@ -1,10 +1,7 @@
package fr.free.nrw.commons.category;
-import android.content.ContentProviderClient;
import android.content.SharedPreferences;
import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -31,11 +28,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.data.Category;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.upload.MwVolleyApi;
import fr.free.nrw.commons.utils.StringSortingUtils;
import io.reactivex.Observable;
@@ -45,12 +45,11 @@ import timber.log.Timber;
import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;
-import static fr.free.nrw.commons.category.CategoryContentProvider.AUTHORITY;
/**
* Displays the category suggestion and selection screen. Category search is initiated here.
*/
-public class CategorizationFragment extends Fragment {
+public class CategorizationFragment extends CommonsDaggerSupportFragment {
public static final int SEARCH_CATS_LIMIT = 25;
@@ -65,16 +64,19 @@ public class CategorizationFragment extends Fragment {
@BindView(R.id.categoriesExplanation)
TextView categoriesSkip;
+ @Inject MediaWikiApi mwApi;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+ @Inject CategoryDao categoryDao;
+
private RVRendererAdapter categoriesAdapter;
private OnCategoriesSaveHandler onCategoriesSaveHandler;
private HashMap> categoriesCache;
private List selectedCategories = new ArrayList<>();
- private ContentProviderClient databaseClient;
private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> {
if (item.isSelected()) {
selectedCategories.add(item);
- updateCategoryCount(item, databaseClient);
+ updateCategoryCount(item);
} else {
selectedCategories.remove(item);
}
@@ -88,13 +90,6 @@ public class CategorizationFragment extends Fragment {
categoriesList.setLayoutManager(new LinearLayoutManager(getContext()));
- RxView.clicks(categoriesSkip)
- .takeUntil(RxView.detaches(categoriesSkip))
- .subscribe(o -> {
- getActivity().onBackPressed();
- getActivity().finish();
- });
-
ArrayList items = new ArrayList<>();
categoriesCache = new HashMap<>();
if (savedInstanceState != null) {
@@ -139,12 +134,6 @@ public class CategorizationFragment extends Fragment {
}
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- databaseClient.release();
- }
-
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -180,7 +169,6 @@ public class CategorizationFragment extends Fragment {
setHasOptionsMenu(true);
onCategoriesSaveHandler = (OnCategoriesSaveHandler) getActivity();
getActivity().setTitle(R.string.categories_activity_title);
- databaseClient = getActivity().getContentResolver().acquireContentProviderClient(AUTHORITY);
}
private void updateCategoryList(String filter) {
@@ -205,7 +193,9 @@ public class CategorizationFragment extends Fragment {
.sorted(sortBySimilarity(filter))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
- s -> categoriesAdapter.add(s), Timber::e, () -> {
+ s -> categoriesAdapter.add(s),
+ Timber::e,
+ () -> {
categoriesAdapter.notifyDataSetChanged();
categoriesSearchInProgress.setVisibility(View.GONE);
@@ -253,16 +243,15 @@ public class CategorizationFragment extends Fragment {
private Observable titleCategories() {
//Retrieve the title that was saved when user tapped submit icon
- SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String title = titleDesc.getString("Title", "");
+ String title = prefs.getString("Title", "");
- return CommonsApplication.getInstance().getMWApi()
+ return mwApi
.searchTitles(title, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}
private Observable recentCategories() {
- return Observable.fromIterable(Category.recentCategories(databaseClient, SEARCH_CATS_LIMIT))
+ return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
.map(s -> new CategoryItem(s, false));
}
@@ -279,7 +268,7 @@ public class CategorizationFragment extends Fragment {
}
//otherwise, search API for matching categories
- return CommonsApplication.getInstance().getMWApi()
+ return mwApi
.allCategories(term, SEARCH_CATS_LIMIT)
.map(name -> new CategoryItem(name, false));
}
@@ -290,7 +279,7 @@ public class CategorizationFragment extends Fragment {
return Observable.empty();
}
- return CommonsApplication.getInstance().getMWApi()
+ return mwApi
.searchCategories(term, SEARCH_CATS_LIMIT)
.map(s -> new CategoryItem(s, false));
}
@@ -308,28 +297,22 @@ public class CategorizationFragment extends Fragment {
//Check if item contains a 4-digit word anywhere within the string (.* is wildcard)
//And that item does not equal the current year or previous year
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
+ //Check if the year in the form of XX(X)0s is relevant, i.e. in the 2000s or 2010s as stated in Issue #1029
return ((item.matches(".*(19|20)\\d{2}.*") && !item.contains(yearInString) && !item.contains(prevYearInString))
- || item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)"));
+ || item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
+ || (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
}
- private void updateCategoryCount(CategoryItem item, ContentProviderClient client) {
- Category cat = lookupCategory(item.getName());
- cat.incTimesUsed();
- cat.save(client);
- }
+ private void updateCategoryCount(CategoryItem item) {
+ Category category = categoryDao.find(item.getName());
- private Category lookupCategory(String name) {
- Category cat = Category.find(databaseClient, name);
-
- if (cat == null) {
- // Newly used category...
- cat = new Category();
- cat.setName(name);
- cat.setLastUsed(new Date());
- cat.setTimesUsed(0);
+ // Newly used category...
+ if (category == null) {
+ category = new Category(null, item.getName(), new Date(), 0);
}
- return cat;
+ category.incTimesUsed();
+ categoryDao.save(category);
}
public int getCurrentSelectedCount() {
diff --git a/app/src/main/java/fr/free/nrw/commons/category/Category.java b/app/src/main/java/fr/free/nrw/commons/category/Category.java
new file mode 100644
index 000000000..f2d83d2e5
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/category/Category.java
@@ -0,0 +1,96 @@
+package fr.free.nrw.commons.category;
+
+import android.net.Uri;
+
+import java.util.Date;
+
+/**
+ * Represents a category
+ */
+public class Category {
+ private Uri contentUri;
+ private String name;
+ private Date lastUsed;
+ private int timesUsed;
+
+ public Category() {
+ }
+
+ public Category(Uri contentUri, String name, Date lastUsed, int timesUsed) {
+ this.contentUri = contentUri;
+ this.name = name;
+ this.lastUsed = lastUsed;
+ this.timesUsed = timesUsed;
+ }
+
+ /**
+ * Gets name
+ *
+ * @return name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Modifies name
+ *
+ * @param name Category name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets last used date
+ *
+ * @return Last used date
+ */
+ public Date getLastUsed() {
+ // warning: Date objects are mutable.
+ return (Date)lastUsed.clone();
+ }
+
+ /**
+ * Generates new last used date
+ */
+ private void touch() {
+ lastUsed = new Date();
+ }
+
+ /**
+ * Gets no. of times the category is used
+ *
+ * @return no. of times used
+ */
+ public int getTimesUsed() {
+ return timesUsed;
+ }
+
+ /**
+ * Increments timesUsed by 1 and sets last used date as now.
+ */
+ public void incTimesUsed() {
+ timesUsed++;
+ touch();
+ }
+
+ /**
+ * Gets the content URI for this category
+ *
+ * @return content URI
+ */
+ public Uri getContentUri() {
+ return contentUri;
+ }
+
+ /**
+ * Modifies the content URI - marking this category as already saved in the database
+ *
+ * @param contentUri the content URI
+ */
+ public void setContentUri(Uri contentUri) {
+ this.contentUri = contentUri;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java
index 9fd125b7d..16cf49742 100644
--- a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java
@@ -1,6 +1,5 @@
package fr.free.nrw.commons.category;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
@@ -10,17 +9,18 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.data.Category;
+import javax.inject.Inject;
+
import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH;
-import static fr.free.nrw.commons.data.Category.Table.ALL_FIELDS;
-import static fr.free.nrw.commons.data.Category.Table.COLUMN_ID;
-import static fr.free.nrw.commons.data.Category.Table.TABLE_NAME;
+import static fr.free.nrw.commons.category.CategoryDao.Table.ALL_FIELDS;
+import static fr.free.nrw.commons.category.CategoryDao.Table.COLUMN_ID;
+import static fr.free.nrw.commons.category.CategoryDao.Table.TABLE_NAME;
-public class CategoryContentProvider extends ContentProvider {
+public class CategoryContentProvider extends CommonsDaggerContentProvider {
public static final String AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
// For URI matcher
@@ -37,19 +37,11 @@ public class CategoryContentProvider extends ContentProvider {
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID);
}
- private DBOpenHelper dbOpenHelper;
-
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
- @SuppressWarnings("ConstantConditions")
- @Override
- public boolean onCreate() {
- CommonsApplication app = ((CommonsApplication) getContext().getApplicationContext());
- dbOpenHelper = app.getDBOpenHelper();
- return false;
- }
+ @Inject DBOpenHelper dbOpenHelper;
@SuppressWarnings("ConstantConditions")
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/data/Category.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java
similarity index 54%
rename from app/src/main/java/fr/free/nrw/commons/data/Category.java
rename to app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java
index 757f6b691..e63b04c26 100644
--- a/app/src/main/java/fr/free/nrw/commons/data/Category.java
+++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java
@@ -1,115 +1,64 @@
-package fr.free.nrw.commons.data;
+package fr.free.nrw.commons.category;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
-import fr.free.nrw.commons.category.CategoryContentProvider;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
-public class Category {
- private Uri contentUri;
+public class CategoryDao {
- private String name;
- private Date lastUsed;
- private int timesUsed;
+ private final Provider clientProvider;
- // Getters/setters
- public String getName() {
- return name;
+ @Inject
+ public CategoryDao(@Named("category") Provider clientProvider) {
+ this.clientProvider = clientProvider;
}
- public void setName(String name) {
- this.name = name;
- }
-
- private Date getLastUsed() {
- // warning: Date objects are mutable.
- return (Date)lastUsed.clone();
- }
-
- public void setLastUsed(Date lastUsed) {
- // warning: Date objects are mutable.
- this.lastUsed = (Date)lastUsed.clone();
- }
-
- private void touch() {
- lastUsed = new Date();
- }
-
- private int getTimesUsed() {
- return timesUsed;
- }
-
- public void setTimesUsed(int timesUsed) {
- this.timesUsed = timesUsed;
- }
-
- public void incTimesUsed() {
- timesUsed++;
- touch();
- }
-
- //region Database/content-provider stuff
-
- /**
- * Persist category.
- * @param client ContentProviderClient to handle DB connection
- */
- public void save(ContentProviderClient client) {
+ public void save(Category category) {
+ ContentProviderClient db = clientProvider.get();
try {
- if (contentUri == null) {
- contentUri = client.insert(CategoryContentProvider.BASE_URI, this.toContentValues());
+ if (category.getContentUri() == null) {
+ category.setContentUri(db.insert(CategoryContentProvider.BASE_URI, toContentValues(category)));
} else {
- client.update(contentUri, toContentValues(), null, null);
+ db.update(category.getContentUri(), toContentValues(category), null, null);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
+ } finally {
+ db.release();
}
}
- private ContentValues toContentValues() {
- ContentValues cv = new ContentValues();
- cv.put(Table.COLUMN_NAME, getName());
- cv.put(Table.COLUMN_LAST_USED, getLastUsed().getTime());
- cv.put(Table.COLUMN_TIMES_USED, getTimesUsed());
- return cv;
- }
-
- private static Category fromCursor(Cursor cursor) {
- // Hardcoding column positions!
- Category c = new Category();
- c.contentUri = CategoryContentProvider.uriForId(cursor.getInt(0));
- c.name = cursor.getString(1);
- c.lastUsed = new Date(cursor.getLong(2));
- c.timesUsed = cursor.getInt(3);
- return c;
- }
-
/**
* Find persisted category in database, based on its name.
- * @param client ContentProviderClient to handle DB connection
+ *
* @param name Category's name
* @return category from database, or null if not found
*/
- public static @Nullable Category find(ContentProviderClient client, String name) {
+ @Nullable
+ Category find(String name) {
Cursor cursor = null;
+ ContentProviderClient db = clientProvider.get();
try {
- cursor = client.query(
+ cursor = db.query(
CategoryContentProvider.BASE_URI,
- Category.Table.ALL_FIELDS,
- Category.Table.COLUMN_NAME + "=?",
+ Table.ALL_FIELDS,
+ Table.COLUMN_NAME + "=?",
new String[]{name},
null);
if (cursor != null && cursor.moveToFirst()) {
- return Category.fromCursor(cursor);
+ return fromCursor(cursor);
}
} catch (RemoteException e) {
// This feels lazy, but to hell with checked exceptions. :)
@@ -118,29 +67,32 @@ public class Category {
if (cursor != null) {
cursor.close();
}
+ db.release();
}
return null;
}
/**
* Retrieve recently-used categories, ordered by descending date.
+ *
* @return a list containing recent categories
*/
- public static @NonNull ArrayList recentCategories(ContentProviderClient client, int limit) {
- ArrayList items = new ArrayList<>();
+ @NonNull
+ List recentCategories(int limit) {
+ List items = new ArrayList<>();
Cursor cursor = null;
+ ContentProviderClient db = clientProvider.get();
try {
- cursor = client.query(
+ cursor = db.query(
CategoryContentProvider.BASE_URI,
- Category.Table.ALL_FIELDS,
+ Table.ALL_FIELDS,
null,
new String[]{},
- Category.Table.COLUMN_LAST_USED + " DESC");
+ Table.COLUMN_LAST_USED + " DESC");
// fixme add a limit on the original query instead of falling out of the loop?
while (cursor != null && cursor.moveToNext()
&& cursor.getPosition() < limit) {
- Category cat = Category.fromCursor(cursor);
- items.add(cat.getName());
+ items.add(fromCursor(cursor).getName());
}
} catch (RemoteException e) {
throw new RuntimeException(e);
@@ -148,17 +100,36 @@ public class Category {
if (cursor != null) {
cursor.close();
}
+ db.release();
}
return items;
}
+ Category fromCursor(Cursor cursor) {
+ // Hardcoding column positions!
+ return new Category(
+ CategoryContentProvider.uriForId(cursor.getInt(0)),
+ cursor.getString(1),
+ new Date(cursor.getLong(2)),
+ cursor.getInt(3)
+ );
+ }
+
+ private ContentValues toContentValues(Category category) {
+ ContentValues cv = new ContentValues();
+ cv.put(CategoryDao.Table.COLUMN_NAME, category.getName());
+ cv.put(CategoryDao.Table.COLUMN_LAST_USED, category.getLastUsed().getTime());
+ cv.put(CategoryDao.Table.COLUMN_TIMES_USED, category.getTimesUsed());
+ return cv;
+ }
+
public static class Table {
public static final String TABLE_NAME = "categories";
public static final String COLUMN_ID = "_id";
- public static final String COLUMN_NAME = "name";
- public static final String COLUMN_LAST_USED = "last_used";
- public static final String COLUMN_TIMES_USED = "times_used";
+ static final String COLUMN_NAME = "name";
+ static final String COLUMN_LAST_USED = "last_used";
+ static final String COLUMN_TIMES_USED = "times_used";
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
public static final String[] ALL_FIELDS = {
@@ -168,7 +139,9 @@ public class Category {
COLUMN_TIMES_USED
};
- private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
+
+ static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ COLUMN_ID + " INTEGER PRIMARY KEY,"
+ COLUMN_NAME + " STRING,"
+ COLUMN_LAST_USED + " INTEGER,"
@@ -180,7 +153,7 @@ public class Category {
}
public static void onDelete(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
}
@@ -208,5 +181,4 @@ public class Category {
}
}
}
- //endregion
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
index f9060b7b5..7861f96de 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java
@@ -1,13 +1,8 @@
package fr.free.nrw.commons.contributions;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Parcel;
-import android.os.RemoteException;
-import android.text.TextUtils;
+import android.support.annotation.NonNull;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -16,7 +11,6 @@ import java.util.Locale;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
-import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
public class Contribution extends Media {
@@ -43,7 +37,6 @@ public class Contribution extends Media {
public static final String SOURCE_GALLERY = "gallery";
public static final String SOURCE_EXTERNAL = "external";
- private ContentProviderClient client;
private Uri contentUri;
private String source;
private String editSummary;
@@ -51,24 +44,42 @@ public class Contribution extends Media {
private int state;
private long transferred;
private String decimalCoords;
-
private boolean isMultiple;
- public boolean getMultiple() {
- return isMultiple;
+ public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
+ int state, long dataLength, Date dateUploaded, long transferred,
+ String source, String description, String creator, boolean isMultiple,
+ int width, int height, String license) {
+ super(localUri, imageUrl, filename, description, dataLength, timestamp, dateUploaded, creator);
+ this.contentUri = contentUri;
+ this.state = state;
+ this.timestamp = timestamp;
+ this.transferred = transferred;
+ this.source = source;
+ this.isMultiple = isMultiple;
+ this.width = width;
+ this.height = height;
+ this.license = license;
}
- public void setMultiple(boolean multiple) {
- isMultiple = multiple;
- }
-
- public Contribution(Uri localUri, String remoteUri, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords) {
- super(localUri, remoteUri, filename, description, dataLength, dateCreated, dateUploaded, creator);
+ public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
+ 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;
timestamp = new Date(System.currentTimeMillis());
}
+ public Contribution(Parcel in) {
+ super(in);
+ contentUri = in.readParcelable(Uri.class.getClassLoader());
+ source = in.readString();
+ timestamp = (Date) in.readSerializable();
+ state = in.readInt();
+ transferred = in.readLong();
+ isMultiple = in.readInt() == 1;
+ }
+
@Override
public void writeToParcel(Parcel parcel, int flags) {
super.writeToParcel(parcel, flags);
@@ -80,14 +91,12 @@ public class Contribution extends Media {
parcel.writeInt(isMultiple ? 1 : 0);
}
- public Contribution(Parcel in) {
- super(in);
- contentUri = in.readParcelable(Uri.class.getClassLoader());
- source = in.readString();
- timestamp = (Date) in.readSerializable();
- state = in.readInt();
- transferred = in.readLong();
- isMultiple = in.readInt() == 1;
+ public boolean getMultiple() {
+ return isMultiple;
+ }
+
+ public void setMultiple(boolean multiple) {
+ isMultiple = multiple;
}
public long getTransferred() {
@@ -106,10 +115,18 @@ public class Contribution extends Media {
return contentUri;
}
+ public void setContentUri(Uri contentUri) {
+ this.contentUri = contentUri;
+ }
+
public Date getTimestamp() {
return timestamp;
}
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
+
public int getState() {
return state;
}
@@ -149,68 +166,12 @@ public class Contribution extends Media {
}
buffer.append("== {{int:license-header}} ==\n")
- .append(Utils.licenseTemplateFor(getLicense())).append("\n\n")
+ .append(licenseTemplateFor(getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
.append(getTrackingTemplates());
return buffer.toString();
}
- public void setContentProviderClient(ContentProviderClient client) {
- this.client = client;
- }
-
- public void save() {
- try {
- if (contentUri == null) {
- contentUri = client.insert(ContributionsContentProvider.BASE_URI, this.toContentValues());
- } else {
- client.update(contentUri, toContentValues(), null, null);
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- public void delete() {
- try {
- if (contentUri == null) {
- // noooo
- throw new RuntimeException("tried to delete item with no content URI");
- } else {
- client.delete(contentUri, null, null);
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
-
- public ContentValues toContentValues() {
- ContentValues cv = new ContentValues();
- cv.put(Table.COLUMN_FILENAME, getFilename());
- if (getLocalUri() != null) {
- cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString());
- }
- if (getImageUrl() != null) {
- cv.put(Table.COLUMN_IMAGE_URL, getImageUrl());
- }
- if (getDateUploaded() != null) {
- cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime());
- }
- cv.put(Table.COLUMN_LENGTH, getDataLength());
- cv.put(Table.COLUMN_TIMESTAMP, getTimestamp().getTime());
- cv.put(Table.COLUMN_STATE, getState());
- cv.put(Table.COLUMN_TRANSFERRED, transferred);
- cv.put(Table.COLUMN_SOURCE, source);
- cv.put(Table.COLUMN_DESCRIPTION, description);
- cv.put(Table.COLUMN_CREATOR, creator);
- cv.put(Table.COLUMN_MULTIPLE, isMultiple ? 1 : 0);
- cv.put(Table.COLUMN_WIDTH, width);
- cv.put(Table.COLUMN_HEIGHT, height);
- cv.put(Table.COLUMN_LICENSE, license);
- return cv;
- }
-
@Override
public void setFilename(String filename) {
this.filename = filename;
@@ -224,33 +185,6 @@ public class Contribution extends Media {
timestamp = new Date(System.currentTimeMillis());
}
- public static Contribution fromCursor(Cursor cursor) {
- // Hardcoding column positions!
- Contribution c = new Contribution();
-
- //Check that cursor has a value to avoid CursorIndexOutOfBoundsException
- if (cursor.getCount() > 0) {
- c.contentUri = ContributionsContentProvider.uriForId(cursor.getInt(0));
- c.filename = cursor.getString(1);
- c.localUri = TextUtils.isEmpty(cursor.getString(2)) ? null : Uri.parse(cursor.getString(2));
- c.imageUrl = cursor.getString(3);
- c.timestamp = cursor.getLong(4) == 0 ? null : new Date(cursor.getLong(4));
- c.state = cursor.getInt(5);
- c.dataLength = cursor.getLong(6);
- c.dateUploaded = cursor.getLong(7) == 0 ? null : new Date(cursor.getLong(7));
- c.transferred = cursor.getLong(8);
- c.source = cursor.getString(9);
- c.description = cursor.getString(10);
- c.creator = cursor.getString(11);
- c.isMultiple = cursor.getInt(12) == 1;
- c.width = cursor.getInt(13);
- c.height = cursor.getInt(14);
- c.license = cursor.getString(15);
- }
-
- return c;
- }
-
public String getSource() {
return source;
}
@@ -267,118 +201,25 @@ public class Contribution extends Media {
this.decimalCoords = decimalCoords;
}
- public static class Table {
- public static final String TABLE_NAME = "contributions";
-
- public static final String COLUMN_ID = "_id";
- public static final String COLUMN_FILENAME = "filename";
- public static final String COLUMN_LOCAL_URI = "local_uri";
- public static final String COLUMN_IMAGE_URL = "image_url";
- public static final String COLUMN_TIMESTAMP = "timestamp";
- public static final String COLUMN_STATE = "state";
- public static final String COLUMN_LENGTH = "length";
- public static final String COLUMN_UPLOADED = "uploaded";
- public static final String COLUMN_TRANSFERRED = "transferred"; // Currently transferred number of bytes
- public static final String COLUMN_SOURCE = "source";
- public static final String COLUMN_DESCRIPTION = "description";
- public static final String COLUMN_CREATOR = "creator"; // Initial uploader
- public static final String COLUMN_MULTIPLE = "multiple";
- public static final String COLUMN_WIDTH = "width";
- public static final String COLUMN_HEIGHT = "height";
- public static final String COLUMN_LICENSE = "license";
-
- // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
- public static final String[] ALL_FIELDS = {
- COLUMN_ID,
- COLUMN_FILENAME,
- COLUMN_LOCAL_URI,
- COLUMN_IMAGE_URL,
- COLUMN_TIMESTAMP,
- COLUMN_STATE,
- COLUMN_LENGTH,
- COLUMN_UPLOADED,
- COLUMN_TRANSFERRED,
- COLUMN_SOURCE,
- COLUMN_DESCRIPTION,
- COLUMN_CREATOR,
- COLUMN_MULTIPLE,
- COLUMN_WIDTH,
- COLUMN_HEIGHT,
- COLUMN_LICENSE
- };
-
-
- private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
- + "_id INTEGER PRIMARY KEY,"
- + "filename STRING,"
- + "local_uri STRING,"
- + "image_url STRING,"
- + "uploaded INTEGER,"
- + "timestamp INTEGER,"
- + "state INTEGER,"
- + "length INTEGER,"
- + "transferred INTEGER,"
- + "source STRING,"
- + "description STRING,"
- + "creator STRING,"
- + "multiple INTEGER,"
- + "width INTEGER,"
- + "height INTEGER,"
- + "LICENSE STRING"
- + ");";
-
-
- public static void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_STATEMENT);
+ @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}}";
+ case Prefs.Licenses.CC_BY:
+ return "{{self|cc-by-3.0}}";
+ case Prefs.Licenses.CC_BY_SA:
+ return "{{self|cc-by-sa-3.0}}";
}
- public static void onDelete(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
- onCreate(db);
- }
-
- public static void onUpdate(SQLiteDatabase db, int from, int to) {
- if (from == to) {
- return;
- }
- if (from == 1) {
- db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN description STRING;");
- db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN creator STRING;");
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 2) {
- db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN multiple INTEGER;");
- db.execSQL("UPDATE " + TABLE_NAME + " SET multiple = 0");
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 3) {
- // Do nothing
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 4) {
- // Do nothing -- added Category
- from++;
- onUpdate(db, from, to);
- return;
- }
- if (from == 5) {
- // Added width and height fields
- db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN width INTEGER;");
- db.execSQL("UPDATE " + TABLE_NAME + " SET width = 0");
- db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN height INTEGER;");
- db.execSQL("UPDATE " + TABLE_NAME + " SET height = 0");
- db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN license STRING;");
- db.execSQL("UPDATE " + TABLE_NAME + " SET license='" + Prefs.Licenses.CC_BY_SA_3 + "';");
- from++;
- onUpdate(db, from, to);
- return;
- }
- }
+ throw new RuntimeException("Unrecognized license value: " + license);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
index a243330c3..db20963e7 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java
@@ -93,10 +93,15 @@ class ContributionController {
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
break;
case SELECT_FROM_CAMERA:
- shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type
+ //FIXME: Find out appropriate mime type
+ // AFAIK this is the right type for a JPEG image
+ // https://developer.android.com/training/sharing/send.html#send-binary-content
+ shareIntent.setType("image/jpeg");
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
break;
+ default:
+ break;
}
Timber.i("Image selected");
try {
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java
new file mode 100644
index 000000000..9d3038e03
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java
@@ -0,0 +1,281 @@
+package fr.free.nrw.commons.contributions;
+
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.Date;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+
+import fr.free.nrw.commons.settings.Prefs;
+
+import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
+import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
+import static fr.free.nrw.commons.contributions.ContributionsContentProvider.uriForId;
+
+public class ContributionDao {
+ /*
+ This sorts in the following order:
+ Currently Uploading
+ Failed (Sorted in ascending order of time added - FIFO)
+ Queued to Upload (Sorted in ascending order of time added - FIFO)
+ Completed (Sorted in descending order of time added)
+
+ This is why Contribution.STATE_COMPLETED is -1.
+ */
+ static final String CONTRIBUTION_SORT = Table.COLUMN_STATE + " DESC, "
+ + Table.COLUMN_UPLOADED + " DESC , ("
+ + Table.COLUMN_TIMESTAMP + " * "
+ + Table.COLUMN_STATE + ")";
+
+ private final Provider clientProvider;
+
+ @Inject
+ public ContributionDao(@Named("contribution") Provider clientProvider) {
+ this.clientProvider = clientProvider;
+ }
+
+ Cursor loadAllContributions() {
+ ContentProviderClient db = clientProvider.get();
+ try {
+ return db.query(BASE_URI, ALL_FIELDS, "", null, CONTRIBUTION_SORT);
+ } catch (RemoteException e) {
+ return null;
+ } finally {
+ db.release();
+ }
+ }
+
+ public void save(Contribution contribution) {
+ ContentProviderClient db = clientProvider.get();
+ try {
+ if (contribution.getContentUri() == null) {
+ contribution.setContentUri(db.insert(BASE_URI, toContentValues(contribution)));
+ } else {
+ db.update(contribution.getContentUri(), toContentValues(contribution), null, null);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ }
+
+ public void delete(Contribution contribution) {
+ ContentProviderClient db = clientProvider.get();
+ try {
+ if (contribution.getContentUri() == null) {
+ // noooo
+ throw new RuntimeException("tried to delete item with no content URI");
+ } else {
+ db.delete(contribution.getContentUri(), null, null);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ }
+
+ ContentValues toContentValues(Contribution contribution) {
+ ContentValues cv = new ContentValues();
+ cv.put(Table.COLUMN_FILENAME, contribution.getFilename());
+ if (contribution.getLocalUri() != null) {
+ cv.put(Table.COLUMN_LOCAL_URI, contribution.getLocalUri().toString());
+ }
+ if (contribution.getImageUrl() != null) {
+ cv.put(Table.COLUMN_IMAGE_URL, contribution.getImageUrl());
+ }
+ if (contribution.getDateUploaded() != null) {
+ cv.put(Table.COLUMN_UPLOADED, contribution.getDateUploaded().getTime());
+ }
+ cv.put(Table.COLUMN_LENGTH, contribution.getDataLength());
+ cv.put(Table.COLUMN_TIMESTAMP, contribution.getTimestamp().getTime());
+ cv.put(Table.COLUMN_STATE, contribution.getState());
+ cv.put(Table.COLUMN_TRANSFERRED, contribution.getTransferred());
+ cv.put(Table.COLUMN_SOURCE, contribution.getSource());
+ cv.put(Table.COLUMN_DESCRIPTION, contribution.getDescription());
+ cv.put(Table.COLUMN_CREATOR, contribution.getCreator());
+ cv.put(Table.COLUMN_MULTIPLE, contribution.getMultiple() ? 1 : 0);
+ cv.put(Table.COLUMN_WIDTH, contribution.getWidth());
+ cv.put(Table.COLUMN_HEIGHT, contribution.getHeight());
+ cv.put(Table.COLUMN_LICENSE, contribution.getLicense());
+ return cv;
+ }
+
+ public Contribution fromCursor(Cursor cursor) {
+ // Hardcoding column positions!
+ //Check that cursor has a value to avoid CursorIndexOutOfBoundsException
+ if (cursor.getCount() > 0) {
+ return new Contribution(
+ uriForId(cursor.getInt(0)),
+ cursor.getString(1),
+ parseUri(cursor.getString(2)),
+ cursor.getString(3),
+ parseTimestamp(cursor.getLong(4)),
+ cursor.getInt(5),
+ cursor.getLong(6),
+ parseTimestamp(cursor.getLong(7)),
+ cursor.getLong(8),
+ cursor.getString(9),
+ cursor.getString(10),
+ cursor.getString(11),
+ cursor.getInt(12) == 1,
+ cursor.getInt(13),
+ cursor.getInt(14),
+ cursor.getString(15));
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static Date parseTimestamp(long timestamp) {
+ return timestamp == 0 ? null : new Date(timestamp);
+ }
+
+ @Nullable
+ private static Uri parseUri(String uriString) {
+ return TextUtils.isEmpty(uriString) ? null : Uri.parse(uriString);
+ }
+
+ public static class Table {
+ public static final String TABLE_NAME = "contributions";
+
+ public static final String COLUMN_ID = "_id";
+ public static final String COLUMN_FILENAME = "filename";
+ public static final String COLUMN_LOCAL_URI = "local_uri";
+ public static final String COLUMN_IMAGE_URL = "image_url";
+ public static final String COLUMN_TIMESTAMP = "timestamp";
+ public static final String COLUMN_STATE = "state";
+ public static final String COLUMN_LENGTH = "length";
+ public static final String COLUMN_UPLOADED = "uploaded";
+ public static final String COLUMN_TRANSFERRED = "transferred"; // Currently transferred number of bytes
+ public static final String COLUMN_SOURCE = "source";
+ public static final String COLUMN_DESCRIPTION = "description";
+ public static final String COLUMN_CREATOR = "creator"; // Initial uploader
+ public static final String COLUMN_MULTIPLE = "multiple";
+ public static final String COLUMN_WIDTH = "width";
+ public static final String COLUMN_HEIGHT = "height";
+ public static final String COLUMN_LICENSE = "license";
+
+ // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
+ public static final String[] ALL_FIELDS = {
+ COLUMN_ID,
+ COLUMN_FILENAME,
+ COLUMN_LOCAL_URI,
+ COLUMN_IMAGE_URL,
+ COLUMN_TIMESTAMP,
+ COLUMN_STATE,
+ COLUMN_LENGTH,
+ COLUMN_UPLOADED,
+ COLUMN_TRANSFERRED,
+ COLUMN_SOURCE,
+ COLUMN_DESCRIPTION,
+ COLUMN_CREATOR,
+ COLUMN_MULTIPLE,
+ COLUMN_WIDTH,
+ COLUMN_HEIGHT,
+ COLUMN_LICENSE
+ };
+
+ public static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
+
+ public static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ + "_id INTEGER PRIMARY KEY,"
+ + "filename STRING,"
+ + "local_uri STRING,"
+ + "image_url STRING,"
+ + "uploaded INTEGER,"
+ + "timestamp INTEGER,"
+ + "state INTEGER,"
+ + "length INTEGER,"
+ + "transferred INTEGER,"
+ + "source STRING,"
+ + "description STRING,"
+ + "creator STRING,"
+ + "multiple INTEGER,"
+ + "width INTEGER,"
+ + "height INTEGER,"
+ + "LICENSE STRING"
+ + ");";
+
+ // Upgrade from version 1 ->
+ static final String ADD_CREATOR_FIELD = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN creator STRING;";
+ static final String ADD_DESCRIPTION_FIELD = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN description STRING;";
+
+ // Upgrade from version 2 ->
+ static final String ADD_MULTIPLE_FIELD = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN multiple INTEGER;";
+ static final String SET_DEFAULT_MULTIPLE = "UPDATE " + TABLE_NAME + " SET multiple = 0";
+
+ // Upgrade from version 5 ->
+ static final String ADD_WIDTH_FIELD = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN width INTEGER;";
+ static final String SET_DEFAULT_WIDTH = "UPDATE " + TABLE_NAME + " SET width = 0";
+ static final String ADD_HEIGHT_FIELD = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN height INTEGER;";
+ static final String SET_DEFAULT_HEIGHT = "UPDATE " + TABLE_NAME + " SET height = 0";
+ static final String ADD_LICENSE_FIELD = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN license STRING;";
+ static final String SET_DEFAULT_LICENSE = "UPDATE " + TABLE_NAME + " SET license='" + Prefs.Licenses.CC_BY_SA_3 + "';";
+
+
+ public static void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_STATEMENT);
+ }
+
+ public static void onDelete(SQLiteDatabase db) {
+ db.execSQL(DROP_TABLE_STATEMENT);
+ onCreate(db);
+ }
+
+ public static void onUpdate(SQLiteDatabase db, int from, int to) {
+ if (from == to) {
+ return;
+ }
+ if (from == 1) {
+ db.execSQL(ADD_DESCRIPTION_FIELD);
+ db.execSQL(ADD_CREATOR_FIELD);
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ if (from == 2) {
+ db.execSQL(ADD_MULTIPLE_FIELD);
+ db.execSQL(SET_DEFAULT_MULTIPLE);
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ if (from == 3) {
+ // Do nothing
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ if (from == 4) {
+ // Do nothing -- added Category
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ if (from == 5) {
+ // Added width and height fields
+ db.execSQL(ADD_WIDTH_FIELD);
+ db.execSQL(SET_DEFAULT_WIDTH);
+ db.execSQL(ADD_HEIGHT_FIELD);
+ db.execSQL(SET_DEFAULT_HEIGHT);
+ db.execSQL(ADD_LICENSE_FIELD);
+ db.execSQL(SET_DEFAULT_LICENSE);
+ from++;
+ onUpdate(db, from, to);
+ return;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java
index f27bfe210..5c1ecfaa0 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsActivity.java
@@ -9,7 +9,6 @@ import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
@@ -23,13 +22,17 @@ import android.widget.AdapterView;
import java.util.ArrayList;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService;
import io.reactivex.android.schedulers.AndroidSchedulers;
@@ -39,15 +42,22 @@ import timber.log.Timber;
import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
-import static fr.free.nrw.commons.contributions.Contribution.Table.ALL_FIELDS;
-import static fr.free.nrw.commons.contributions.ContributionsContentProvider.AUTHORITY;
+import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
-public class ContributionsActivity extends AuthenticatedActivity
- implements LoaderManager.LoaderCallbacks, AdapterView.OnItemClickListener,
- MediaDetailPagerFragment.MediaDetailProvider, FragmentManager.OnBackStackChangedListener,
- ContributionsListFragment.SourceRefresher {
+public class ContributionsActivity
+ extends AuthenticatedActivity
+ implements LoaderManager.LoaderCallbacks,
+ AdapterView.OnItemClickListener,
+ MediaDetailPagerFragment.MediaDetailProvider,
+ FragmentManager.OnBackStackChangedListener,
+ ContributionsListFragment.SourceRefresher {
+
+ @Inject MediaWikiApi mediaWikiApi;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+ @Inject ContributionDao contributionDao;
private Cursor allContributions;
private ContributionsListFragment contributionsList;
@@ -55,21 +65,6 @@ public class ContributionsActivity extends AuthenticatedActivity
private UploadService uploadService;
private boolean isUploadServiceConnected;
private ArrayList observersWaitingForLoad = new ArrayList<>();
- private String CONTRIBUTION_SELECTION = "";
-
- /*
- This sorts in the following order:
- Currently Uploading
- Failed (Sorted in ascending order of time added - FIFO)
- Queued to Upload (Sorted in ascending order of time added - FIFO)
- Completed (Sorted in descending order of time added)
-
- This is why Contribution.STATE_COMPLETED is -1.
- */
- private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, "
- + Contribution.Table.COLUMN_UPLOADED + " DESC , ("
- + Contribution.Table.COLUMN_TIMESTAMP + " * "
- + Contribution.Table.COLUMN_STATE + ")";
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@@ -84,7 +79,7 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
- throw 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!"));
}
};
@@ -101,12 +96,8 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onResume() {
super.onResume();
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean isSettingsChanged =
- sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
- editor.apply();
+ boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
+ prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
if (isSettingsChanged) {
refreshSource();
}
@@ -114,16 +105,14 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
- // Do a sync every time we get here!
- CommonsApplication app = ((CommonsApplication) getApplication());
- requestSync(app.getCurrentAccount(), AUTHORITY, new Bundle());
+ // Do a sync everytime we get here!
+ requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.CONTRIBUTION_AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
- allContributions = getContentResolver().query(BASE_URI, ALL_FIELDS,
- CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
+ allContributions = contributionDao.loadAllContributions();
getSupportLoaderManager().initLoader(0, null, this);
}
@@ -137,17 +126,20 @@ public class ContributionsActivity extends AuthenticatedActivity
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
FragmentManager supportFragmentManager = getSupportFragmentManager();
- contributionsList = (ContributionsListFragment) supportFragmentManager
+ contributionsList = (ContributionsListFragment)supportFragmentManager
.findFragmentById(R.id.contributionsListFragment);
supportFragmentManager.addOnBackStackChangedListener(this);
if (savedInstanceState != null) {
- mediaDetails = (MediaDetailPagerFragment) supportFragmentManager
+ mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
.findFragmentById(R.id.contributionsFragmentContainer);
+
+ getSupportLoaderManager().initLoader(0, null, this);
}
requestAuthToken();
initDrawer();
setTitle(getString(R.string.title_activity_contributions));
+ setUploadCount();
}
@Override
@@ -178,24 +170,23 @@ public class ContributionsActivity extends AuthenticatedActivity
public void retryUpload(int i) {
allContributions.moveToPosition(i);
- Contribution c = Contribution.fromCursor(allContributions);
+ Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
- Timber.d("Restarting for %s", c.toContentValues());
+ Timber.d("Restarting for %s", c.toString());
} else {
- Timber.d("Skipping re-upload for non-failed %s", c.toContentValues());
+ Timber.d("Skipping re-upload for non-failed %s", c.toString());
}
}
public void deleteUpload(int i) {
allContributions.moveToPosition(i);
- Contribution c = Contribution.fromCursor(allContributions);
+ Contribution c = contributionDao.fromCursor(allContributions);
if (c.getState() == STATE_FAILED) {
- Timber.d("Deleting failed contrib %s", c.toContentValues());
- c.setContentProviderClient(getContentResolver().acquireContentProviderClient(AUTHORITY));
- c.delete();
+ Timber.d("Deleting failed contrib %s", c.toString());
+ contributionDao.delete(c);
} else {
- Timber.d("Skipping deletion for non-failed contrib %s", c.toContentValues());
+ Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
}
}
@@ -229,24 +220,23 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
public Loader onCreateLoader(int i, Bundle bundle) {
- SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
- int uploads = sharedPref.getInt(UPLOADS_SHOWING, 100);
+ int uploads = prefs.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(this, BASE_URI,
- ALL_FIELDS, CONTRIBUTION_SELECTION, null,
- CONTRIBUTION_SORT + "LIMIT " + uploads);
+ ALL_FIELDS, "", null,
+ ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads);
}
@Override
public void onLoadFinished(Loader cursorLoader, Cursor cursor) {
+ contributionsList.changeProgressBarVisibility(false);
+
if (contributionsList.getAdapter() == null) {
contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(),
- cursor, 0));
+ cursor, 0, contributionDao));
} else {
((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor);
}
- setUploadCount();
-
contributionsList.clearSyncMessage();
notifyAndMigrateDataSetObservers();
}
@@ -263,7 +253,7 @@ public class ContributionsActivity extends AuthenticatedActivity
// not yet ready to return data
return null;
} else {
- return Contribution.fromCursor((Cursor) contributionsList.getAdapter().getItem(i));
+ return contributionDao.fromCursor((Cursor) contributionsList.getAdapter().getItem(i));
}
}
@@ -277,19 +267,16 @@ public class ContributionsActivity extends AuthenticatedActivity
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
- CommonsApplication app = ((CommonsApplication) getApplication());
- compositeDisposable.add(
- app.getMWApi()
- .getUploadCount(app.getCurrentAccount().name)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- uploadCount -> getSupportActionBar().setSubtitle(getResources()
- .getQuantityString(R.plurals.contributions_subtitle,
- uploadCount, uploadCount)),
- t -> Timber.e(t, "Fetching upload count failed")
- )
- );
+ compositeDisposable.add(mediaWikiApi
+ .getUploadCount(sessionManager.getCurrentAccount().name)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ uploadCount -> getSupportActionBar().setSubtitle(getResources()
+ .getQuantityString(R.plurals.contributions_subtitle,
+ uploadCount, uploadCount)),
+ t -> Timber.e(t, "Fetching upload count failed")
+ ));
}
@Override
@@ -341,9 +328,4 @@ public class ContributionsActivity extends AuthenticatedActivity
public void refreshSource() {
getSupportLoaderManager().restartLoader(0, null, this);
}
-
- public static void startYourself(Context context) {
- context.startActivity(new Intent(context, ContributionsActivity.class));
- }
-
}
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java
index aae9fa3bc..0a68ac626 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContentProvider.java
@@ -1,6 +1,5 @@
package fr.free.nrw.commons.contributions;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
@@ -10,36 +9,36 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH;
-import static fr.free.nrw.commons.contributions.Contribution.Table.ALL_FIELDS;
-import static fr.free.nrw.commons.contributions.Contribution.Table.TABLE_NAME;
+import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
+import static fr.free.nrw.commons.contributions.ContributionDao.Table.TABLE_NAME;
-public class ContributionsContentProvider extends ContentProvider {
+public class ContributionsContentProvider extends CommonsDaggerContentProvider {
private static final int CONTRIBUTIONS = 1;
private static final int CONTRIBUTIONS_ID = 2;
private static final String BASE_PATH = "contributions";
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
- public static final String AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
+ public static final String CONTRIBUTION_AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
- public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
+ public static final Uri BASE_URI = Uri.parse("content://" + CONTRIBUTION_AUTHORITY + "/" + BASE_PATH);
static {
- uriMatcher.addURI(AUTHORITY, BASE_PATH, CONTRIBUTIONS);
- uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID);
+ uriMatcher.addURI(CONTRIBUTION_AUTHORITY, BASE_PATH, CONTRIBUTIONS);
+ uriMatcher.addURI(CONTRIBUTION_AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID);
}
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
- @Override
- public boolean onCreate() {
- return false;
- }
+ @Inject DBOpenHelper dbOpenHelper;
@SuppressWarnings("ConstantConditions")
@Override
@@ -50,8 +49,7 @@ public class ContributionsContentProvider extends ContentProvider {
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase db = app.getDBOpenHelper().getReadableDatabase();
+ SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch (uriType) {
@@ -87,8 +85,7 @@ public class ContributionsContentProvider extends ContentProvider {
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id;
switch (uriType) {
case CONTRIBUTIONS:
@@ -107,13 +104,12 @@ public class ContributionsContentProvider extends ContentProvider {
int rows;
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (uriType) {
case CONTRIBUTIONS_ID:
Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
- rows = sqlDB.delete(TABLE_NAME,
+ rows = db.delete(TABLE_NAME,
"_id = ?",
new String[]{uri.getLastPathSegment()}
);
@@ -130,8 +126,7 @@ public class ContributionsContentProvider extends ContentProvider {
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case CONTRIBUTIONS:
@@ -162,8 +157,7 @@ public class ContributionsContentProvider extends ContentProvider {
error out otherwise.
*/
int uriType = uriMatcher.match(uri);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- SQLiteDatabase sqlDB = app.getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
switch (uriType) {
case CONTRIBUTIONS:
@@ -175,7 +169,7 @@ public class ContributionsContentProvider extends ContentProvider {
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
- Contribution.Table.COLUMN_ID + " = ?",
+ ContributionDao.Table.COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java
index 0882d09c4..a31caf54f 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java
@@ -11,8 +11,11 @@ import fr.free.nrw.commons.R;
class ContributionsListAdapter extends CursorAdapter {
- public ContributionsListAdapter(Context context, Cursor c, int flags) {
+ private final ContributionDao contributionDao;
+
+ public ContributionsListAdapter(Context context, Cursor c, int flags, ContributionDao contributionDao) {
super(context, c, flags);
+ this.contributionDao = contributionDao;
}
@Override
@@ -26,7 +29,7 @@ class ContributionsListAdapter extends CursorAdapter {
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
- final Contribution contribution = Contribution.fromCursor(cursor);
+ final Contribution contribution = contributionDao.fromCursor(cursor);
views.imageView.setMedia(contribution);
views.titleView.setText(contribution.getDisplayTitle());
@@ -34,7 +37,7 @@ class ContributionsListAdapter extends CursorAdapter {
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
views.seqNumView.setVisibility(View.VISIBLE);
- switch(contribution.getState()) {
+ switch (contribution.getState()) {
case Contribution.STATE_COMPLETED:
views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.GONE);
@@ -50,7 +53,7 @@ class ContributionsListAdapter extends CursorAdapter {
views.progressView.setVisibility(View.VISIBLE);
long total = contribution.getDataLength();
long transferred = contribution.getTransferred();
- if(transferred == 0 || transferred >= total) {
+ if (transferred == 0 || transferred >= total) {
views.progressView.setIndeterminate(true);
} else {
views.progressView.setProgress((int)(((double)transferred / (double)total) * 100));
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
index 83dbced97..25bf6eb93 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java
@@ -5,9 +5,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
-import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
@@ -19,30 +17,43 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
+import android.widget.ProgressBar;
import android.widget.TextView;
+import java.util.Arrays;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.nearby.NearbyActivity;
import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.Activity.RESULT_OK;
-import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.View.GONE;
-public class ContributionsListFragment extends Fragment {
+public class ContributionsListFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.contributionsList)
GridView contributionsList;
@BindView(R.id.waitingMessage)
TextView waitingMessage;
- @BindView(R.id.emptyMessage)
- TextView emptyMessage;
+ @BindView(R.id.loadingContributionsProgressBar)
+ ProgressBar progressBar;
+
+ @Inject
+ @Named("prefs")
+ SharedPreferences prefs;
+ @Inject
+ @Named("default_preferences")
+ SharedPreferences defaultPrefs;
+
private ContributionController controller;
@Override
@@ -57,7 +68,6 @@ public class ContributionsListFragment extends Fragment {
}
//TODO: Should this be in onResume?
- SharedPreferences prefs = getActivity().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Timber.d("Last Sync Timestamp: %s", lastModified);
@@ -67,6 +77,7 @@ public class ContributionsListFragment extends Fragment {
waitingMessage.setVisibility(GONE);
}
+ changeProgressBarVisibility(true);
return v;
}
@@ -78,6 +89,10 @@ public class ContributionsListFragment extends Fragment {
this.contributionsList.setAdapter(adapter);
}
+ public void changeProgressBarVisibility(boolean isVisible) {
+ this.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE);
+ }
+
@Override
public void onSaveInstanceState(Bundle outState) {
if (outState == null) {
@@ -155,9 +170,7 @@ public class ContributionsListFragment extends Fragment {
return true;
case R.id.menu_from_camera:
- SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
- boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
+ boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE)
@@ -201,7 +214,7 @@ public class ContributionsListFragment extends Fragment {
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = "
- + permissions + " grant =" + grantResults);
+ + Arrays.toString(permissions) + " grant =" + Arrays.toString(grantResults));
switch (requestCode) {
// 1 = Storage allowed when gallery selected
@@ -235,12 +248,17 @@ public class ContributionsListFragment extends Fragment {
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu);
- CommonsApplication app = (CommonsApplication) getContext().getApplicationContext();
- if (!app.deviceHasCamera()) {
+ if (!deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false);
}
}
+ public boolean deviceHasCamera() {
+ PackageManager pm = getContext().getPackageManager();
+ return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
+ pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java
index fdcc52380..aedc3f789 100644
--- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsSyncAdapter.java
@@ -13,21 +13,27 @@ import android.os.RemoteException;
import android.text.TextUtils;
import java.io.IOException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.inject.Inject;
+import javax.inject.Named;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.LogEventResult;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
-import static android.content.Context.MODE_PRIVATE;
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
-import static fr.free.nrw.commons.contributions.Contribution.Table.COLUMN_FILENAME;
+import static fr.free.nrw.commons.contributions.ContributionDao.Table.COLUMN_FILENAME;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
+@SuppressWarnings("WeakerAccess")
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String[] existsQuery = {COLUMN_FILENAME};
@@ -35,6 +41,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final ContentValues[] EMPTY = {};
private static int COMMIT_THRESHOLD = 10;
+ @SuppressWarnings("WeakerAccess")
+ @Inject MediaWikiApi mwApi;
+ @Inject @Named("prefs") SharedPreferences prefs;
+
public ContributionsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@@ -47,6 +57,9 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
}
private boolean fileExists(ContentProviderClient client, String filename) {
+ if (filename == null) {
+ return false;
+ }
Cursor cursor = null;
try {
cursor = client.query(BASE_URI,
@@ -68,19 +81,23 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void onPerformSync(Account account, Bundle bundle, String authority,
ContentProviderClient contentProviderClient, SyncResult syncResult) {
+ ApplicationlessInjection
+ .getInstance(getContext()
+ .getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
String user = account.name;
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- SharedPreferences prefs = getContext().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Date curTime = new Date();
LogEventResult result;
Boolean done = false;
String queryContinue = null;
+ ContributionDao contributionDao = new ContributionDao(() -> contentProviderClient);
while (!done) {
try {
- result = api.logEvents(user, lastModified, queryContinue, getLimit());
+ result = mwApi.logEvents(user, lastModified, queryContinue, getLimit());
} catch (IOException e) {
// There isn't really much we can do, eh?
// FIXME: Perhaps add EventLogging?
@@ -109,7 +126,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
"", -1, dateUpdated, dateUpdated, user,
"", "");
contrib.setState(STATE_COMPLETED);
- imageValues.add(contrib.toContentValues());
+ imageValues.add(contributionDao.toContentValues(contrib));
if (imageValues.size() % COMMIT_THRESHOLD == 0) {
try {
@@ -134,8 +151,13 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
done = true;
}
}
- prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
+ prefs.edit().putString("lastSyncTimestamp", toMWDate(curTime)).apply();
Timber.d("Oh hai, everyone! Look, a kitty!");
}
+ private String toMWDate(Date date) {
+ SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
+ isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return isoFormat.format(date);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
index 8ef9f336c..35305c5ba 100644
--- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
+++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java
@@ -4,16 +4,18 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import fr.free.nrw.commons.contributions.Contribution;
-import fr.free.nrw.commons.modifications.ModifierSequence;
+import fr.free.nrw.commons.category.CategoryDao;
+import fr.free.nrw.commons.contributions.ContributionDao;
+import fr.free.nrw.commons.modifications.ModifierSequenceDao;
-public class DBOpenHelper extends SQLiteOpenHelper{
+public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
private static final int DATABASE_VERSION = 6;
/**
- * Do not use, please call CommonsApplication.getDBOpenHelper()
+ * Do not use directly - @Inject an instance where it's needed and let
+ * dependency injection take care of managing this as a singleton.
*/
public DBOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -21,15 +23,15 @@ public class DBOpenHelper extends SQLiteOpenHelper{
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
- Contribution.Table.onCreate(sqLiteDatabase);
- ModifierSequence.Table.onCreate(sqLiteDatabase);
- Category.Table.onCreate(sqLiteDatabase);
+ ContributionDao.Table.onCreate(sqLiteDatabase);
+ ModifierSequenceDao.Table.onCreate(sqLiteDatabase);
+ CategoryDao.Table.onCreate(sqLiteDatabase);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
- Contribution.Table.onUpdate(sqLiteDatabase, from, to);
- ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to);
- Category.Table.onUpdate(sqLiteDatabase, from, to);
+ ContributionDao.Table.onUpdate(sqLiteDatabase, from, to);
+ ModifierSequenceDao.Table.onUpdate(sqLiteDatabase, from, to);
+ CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
new file mode 100644
index 000000000..e4fb13427
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java
@@ -0,0 +1,49 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.AboutActivity;
+import fr.free.nrw.commons.WelcomeActivity;
+import fr.free.nrw.commons.auth.LoginActivity;
+import fr.free.nrw.commons.auth.SignupActivity;
+import fr.free.nrw.commons.contributions.ContributionsActivity;
+import fr.free.nrw.commons.nearby.NearbyActivity;
+import fr.free.nrw.commons.notification.NotificationActivity;
+import fr.free.nrw.commons.settings.SettingsActivity;
+import fr.free.nrw.commons.upload.MultipleShareActivity;
+import fr.free.nrw.commons.upload.ShareActivity;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class ActivityBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract LoginActivity bindLoginActivity();
+
+ @ContributesAndroidInjector
+ abstract WelcomeActivity bindWelcomeActivity();
+
+ @ContributesAndroidInjector
+ abstract ShareActivity bindShareActivity();
+
+ @ContributesAndroidInjector
+ abstract MultipleShareActivity bindMultipleShareActivity();
+
+ @ContributesAndroidInjector
+ abstract ContributionsActivity bindContributionsActivity();
+
+ @ContributesAndroidInjector
+ abstract SettingsActivity bindSettingsActivity();
+
+ @ContributesAndroidInjector
+ abstract AboutActivity bindAboutActivity();
+
+ @ContributesAndroidInjector
+ abstract SignupActivity bindSignupActivity();
+
+ @ContributesAndroidInjector
+ abstract NearbyActivity bindNearbyActivity();
+
+ @ContributesAndroidInjector
+ abstract NotificationActivity bindNotificationActivity();
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ApplicationlessInjection.java b/app/src/main/java/fr/free/nrw/commons/di/ApplicationlessInjection.java
new file mode 100644
index 000000000..2faf09b83
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ApplicationlessInjection.java
@@ -0,0 +1,93 @@
+package fr.free.nrw.commons.di;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.support.v4.app.Fragment;
+
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjector;
+import dagger.android.DispatchingAndroidInjector;
+import dagger.android.HasActivityInjector;
+import dagger.android.HasBroadcastReceiverInjector;
+import dagger.android.HasContentProviderInjector;
+import dagger.android.HasFragmentInjector;
+import dagger.android.HasServiceInjector;
+import dagger.android.support.HasSupportFragmentInjector;
+
+public class ApplicationlessInjection
+ implements
+ HasActivityInjector,
+ HasFragmentInjector,
+ HasSupportFragmentInjector,
+ HasServiceInjector,
+ HasBroadcastReceiverInjector,
+ HasContentProviderInjector {
+
+ private static ApplicationlessInjection instance = null;
+
+ @Inject DispatchingAndroidInjector activityInjector;
+ @Inject DispatchingAndroidInjector broadcastReceiverInjector;
+ @Inject DispatchingAndroidInjector fragmentInjector;
+ @Inject DispatchingAndroidInjector supportFragmentInjector;
+ @Inject DispatchingAndroidInjector serviceInjector;
+ @Inject DispatchingAndroidInjector contentProviderInjector;
+
+ private CommonsApplicationComponent commonsApplicationComponent;
+
+ public ApplicationlessInjection(Context applicationContext) {
+ commonsApplicationComponent = DaggerCommonsApplicationComponent.builder()
+ .appModule(new CommonsApplicationModule(applicationContext)).build();
+ commonsApplicationComponent.inject(this);
+ }
+
+ @Override
+ public DispatchingAndroidInjector activityInjector() {
+ return activityInjector;
+ }
+
+ @Override
+ public DispatchingAndroidInjector fragmentInjector() {
+ return fragmentInjector;
+ }
+
+ @Override
+ public DispatchingAndroidInjector supportFragmentInjector() {
+ return supportFragmentInjector;
+ }
+
+ @Override
+ public DispatchingAndroidInjector broadcastReceiverInjector() {
+ return broadcastReceiverInjector;
+ }
+
+ @Override
+ public DispatchingAndroidInjector serviceInjector() {
+ return serviceInjector;
+ }
+
+ @Override
+ public AndroidInjector contentProviderInjector() {
+ return contentProviderInjector;
+ }
+
+ public CommonsApplicationComponent getCommonsApplicationComponent() {
+ return commonsApplicationComponent;
+ }
+
+ public static ApplicationlessInjection getInstance(Context applicationContext) {
+ if (instance == null) {
+ synchronized (ApplicationlessInjection.class) {
+ if (instance == null) {
+ instance = new ApplicationlessInjection(applicationContext);
+ }
+ }
+ }
+
+ return instance;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
new file mode 100644
index 000000000..f5974d519
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java
@@ -0,0 +1,49 @@
+package fr.free.nrw.commons.di;
+
+import javax.inject.Singleton;
+
+import dagger.Component;
+import dagger.android.AndroidInjectionModule;
+import dagger.android.AndroidInjector;
+import dagger.android.support.AndroidSupportInjectionModule;
+import fr.free.nrw.commons.CommonsApplication;
+import fr.free.nrw.commons.MediaWikiImageView;
+import fr.free.nrw.commons.auth.LoginActivity;
+import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
+import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
+import fr.free.nrw.commons.settings.SettingsFragment;
+
+@Singleton
+@Component(modules = {
+ CommonsApplicationModule.class,
+ AndroidInjectionModule.class,
+ AndroidSupportInjectionModule.class,
+ ActivityBuilderModule.class,
+ FragmentBuilderModule.class,
+ ServiceBuilderModule.class,
+ ContentProviderBuilderModule.class
+})
+public interface CommonsApplicationComponent extends AndroidInjector {
+ void inject(CommonsApplication application);
+
+ void inject(ContributionsSyncAdapter syncAdapter);
+
+ void inject(ModificationsSyncAdapter syncAdapter);
+
+ void inject(MediaWikiImageView mediaWikiImageView);
+
+ void inject(LoginActivity activity);
+
+ void inject(SettingsFragment fragment);
+
+ @Override
+ void inject(ApplicationlessInjection instance);
+
+ @Component.Builder
+ @SuppressWarnings({"WeakerAccess", "unused"})
+ interface Builder {
+ Builder appModule(CommonsApplicationModule applicationModule);
+
+ CommonsApplicationComponent build();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
new file mode 100644
index 000000000..efc9c29b6
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.java
@@ -0,0 +1,139 @@
+package fr.free.nrw.commons.di;
+
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.support.v4.util.LruCache;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.CommonsApplication;
+import fr.free.nrw.commons.auth.AccountUtil;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.caching.CacheController;
+import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.location.LocationServiceManager;
+import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+import fr.free.nrw.commons.nearby.NearbyPlaces;
+import fr.free.nrw.commons.upload.UploadController;
+
+import static android.content.Context.MODE_PRIVATE;
+import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
+import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MODIFICATIONS_AUTHORITY;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class CommonsApplicationModule {
+ public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
+ public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
+
+ private CommonsApplication application;
+ private Context applicationContext;
+
+ public CommonsApplicationModule(Context applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ @Provides
+ public Context providesApplicationContext() {
+ return this.applicationContext;
+ }
+
+ @Provides
+ public AccountUtil providesAccountUtil(Context context) {
+ return new AccountUtil(context);
+ }
+
+ @Provides
+ @Named("category")
+ public ContentProviderClient provideCategoryContentProviderClient(Context context) {
+ return context.getContentResolver().acquireContentProviderClient(CATEGORY_AUTHORITY);
+ }
+
+ @Provides
+ @Named("contribution")
+ public ContentProviderClient provideContributionContentProviderClient(Context context) {
+ return context.getContentResolver().acquireContentProviderClient(CONTRIBUTION_AUTHORITY);
+ }
+
+ @Provides
+ @Named("modification")
+ public ContentProviderClient provideModificationContentProviderClient(Context context) {
+ return context.getContentResolver().acquireContentProviderClient(MODIFICATIONS_AUTHORITY);
+ }
+
+ @Provides
+ @Named("application_preferences")
+ public SharedPreferences providesApplicationSharedPreferences(Context context) {
+ return context.getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
+ }
+
+ @Provides
+ @Named("default_preferences")
+ public SharedPreferences providesDefaultSharedPreferences(Context context) {
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ @Provides
+ @Named("prefs")
+ public SharedPreferences providesOtherSharedPreferences(Context context) {
+ return context.getSharedPreferences("prefs", MODE_PRIVATE);
+ }
+
+ @Provides
+ public UploadController providesUploadController(Context context,
+ SessionManager sessionManager,
+ @Named("default_preferences") SharedPreferences sharedPreferences) {
+ return new UploadController(sessionManager, context, sharedPreferences);
+ }
+
+ @Provides
+ @Singleton
+ public SessionManager providesSessionManager(Context context,
+ MediaWikiApi mediaWikiApi,
+ @Named("default_preferences") SharedPreferences sharedPreferences) {
+ return new SessionManager(context, mediaWikiApi, sharedPreferences);
+ }
+
+ @Provides
+ @Singleton
+ public MediaWikiApi provideMediaWikiApi(Context context, @Named("default_preferences") SharedPreferences sharedPreferences) {
+ return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, sharedPreferences);
+ }
+
+ @Provides
+ @Singleton
+ public LocationServiceManager provideLocationServiceManager(Context context) {
+ return new LocationServiceManager(context);
+ }
+
+ @Provides
+ @Singleton
+ public CacheController provideCacheController() {
+ return new CacheController();
+ }
+
+ @Provides
+ @Singleton
+ public DBOpenHelper provideDBOpenHelper(Context context) {
+ return new DBOpenHelper(context);
+ }
+
+ @Provides
+ @Singleton
+ public NearbyPlaces provideNearbyPlaces() {
+ return new NearbyPlaces();
+ }
+
+ @Provides
+ @Singleton
+ public LruCache provideLruCache() {
+ return new LruCache<>(1024);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerAppCompatActivity.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerAppCompatActivity.java
new file mode 100644
index 000000000..9f06725de
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerAppCompatActivity.java
@@ -0,0 +1,43 @@
+package fr.free.nrw.commons.di;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.AppCompatActivity;
+
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjector;
+import dagger.android.DispatchingAndroidInjector;
+import dagger.android.support.HasSupportFragmentInjector;
+
+public abstract class CommonsDaggerAppCompatActivity extends AppCompatActivity implements HasSupportFragmentInjector {
+
+ @Inject
+ DispatchingAndroidInjector supportFragmentInjector;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ inject();
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public AndroidInjector supportFragmentInjector() {
+ return supportFragmentInjector;
+ }
+
+ private void inject() {
+ ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext());
+
+ AndroidInjector activityInjector = injection.activityInjector();
+
+ if (activityInjector == null) {
+ throw new NullPointerException("ApplicationlessInjection.activityInjector() returned null");
+ }
+
+ activityInjector.inject(this);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerBroadcastReceiver.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerBroadcastReceiver.java
new file mode 100644
index 000000000..3c4cb9914
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerBroadcastReceiver.java
@@ -0,0 +1,31 @@
+package fr.free.nrw.commons.di;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import dagger.android.AndroidInjector;
+
+public abstract class CommonsDaggerBroadcastReceiver extends BroadcastReceiver {
+
+ public CommonsDaggerBroadcastReceiver() {
+ super();
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ inject(context);
+ }
+
+ private void inject(Context context) {
+ ApplicationlessInjection injection = ApplicationlessInjection.getInstance(context.getApplicationContext());
+
+ AndroidInjector serviceInjector = injection.broadcastReceiverInjector();
+
+ if (serviceInjector == null) {
+ throw new NullPointerException("ApplicationlessInjection.broadcastReceiverInjector() returned null");
+ }
+ serviceInjector.inject(this);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.java
new file mode 100644
index 000000000..38506c4ca
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerContentProvider.java
@@ -0,0 +1,32 @@
+package fr.free.nrw.commons.di;
+
+import android.content.ContentProvider;
+
+import dagger.android.AndroidInjector;
+
+
+public abstract class CommonsDaggerContentProvider extends ContentProvider {
+
+ public CommonsDaggerContentProvider() {
+ super();
+ }
+
+ @Override
+ public boolean onCreate() {
+ inject();
+ return true;
+ }
+
+ private void inject() {
+ ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getContext());
+
+ AndroidInjector serviceInjector = injection.contentProviderInjector();
+
+ if (serviceInjector == null) {
+ throw new NullPointerException("ApplicationlessInjection.contentProviderInjector() returned null");
+ }
+
+ serviceInjector.inject(this);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerIntentService.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerIntentService.java
new file mode 100644
index 000000000..995c517a1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerIntentService.java
@@ -0,0 +1,32 @@
+package fr.free.nrw.commons.di;
+
+import android.app.IntentService;
+import android.app.Service;
+
+import dagger.android.AndroidInjector;
+
+public abstract class CommonsDaggerIntentService extends IntentService {
+
+ public CommonsDaggerIntentService(String name) {
+ super(name);
+ }
+
+ @Override
+ public void onCreate() {
+ inject();
+ super.onCreate();
+ }
+
+ private void inject() {
+ ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext());
+
+ AndroidInjector serviceInjector = injection.serviceInjector();
+
+ if (serviceInjector == null) {
+ throw new NullPointerException("ApplicationlessInjection.serviceInjector() returned null");
+ }
+
+ serviceInjector.inject(this);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerService.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerService.java
new file mode 100644
index 000000000..dc6c10b9f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerService.java
@@ -0,0 +1,31 @@
+package fr.free.nrw.commons.di;
+
+import android.app.Service;
+
+import dagger.android.AndroidInjector;
+
+public abstract class CommonsDaggerService extends Service {
+
+ public CommonsDaggerService() {
+ super();
+ }
+
+ @Override
+ public void onCreate() {
+ inject();
+ super.onCreate();
+ }
+
+ private void inject() {
+ ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext());
+
+ AndroidInjector serviceInjector = injection.serviceInjector();
+
+ if (serviceInjector == null) {
+ throw new NullPointerException("ApplicationlessInjection.serviceInjector() returned null");
+ }
+
+ serviceInjector.inject(this);
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerSupportFragment.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerSupportFragment.java
new file mode 100644
index 000000000..8c33e7a98
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsDaggerSupportFragment.java
@@ -0,0 +1,65 @@
+package fr.free.nrw.commons.di;
+
+import android.app.Activity;
+import android.content.Context;
+import android.support.v4.app.Fragment;
+
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjector;
+import dagger.android.DispatchingAndroidInjector;
+import dagger.android.support.HasSupportFragmentInjector;
+
+public abstract class CommonsDaggerSupportFragment extends Fragment implements HasSupportFragmentInjector {
+
+ @Inject
+ DispatchingAndroidInjector childFragmentInjector;
+
+ @Override
+ public void onAttach(Context context) {
+ inject();
+ super.onAttach(context);
+ }
+
+ @Override
+ public AndroidInjector supportFragmentInjector() {
+ return childFragmentInjector;
+ }
+
+
+ public void inject() {
+ HasSupportFragmentInjector hasSupportFragmentInjector = findHasFragmentInjector();
+
+ AndroidInjector fragmentInjector = hasSupportFragmentInjector.supportFragmentInjector();
+
+ if (fragmentInjector == null) {
+ throw new NullPointerException(String.format("%s.supportFragmentInjector() returned null", hasSupportFragmentInjector.getClass().getCanonicalName()));
+ }
+
+ fragmentInjector.inject(this);
+ }
+
+ private HasSupportFragmentInjector findHasFragmentInjector() {
+ Fragment parentFragment = this;
+
+ while ((parentFragment = parentFragment.getParentFragment()) != null) {
+ if (parentFragment instanceof HasSupportFragmentInjector) {
+ return (HasSupportFragmentInjector) parentFragment;
+ }
+ }
+
+ Activity activity = getActivity();
+
+ if (activity instanceof HasSupportFragmentInjector) {
+ return (HasSupportFragmentInjector) activity;
+ }
+
+ ApplicationlessInjection injection = ApplicationlessInjection.getInstance(activity.getApplicationContext());
+ if (injection != null) {
+ return injection;
+ }
+
+ throw new IllegalArgumentException(String.format("No injector was found for %s", getClass().getCanonicalName()));
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ContentProviderBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ContentProviderBuilderModule.java
new file mode 100644
index 000000000..f18c331c5
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ContentProviderBuilderModule.java
@@ -0,0 +1,22 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.category.CategoryContentProvider;
+import fr.free.nrw.commons.contributions.ContributionsContentProvider;
+import fr.free.nrw.commons.modifications.ModificationsContentProvider;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class ContentProviderBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract ContributionsContentProvider bindContributionsContentProvider();
+
+ @ContributesAndroidInjector
+ abstract ModificationsContentProvider bindModificationsContentProvider();
+
+ @ContributesAndroidInjector
+ abstract CategoryContentProvider bindCategoryContentProvider();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
new file mode 100644
index 000000000..ca7340cb1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.java
@@ -0,0 +1,46 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.category.CategorizationFragment;
+import fr.free.nrw.commons.contributions.ContributionsListFragment;
+import fr.free.nrw.commons.media.MediaDetailFragment;
+import fr.free.nrw.commons.media.MediaDetailPagerFragment;
+import fr.free.nrw.commons.nearby.NearbyListFragment;
+import fr.free.nrw.commons.nearby.NoPermissionsFragment;
+import fr.free.nrw.commons.settings.SettingsFragment;
+import fr.free.nrw.commons.upload.MultipleUploadListFragment;
+import fr.free.nrw.commons.upload.SingleUploadFragment;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class FragmentBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract CategorizationFragment bindCategorizationFragment();
+
+ @ContributesAndroidInjector
+ abstract ContributionsListFragment bindContributionsListFragment();
+
+ @ContributesAndroidInjector
+ abstract MediaDetailFragment bindMediaDetailFragment();
+
+ @ContributesAndroidInjector
+ abstract MediaDetailPagerFragment bindMediaDetailPagerFragment();
+
+ @ContributesAndroidInjector
+ abstract NearbyListFragment bindNearbyListFragment();
+
+ @ContributesAndroidInjector
+ abstract NoPermissionsFragment bindNoPermissionsFragment();
+
+ @ContributesAndroidInjector
+ abstract SettingsFragment bindSettingsFragment();
+
+ @ContributesAndroidInjector
+ abstract MultipleUploadListFragment bindMultipleUploadListFragment();
+
+ @ContributesAndroidInjector
+ abstract SingleUploadFragment bindSingleUploadFragment();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/di/ServiceBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ServiceBuilderModule.java
new file mode 100644
index 000000000..2d4072d15
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/di/ServiceBuilderModule.java
@@ -0,0 +1,18 @@
+package fr.free.nrw.commons.di;
+
+import dagger.Module;
+import dagger.android.ContributesAndroidInjector;
+import fr.free.nrw.commons.auth.WikiAccountAuthenticatorService;
+import fr.free.nrw.commons.upload.UploadService;
+
+@Module
+@SuppressWarnings({"WeakerAccess", "unused"})
+public abstract class ServiceBuilderModule {
+
+ @ContributesAndroidInjector
+ abstract UploadService bindUploadService();
+
+ @ContributesAndroidInjector
+ abstract WikiAccountAuthenticatorService bindWikiAccountAuthenticatorService();
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java
index 1e3202914..494cce077 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LatLng.java
+++ b/app/src/main/java/fr/free/nrw/commons/location/LatLng.java
@@ -1,125 +1,156 @@
-package fr.free.nrw.commons.location;
-
-public class LatLng {
-
- private final double latitude;
- private final double longitude;
- private final float accuracy;
-
- /** Accepts latitude and longitude.
- * North and South values are cut off at 90°
- *
- * @param latitude double value
- * @param longitude double value
- */
- public LatLng(double latitude, double longitude, float accuracy) {
- if(-180.0D <= longitude && longitude < 180.0D) {
- this.longitude = longitude;
- } else {
- this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D;
- }
- this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude));
- this.accuracy = accuracy;
- }
-
- public int hashCode() {
- boolean var1 = true;
- byte var2 = 1;
- long var3 = Double.doubleToLongBits(this.latitude);
- int var5 = 31 * var2 + (int)(var3 ^ var3 >>> 32);
- var3 = Double.doubleToLongBits(this.longitude);
- var5 = 31 * var5 + (int)(var3 ^ var3 >>> 32);
- return var5;
- }
-
- public boolean equals(Object o) {
- if(this == o) {
- return true;
- } else if(!(o instanceof LatLng)) {
- return false;
- } else {
- LatLng var2 = (LatLng)o;
- return Double.doubleToLongBits(this.latitude) == Double.doubleToLongBits(var2.latitude) && Double.doubleToLongBits(this.longitude) == Double.doubleToLongBits(var2.longitude);
- }
- }
-
- public String toString() {
- return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
- }
-
- /**
- * Rounds the float to 4 digits and returns absolute value.
- *
- * @param coordinate A coordinate value as string.
- * @return String of the rounded number.
- */
- private String formatCoordinate(double coordinate) {
- double roundedNumber = Math.round(coordinate * 10000d) / 10000d;
- double absoluteNumber = Math.abs(roundedNumber);
- return String.valueOf(absoluteNumber);
- }
-
- /**
- * Returns "N" or "S" depending on the latitude.
- *
- * @return "N" or "S".
- */
- private String getNorthSouth() {
- if (this.latitude < 0) {
- return "S";
- }
-
- return "N";
- }
-
- /**
- * Returns "E" or "W" depending on the longitude.
- *
- * @return "E" or "W".
- */
- private String getEastWest() {
- if (this.longitude >= 0 && this.longitude < 180) {
- return "E";
- }
-
- return "W";
- }
-
- /**
- * Returns a nicely formatted coordinate string. Used e.g. in
- * the detail view.
- *
- * @return The formatted string.
- */
- public String getPrettyCoordinateString() {
- return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", "
- + formatCoordinate(this.longitude) + " " + this.getEastWest();
- }
-
- /**
- * Return the location accuracy in meter.
- *
- * @return float
- */
- public float getAccuracy() {
- return accuracy;
- }
-
- /**
- * Return the longitude in degrees.
- *
- * @return double
- */
- public double getLongitude() {
- return longitude;
- }
-
- /**
- * Return the latitude in degrees.
- *
- * @return double
- */
- public double getLatitude() {
- return latitude;
- }
-}
+package fr.free.nrw.commons.location;
+
+import android.location.Location;
+import android.support.annotation.NonNull;
+
+/**
+ * a latitude and longitude point with accuracy information, often of a picture
+ */
+public class LatLng {
+
+ private final double latitude;
+ private final double longitude;
+ private final float accuracy;
+
+ /**
+ * Accepts latitude and longitude.
+ * North and South values are cut off at 90°
+ *
+ * @param latitude the latitude
+ * @param longitude the longitude
+ * @param accuracy the accuracy
+ *
+ * Examples:
+ * the Statue of Liberty is located at 40.69° N, 74.04° W
+ * The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0)
+ * where positive signifies north, east and negative signifies south, west.
+ */
+ public LatLng(double latitude, double longitude, float accuracy) {
+ if (-180.0D <= longitude && longitude < 180.0D) {
+ this.longitude = longitude;
+ } else {
+ this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D;
+ }
+ this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude));
+ this.accuracy = accuracy;
+ }
+
+ /**
+ * gets the latitude and longitude of a given non-null location
+ * @param location the non-null location of the user
+ * @return LatLng the Latitude and Longitude of a given location
+ */
+ public static LatLng from(@NonNull Location location) {
+ return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy());
+ }
+
+ /**
+ * creates a hash code for the longitude and longitude
+ */
+ public int hashCode() {
+ byte var1 = 1;
+ long var2 = Double.doubleToLongBits(this.latitude);
+ int var3 = 31 * var1 + (int)(var2 ^ var2 >>> 32);
+ var2 = Double.doubleToLongBits(this.longitude);
+ var3 = 31 * var3 + (int)(var2 ^ var2 >>> 32);
+ return var3;
+ }
+
+ /**
+ * checks for equality of two LatLng objects
+ * @param o the second LatLng object
+ */
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (!(o instanceof LatLng)) {
+ return false;
+ } else {
+ LatLng var2 = (LatLng)o;
+ return Double.doubleToLongBits(this.latitude) == Double.doubleToLongBits(var2.latitude) && Double.doubleToLongBits(this.longitude) == Double.doubleToLongBits(var2.longitude);
+ }
+ }
+
+ /**
+ * returns a string representation of the latitude and longitude
+ */
+ public String toString() {
+ return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
+ }
+
+ /**
+ * Rounds the float to 4 digits and returns absolute value.
+ *
+ * @param coordinate A coordinate value as string.
+ * @return String of the rounded number.
+ */
+ private String formatCoordinate(double coordinate) {
+ double roundedNumber = Math.round(coordinate * 10000d) / 10000d;
+ double absoluteNumber = Math.abs(roundedNumber);
+ return String.valueOf(absoluteNumber);
+ }
+
+ /**
+ * Returns "N" or "S" depending on the latitude.
+ *
+ * @return "N" or "S".
+ */
+ private String getNorthSouth() {
+ if (this.latitude < 0) {
+ return "S";
+ }
+
+ return "N";
+ }
+
+ /**
+ * Returns "E" or "W" depending on the longitude.
+ *
+ * @return "E" or "W".
+ */
+ private String getEastWest() {
+ if (this.longitude >= 0 && this.longitude < 180) {
+ return "E";
+ }
+
+ return "W";
+ }
+
+ /**
+ * Returns a nicely formatted coordinate string. Used e.g. in
+ * the detail view.
+ *
+ * @return The formatted string.
+ */
+ public String getPrettyCoordinateString() {
+ return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", "
+ + formatCoordinate(this.longitude) + " " + this.getEastWest();
+ }
+
+ /**
+ * Return the location accuracy in meter.
+ *
+ * @return float
+ */
+ public float getAccuracy() {
+ return accuracy;
+ }
+
+ /**
+ * Return the longitude in degrees.
+ *
+ * @return double
+ */
+ public double getLongitude() {
+ return longitude;
+ }
+
+ /**
+ * Return the latitude in degrees.
+ *
+ * @return double
+ */
+ public double getLatitude() {
+ return latitude;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
index e87eaf92e..851114ef9 100644
--- a/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationServiceManager.java
@@ -1,62 +1,184 @@
package fr.free.nrw.commons.location;
+import android.Manifest;
+import android.app.Activity;
import android.content.Context;
-import android.location.Criteria;
+import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import timber.log.Timber;
public class LocationServiceManager implements LocationListener {
+ public static final int LOCATION_REQUEST = 1;
- private String provider;
+ private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 2 * 60 * 1000;
+ private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 10;
+
+ private Context context;
private LocationManager locationManager;
- private LatLng latestLocation;
- private Float latestLocationAccuracy;
+ private Location lastLocation;
+ private final List locationListeners = new CopyOnWriteArrayList<>();
+ private boolean isLocationManagerRegistered = false;
+ /**
+ * Constructs a new instance of LocationServiceManager.
+ *
+ * @param context the context
+ */
public LocationServiceManager(Context context) {
+ this.context = context;
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
- provider = locationManager.getBestProvider(new Criteria(), true);
- }
-
- public LatLng getLatestLocation() {
- return latestLocation;
}
/**
- * Returns the accuracy of the location. The measurement is
- * given as a radius in meter of 68 % confidence.
+ * Returns the current status of the GPS provider.
*
- * @return Float
+ * @return true if the GPS provider is enabled
*/
- public Float getLatestLocationAccuracy() {
- return latestLocationAccuracy;
+ public boolean isProviderEnabled() {
+ return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
- /** Registers a LocationManager to listen for current location.
+ /**
+ * Returns whether the location permission is granted.
+ *
+ * @return true if the location permission is granted
+ */
+ public boolean isLocationPermissionGranted() {
+ return ContextCompat.checkSelfPermission(context,
+ Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Requests the location permission to be granted.
+ *
+ * @param activity the activity
+ */
+ public void requestPermissions(Activity activity) {
+ if (activity.isFinishing()) {
+ return;
+ }
+ ActivityCompat.requestPermissions(activity,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ LOCATION_REQUEST);
+ }
+
+ public boolean isPermissionExplanationRequired(Activity activity) {
+ return !activity.isFinishing() &&
+ ActivityCompat.shouldShowRequestPermissionRationale(activity,
+ Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+
+ public LatLng getLastLocation() {
+ if (lastLocation == null) {
+ return null;
+ }
+ return LatLng.from(lastLocation);
+ }
+
+ /**
+ * Registers a LocationManager to listen for current location.
*/
public void registerLocationManager() {
+ if (!isLocationManagerRegistered)
+ isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
+ && requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
+ }
+
+ /**
+ * Requests location updates from the specified provider.
+ *
+ * @param locationProvider the location provider
+ * @return true if successful
+ */
+ private boolean requestLocationUpdatesFromProvider(String locationProvider) {
try {
- locationManager.requestLocationUpdates(provider, 400, 1, this);
- Location location = locationManager.getLastKnownLocation(provider);
- //Location works, just need to 'send' GPS coords
- // via emulator extended controls if testing on emulator
- Timber.d("Checking for location...");
- if (location != null) {
- this.onLocationChanged(location);
- }
+ locationManager.requestLocationUpdates(locationProvider,
+ MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS,
+ MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS,
+ this);
+ return true;
} catch (IllegalArgumentException e) {
Timber.e(e, "Illegal argument exception");
+ return false;
} catch (SecurityException e) {
Timber.e(e, "Security exception");
+ return false;
}
}
- /** Unregisters location manager.
+ /**
+ * Returns whether a given location is better than the current best location.
+ *
+ * @param location the location to be tested
+ * @param currentBestLocation the current best location
+ * @return true if the given location is better
+ */
+ protected boolean isBetterLocation(Location location, Location currentBestLocation) {
+ if (currentBestLocation == null) {
+ // A new location is always better than no location
+ return true;
+ }
+
+ // Check whether the new location fix is newer or older
+ long timeDelta = location.getTime() - currentBestLocation.getTime();
+ boolean isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS;
+ boolean isSignificantlyOlder = timeDelta < -MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS;
+ boolean isNewer = timeDelta > 0;
+
+ // If it's been more than two minutes since the current location, use the new location
+ // because the user has likely moved
+ if (isSignificantlyNewer) {
+ return true;
+ // If the new location is more than two minutes older, it must be worse
+ } else if (isSignificantlyOlder) {
+ return false;
+ }
+
+ // Check whether the new location fix is more or less accurate
+ int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
+ boolean isLessAccurate = accuracyDelta > 0;
+ boolean isMoreAccurate = accuracyDelta < 0;
+ boolean isSignificantlyLessAccurate = accuracyDelta > 200;
+
+ // Check if the old and new location are from the same provider
+ boolean isFromSameProvider = isSameProvider(location.getProvider(),
+ currentBestLocation.getProvider());
+
+ // Determine location quality using a combination of timeliness and accuracy
+ if (isMoreAccurate) {
+ return true;
+ } else if (isNewer && !isLessAccurate) {
+ return true;
+ } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether two providers are the same
+ */
+ private boolean isSameProvider(String provider1, String provider2) {
+ if (provider1 == null) {
+ return provider2 == null;
+ }
+ return provider1.equals(provider2);
+ }
+
+ /**
+ * Unregisters location manager.
*/
public void unregisterLocationManager() {
+ isLocationManagerRegistered = false;
try {
locationManager.removeUpdates(this);
} catch (SecurityException e) {
@@ -64,15 +186,34 @@ public class LocationServiceManager implements LocationListener {
}
}
+ /**
+ * Adds a new listener to the list of location listeners.
+ *
+ * @param listener the new listener
+ */
+ public void addLocationListener(LocationUpdateListener listener) {
+ if (!locationListeners.contains(listener)) {
+ locationListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes a listener from the list of location listeners.
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeLocationListener(LocationUpdateListener listener) {
+ locationListeners.remove(listener);
+ }
+
@Override
public void onLocationChanged(Location location) {
- double currentLatitude = location.getLatitude();
- double currentLongitude = location.getLongitude();
- latestLocationAccuracy = location.getAccuracy();
- Timber.d("Latitude: %f Longitude: %f Accuracy %f",
- currentLatitude, currentLongitude, latestLocationAccuracy);
-
- latestLocation = new LatLng(currentLatitude, currentLongitude, latestLocationAccuracy);
+ if (isBetterLocation(location, lastLocation)) {
+ lastLocation = location;
+ for (LocationUpdateListener listener : locationListeners) {
+ listener.onLocationChanged(LatLng.from(lastLocation));
+ }
+ }
}
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java
new file mode 100644
index 000000000..69d3048a1
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.java
@@ -0,0 +1,5 @@
+package fr.free.nrw.commons.location;
+
+public interface LocationUpdateListener {
+ void onLocationChanged(LatLng latLng);
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
index fe68203ec..ecf3cedb1 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java
@@ -6,7 +6,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -22,6 +21,9 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
import fr.free.nrw.commons.License;
import fr.free.nrw.commons.LicenseList;
import fr.free.nrw.commons.Media;
@@ -29,11 +31,12 @@ import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.PageTitle;
import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.ui.widget.CompatTextView;
import timber.log.Timber;
-public class MediaDetailFragment extends Fragment {
+public class MediaDetailFragment extends CommonsDaggerSupportFragment {
private boolean editable;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
@@ -53,6 +56,9 @@ public class MediaDetailFragment extends Fragment {
return mf;
}
+ @Inject
+ Provider mediaDataExtractorProvider;
+
private MediaWikiImageView image;
private MediaDetailSpacer spacer;
private int initialListTop = 0;
@@ -69,8 +75,8 @@ public class MediaDetailFragment extends Fragment {
private boolean categoriesPresent = false;
private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private ViewTreeObserver.OnScrollChangedListener scrollListener;
- DataSetObserver dataObserver;
- private AsyncTask detailFetchTask;
+ private DataSetObserver dataObserver;
+ private AsyncTask detailFetchTask;
private LicenseList licenseList;
@Override
@@ -89,7 +95,7 @@ public class MediaDetailFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
+ detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable");
@@ -150,7 +156,8 @@ public class MediaDetailFragment extends Fragment {
return view;
}
- @Override public void onResume() {
+ @Override
+ public void onResume() {
super.onResume();
Media media = detailProvider.getMediaAtPosition(index);
if (media == null) {
@@ -188,13 +195,13 @@ public class MediaDetailFragment extends Fragment {
@Override
protected void onPreExecute() {
- extractor = new MediaDataExtractor(media.getFilename(), licenseList);
+ extractor = mediaDataExtractorProvider.get();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
- extractor.fetch();
+ extractor.fetch(media.getFilename(), licenseList);
return Boolean.TRUE;
} catch (IOException e) {
Timber.d(e);
@@ -232,13 +239,13 @@ public class MediaDetailFragment extends Fragment {
detailFetchTask.cancel(true);
detailFetchTask = null;
}
- if (layoutListener != null) {
+ if (layoutListener != null && getView() != null) {
getView().getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener); // old Android was on crack. CRACK IS WHACK
layoutListener = null;
}
- if (scrollListener != null) {
+ if (scrollListener != null && getView() != null) {
getView().getViewTreeObserver().removeOnScrollChangedListener(scrollListener);
- scrollListener = null;
+ scrollListener = null;
}
if (dataObserver != null) {
detailProvider.unregisterDataSetObserver(dataObserver);
@@ -283,7 +290,7 @@ public class MediaDetailFragment extends Fragment {
private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
- final CompatTextView textView = (CompatTextView)item.findViewById(R.id.mediaDetailCategoryItemText);
+ final CompatTextView textView = (CompatTextView) item.findViewById(R.id.mediaDetailCategoryItemText);
textView.setText(catName);
if (categoriesLoaded && categoriesPresent) {
@@ -302,7 +309,7 @@ public class MediaDetailFragment extends Fragment {
// You must face the darkness alone
int scrollY = scrollView.getScrollY();
int scrollMax = getView().getHeight();
- float scrollPercentage = (float)scrollY / (float)scrollMax;
+ float scrollPercentage = (float) scrollY / (float) scrollMax;
final float transparencyMax = 0.75f;
if (scrollPercentage > transparencyMax) {
scrollPercentage = transparencyMax;
@@ -356,7 +363,8 @@ public class MediaDetailFragment extends Fragment {
}
- private @Nullable String licenseLink(Media media) {
+ private @Nullable
+ String licenseLink(Media media) {
String licenseKey = media.getLicense();
if (licenseKey == null || licenseKey.equals("")) {
return null;
@@ -377,7 +385,7 @@ public class MediaDetailFragment extends Fragment {
private void openMap(LatLng coordinates) {
//Open map app at given position
Uri gmmIntentUri = Uri.parse(
- "geo:0,0?q=" + coordinates.getLatitude() + "," + coordinates.getLatitude());
+ "geo:0,0?q=" + coordinates.getLatitude() + "," + coordinates.getLongitude());
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
index 0b718df2a..3dd8d69e8 100644
--- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java
@@ -3,6 +3,7 @@ package fr.free.nrw.commons.media;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
@@ -24,20 +25,31 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity;
-import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE;
import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static fr.free.nrw.commons.CommonsApplication.EVENT_SHARE_ATTEMPT;
-public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPageChangeListener {
+public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
+
+ @Inject
+ MediaWikiApi mwApi;
+ @Inject
+ SessionManager sessionManager;
+ @Inject
+ @Named("default_preferences")
+ SharedPreferences prefs;
private ViewPager pager;
private Boolean editable;
@@ -99,12 +111,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
switch (item.getItemId()) {
case R.id.menu_share_current_image:
- // Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252
- CommonsApplication app = (CommonsApplication) getActivity().getApplication();
- EventLog.schema(EVENT_SHARE_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("filename", m.getFilename())
- .log();
+ // Share - intent set in onCreateOptionsMenu, around line 252
return true;
case R.id.menu_browser_current_image:
// View in browser
@@ -141,8 +148,14 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
private void downloadMedia(Media m) {
String imageUrl = m.getImageUrl(),
fileName = m.getFilename();
+
+ if (imageUrl == null || fileName == null) {
+ return;
+ }
+
// Strip 'File:' from beginning of filename, we really shouldn't store it
fileName = fileName.replaceFirst("^File:", "");
+
Uri imageUri = Uri.parse(imageUrl);
DownloadManager.Request req = new DownloadManager.Request(imageUri);
@@ -155,15 +168,19 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
req.allowScanningByMediaScanner();
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- && !(ContextCompat.checkSelfPermission(getContext(),
- READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+ ContextCompat.checkSelfPermission(getContext(), READ_EXTERNAL_STORAGE)
+ != PERMISSION_GRANTED
+ && getView() != null) {
Snackbar.make(getView(), R.string.read_storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
view -> ActivityCompat.requestPermissions(getActivity(),
new String[]{READ_EXTERNAL_STORAGE}, 1)).show();
} else {
- ((DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE)).enqueue(req);
+ DownloadManager systemService = (DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE);
+ if (systemService != null) {
+ systemService.enqueue(req);
+ }
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/CategoryModifier.java b/app/src/main/java/fr/free/nrw/commons/modifications/CategoryModifier.java
index bb650513b..556815a97 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/CategoryModifier.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/CategoryModifier.java
@@ -13,7 +13,7 @@ public class CategoryModifier extends PageModifier {
public CategoryModifier(String... categories) {
super(MODIFIER_NAME);
JSONArray categoriesArray = new JSONArray();
- for(String category: categories) {
+ for (String category: categories) {
categoriesArray.put(category);
}
try {
@@ -34,7 +34,7 @@ public class CategoryModifier extends PageModifier {
categories = params.optJSONArray(PARAM_CATEGORIES);
StringBuilder categoriesString = new StringBuilder();
- for(int i=0; i < categories.length(); i++) {
+ for (int i = 0; i < categories.length(); i++) {
String category = categories.optString(i);
categoriesString.append("\n[[Category:").append(category).append("]]");
}
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java
index 11caa94fa..0d4468d84 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsContentProvider.java
@@ -1,6 +1,5 @@
package fr.free.nrw.commons.modifications;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
@@ -10,50 +9,51 @@ import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.data.DBOpenHelper;
+import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
-public class ModificationsContentProvider extends ContentProvider{
+import static fr.free.nrw.commons.modifications.ModifierSequenceDao.Table.TABLE_NAME;
+
+public class ModificationsContentProvider extends CommonsDaggerContentProvider {
private static final int MODIFICATIONS = 1;
private static final int MODIFICATIONS_ID = 2;
- public static final String AUTHORITY = "fr.free.nrw.commons.modifications.contentprovider";
- private static final String BASE_PATH = "modifications";
+ public static final String MODIFICATIONS_AUTHORITY = "fr.free.nrw.commons.modifications.contentprovider";
+ public static final String BASE_PATH = "modifications";
- public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
+ public static final Uri BASE_URI = Uri.parse("content://" + MODIFICATIONS_AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
- uriMatcher.addURI(AUTHORITY, BASE_PATH, MODIFICATIONS);
- uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_ID);
+ uriMatcher.addURI(MODIFICATIONS_AUTHORITY, BASE_PATH, MODIFICATIONS);
+ uriMatcher.addURI(MODIFICATIONS_AUTHORITY, BASE_PATH + "/#", MODIFICATIONS_ID);
}
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
-
- @Override
- public boolean onCreate() {
- return false;
- }
+ @Inject DBOpenHelper dbOpenHelper;
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
- queryBuilder.setTables(ModifierSequence.Table.TABLE_NAME);
+ queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
- switch(uriType) {
+ switch (uriType) {
case MODIFICATIONS:
break;
default:
throw new IllegalArgumentException("Unknown URI" + uri);
}
- SQLiteDatabase db = CommonsApplication.getInstance().getDBOpenHelper().getReadableDatabase();
+ SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
@@ -69,11 +69,11 @@ public class ModificationsContentProvider extends ContentProvider{
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
- long id = 0;
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
+ long id;
switch (uriType) {
case MODIFICATIONS:
- id = sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, contentValues);
+ id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
@@ -85,11 +85,11 @@ public class ModificationsContentProvider extends ContentProvider{
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
switch (uriType) {
case MODIFICATIONS_ID:
String id = uri.getLastPathSegment();
- sqlDB.delete(ModifierSequence.Table.TABLE_NAME,
+ sqlDB.delete(TABLE_NAME,
"_id = ?",
new String[] { id }
);
@@ -103,13 +103,13 @@ public class ModificationsContentProvider extends ContentProvider{
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ModificationsContentProvider)");
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case MODIFICATIONS:
- for(ContentValues value: values) {
+ for (ContentValues value: values) {
Timber.d("Inserting! %s", value);
- sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
+ sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
@@ -131,11 +131,11 @@ public class ModificationsContentProvider extends ContentProvider{
In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise.
*/
int uriType = uriMatcher.match(uri);
- SQLiteDatabase sqlDB = CommonsApplication.getInstance().getDBOpenHelper().getWritableDatabase();
- int rowsUpdated = 0;
+ SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
+ int rowsUpdated;
switch (uriType) {
case MODIFICATIONS:
- rowsUpdated = sqlDB.update(ModifierSequence.Table.TABLE_NAME,
+ rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
selection,
selectionArgs);
@@ -144,9 +144,9 @@ public class ModificationsContentProvider extends ContentProvider{
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
- rowsUpdated = sqlDB.update(ModifierSequence.Table.TABLE_NAME,
+ rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
- ModifierSequence.Table.COLUMN_ID + " = ?",
+ ModifierSequenceDao.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
} else {
throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID");
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java
index f2940ec74..5d716d738 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModificationsSyncAdapter.java
@@ -1,9 +1,6 @@
package fr.free.nrw.commons.modifications;
import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.AuthenticatorException;
-import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
@@ -14,15 +11,24 @@ import android.os.RemoteException;
import java.io.IOException;
-import fr.free.nrw.commons.CommonsApplication;
-import fr.free.nrw.commons.Utils;
+import javax.inject.Inject;
+
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
+import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
+ @Inject MediaWikiApi mwApi;
+ @Inject ContributionDao contributionDao;
+ @Inject ModifierSequenceDao modifierSequenceDao;
+ @Inject
+ SessionManager sessionManager;
+
public ModificationsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@@ -30,6 +36,11 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
@Override
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
+ ApplicationlessInjection
+ .getInstance(getContext()
+ .getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
Cursor allModifications;
try {
@@ -44,27 +55,17 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
- String authCookie;
- try {
- authCookie = AccountManager.get(getContext()).blockingGetAuthToken(account, "", false);
- } catch (OperationCanceledException | AuthenticatorException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
+ String authCookie = sessionManager.getAuthCookie();
+ if (isNullOrWhiteSpace(authCookie)) {
Timber.d("Could not authenticate :(");
return;
}
- if (Utils.isNullOrWhiteSpace(authCookie)) {
- Timber.d("Could not authenticate :(");
- return;
- }
-
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
- api.setAuthCookie(authCookie);
+ mwApi.setAuthCookie(authCookie);
String editToken;
try {
- editToken = api.getEditToken();
+ editToken = mwApi.getEditToken();
} catch (IOException e) {
Timber.d("Can not retreive edit token!");
return;
@@ -76,28 +77,36 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
ContentProviderClient contributionsClient = null;
try {
- contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
+ contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.CONTRIBUTION_AUTHORITY);
while (!allModifications.isAfterLast()) {
- ModifierSequence sequence = ModifierSequence.fromCursor(allModifications);
- sequence.setContentProviderClient(contentProviderClient);
+ ModifierSequence sequence = modifierSequenceDao.fromCursor(allModifications);
Contribution contrib;
-
Cursor contributionCursor;
+
+ if (contributionsClient == null) {
+ Timber.e("ContributionsClient is null. This should not happen!");
+ return;
+ }
+
try {
contributionCursor = contributionsClient.query(sequence.getMediaUri(), null, null, null, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
- contributionCursor.moveToFirst();
- contrib = Contribution.fromCursor(contributionCursor);
- if (contrib.getState() == Contribution.STATE_COMPLETED) {
+ if (contributionCursor != null) {
+ contributionCursor.moveToFirst();
+ }
+
+ contrib = contributionDao.fromCursor(contributionCursor);
+
+ if (contrib != null && contrib.getState() == Contribution.STATE_COMPLETED) {
String pageContent;
try {
- pageContent = api.revisionsByFilename(contrib.getFilename());
+ pageContent = mwApi.revisionsByFilename(contrib.getFilename());
} catch (IOException e) {
- Timber.d("Network fuckup on modifications sync!");
+ Timber.d("Network messed up on modifications sync!");
continue;
}
@@ -106,19 +115,19 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
String editResult;
try {
- editResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
+ editResult = mwApi.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
} catch (IOException e) {
- Timber.d("Network fuckup on modifications sync!");
+ Timber.d("Network messed up on modifications sync!");
continue;
}
Timber.d("Response is %s", editResult);
- if (!editResult.equals("Success")) {
+ if (!"Success".equals(editResult)) {
// FIXME: Log this somewhere else
Timber.d("Non success result! %s", editResult);
} else {
- sequence.delete();
+ modifierSequenceDao.delete(sequence);
}
}
allModifications.moveToNext();
@@ -129,4 +138,8 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
}
}
}
+
+ private boolean isNullOrWhiteSpace(String value) {
+ return value == null || value.trim().isEmpty();
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequence.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequence.java
index 36012e55e..93cb3bc3d 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequence.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequence.java
@@ -1,14 +1,8 @@
package fr.free.nrw.commons.modifications;
-import android.content.ContentProviderClient;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
-import android.os.RemoteException;
import org.json.JSONArray;
-import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
@@ -17,22 +11,21 @@ public class ModifierSequence {
private Uri mediaUri;
private ArrayList modifiers;
private Uri contentUri;
- private ContentProviderClient client;
public ModifierSequence(Uri mediaUri) {
this.mediaUri = mediaUri;
modifiers = new ArrayList<>();
}
- public ModifierSequence(Uri mediaUri, JSONObject data) {
+ ModifierSequence(Uri mediaUri, JSONObject data) {
this(mediaUri);
JSONArray modifiersJSON = data.optJSONArray("modifiers");
- for (int i=0; i< modifiersJSON.length(); i++) {
+ for (int i = 0; i < modifiersJSON.length(); i++) {
modifiers.add(PageModifier.fromJSON(modifiersJSON.optJSONObject(i)));
}
}
- public Uri getMediaUri() {
+ Uri getMediaUri() {
return mediaUri;
}
@@ -40,113 +33,32 @@ public class ModifierSequence {
modifiers.add(modifier);
}
- public String executeModifications(String pageName, String pageContents) {
+ String executeModifications(String pageName, String pageContents) {
for (PageModifier modifier: modifiers) {
pageContents = modifier.doModification(pageName, pageContents);
}
return pageContents;
}
- public String getEditSummary() {
+ String getEditSummary() {
StringBuilder editSummary = new StringBuilder();
- for(PageModifier modifier: modifiers) {
+ for (PageModifier modifier: modifiers) {
editSummary.append(modifier.getEditSumary()).append(" ");
}
editSummary.append("Via Commons Mobile App");
return editSummary.toString();
}
- public JSONObject toJSON() {
- JSONObject data = new JSONObject();
- try {
- JSONArray modifiersJSON = new JSONArray();
- for (PageModifier modifier: modifiers) {
- modifiersJSON.put(modifier.toJSON());
- }
- data.put("modifiers", modifiersJSON);
- return data;
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
+ ArrayList getModifiers() {
+ return modifiers;
}
- public ContentValues toContentValues() {
- ContentValues cv = new ContentValues();
- cv.put(Table.COLUMN_MEDIA_URI, mediaUri.toString());
- cv.put(Table.COLUMN_DATA, toJSON().toString());
- return cv;
+ Uri getContentUri() {
+ return contentUri;
}
- public static ModifierSequence fromCursor(Cursor cursor) {
- // Hardcoding column positions!
- ModifierSequence ms = null;
- try {
- ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
- new JSONObject(cursor.getString(2)));
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- ms.contentUri = ModificationsContentProvider.uriForId(cursor.getInt(0));
-
- return ms;
+ void setContentUri(Uri contentUri) {
+ this.contentUri = contentUri;
}
- public void save() {
- try {
- if(contentUri == null) {
- contentUri = client.insert(ModificationsContentProvider.BASE_URI, this.toContentValues());
- } else {
- client.update(contentUri, toContentValues(), null, null);
- }
- } catch(RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- public void delete() {
- try {
- client.delete(contentUri, null, null);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- public void setContentProviderClient(ContentProviderClient client) {
- this.client = client;
- }
-
- public static class Table {
- public static final String TABLE_NAME = "modifications";
-
- public static final String COLUMN_ID = "_id";
- public static final String COLUMN_MEDIA_URI = "mediauri";
- public static final String COLUMN_DATA = "data";
-
- // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
- public static final String[] ALL_FIELDS = {
- COLUMN_ID,
- COLUMN_MEDIA_URI,
- COLUMN_DATA
- };
-
- private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
- + "_id INTEGER PRIMARY KEY,"
- + "mediauri STRING,"
- + "data STRING"
- + ");";
-
- public static void onCreate(SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_STATEMENT);
- }
-
- public static void onUpdate(SQLiteDatabase db, int from, int to) {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
- onCreate(db);
- }
-
- public static void onDelete(SQLiteDatabase db) {
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
- onCreate(db);
- }
- }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java b/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java
new file mode 100644
index 000000000..a23079b5e
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/ModifierSequenceDao.java
@@ -0,0 +1,124 @@
+package fr.free.nrw.commons.modifications;
+
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+
+public class ModifierSequenceDao {
+
+ private final Provider clientProvider;
+
+ @Inject
+ public ModifierSequenceDao(@Named("modification") Provider clientProvider) {
+ this.clientProvider = clientProvider;
+ }
+
+ public void save(ModifierSequence sequence) {
+ ContentProviderClient db = clientProvider.get();
+ try {
+ if (sequence.getContentUri() == null) {
+ sequence.setContentUri(db.insert(ModificationsContentProvider.BASE_URI, toContentValues(sequence)));
+ } else {
+ db.update(sequence.getContentUri(), toContentValues(sequence), null, null);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ }
+
+ public void delete(ModifierSequence sequence) {
+ ContentProviderClient db = clientProvider.get();
+ try {
+ db.delete(sequence.getContentUri(), null, null);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ db.release();
+ }
+ }
+
+ ModifierSequence fromCursor(Cursor cursor) {
+ // Hardcoding column positions!
+ ModifierSequence ms;
+ try {
+ ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
+ new JSONObject(cursor.getString(2)));
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ ms.setContentUri( ModificationsContentProvider.uriForId(cursor.getInt(0)));
+
+ return ms;
+ }
+
+ private JSONObject toJSON(ModifierSequence sequence) {
+ JSONObject data = new JSONObject();
+ try {
+ JSONArray modifiersJSON = new JSONArray();
+ for (PageModifier modifier: sequence.getModifiers()) {
+ modifiersJSON.put(modifier.toJSON());
+ }
+ data.put("modifiers", modifiersJSON);
+ return data;
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private ContentValues toContentValues(ModifierSequence sequence) {
+ ContentValues cv = new ContentValues();
+ cv.put(Table.COLUMN_MEDIA_URI, sequence.getMediaUri().toString());
+ cv.put(Table.COLUMN_DATA, toJSON(sequence).toString());
+ return cv;
+ }
+
+ public static class Table {
+ static final String TABLE_NAME = "modifications";
+
+ static final String COLUMN_ID = "_id";
+ static final String COLUMN_MEDIA_URI = "mediauri";
+ static final String COLUMN_DATA = "data";
+
+ // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
+ public static final String[] ALL_FIELDS = {
+ COLUMN_ID,
+ COLUMN_MEDIA_URI,
+ COLUMN_DATA
+ };
+
+ static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
+
+ static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ + "_id INTEGER PRIMARY KEY,"
+ + "mediauri STRING,"
+ + "data STRING"
+ + ");";
+
+ public static void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_STATEMENT);
+ }
+
+ public static void onUpdate(SQLiteDatabase db, int from, int to) {
+ db.execSQL(DROP_TABLE_STATEMENT);
+ onCreate(db);
+ }
+
+ public static void onDelete(SQLiteDatabase db) {
+ db.execSQL(DROP_TABLE_STATEMENT);
+ onCreate(db);
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/PageModifier.java b/app/src/main/java/fr/free/nrw/commons/modifications/PageModifier.java
index f4b3c7359..0da25d9a6 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/PageModifier.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/PageModifier.java
@@ -7,9 +7,9 @@ public abstract class PageModifier {
public static PageModifier fromJSON(JSONObject data) {
String name = data.optString("name");
- if(name.equals(CategoryModifier.MODIFIER_NAME)) {
+ if (name.equals(CategoryModifier.MODIFIER_NAME)) {
return new CategoryModifier(data.optJSONObject("data"));
- } else if(name.equals(TemplateRemoveModifier.MODIFIER_NAME)) {
+ } else if (name.equals(TemplateRemoveModifier.MODIFIER_NAME)) {
return new TemplateRemoveModifier(data.optJSONObject("data"));
}
diff --git a/app/src/main/java/fr/free/nrw/commons/modifications/TemplateRemoveModifier.java b/app/src/main/java/fr/free/nrw/commons/modifications/TemplateRemoveModifier.java
index 6149084c1..f9942b007 100644
--- a/app/src/main/java/fr/free/nrw/commons/modifications/TemplateRemoveModifier.java
+++ b/app/src/main/java/fr/free/nrw/commons/modifications/TemplateRemoveModifier.java
@@ -41,18 +41,18 @@ public class TemplateRemoveModifier extends PageModifier {
Pattern templateStartPattern = Pattern.compile("\\{\\{" + templateNormalized, Pattern.CASE_INSENSITIVE);
Matcher matcher = templateStartPattern.matcher(pageContents);
- while(matcher.find()) {
+ while (matcher.find()) {
int braceCount = 1;
int startIndex = matcher.start();
int curIndex = matcher.end();
Matcher openMatch = PATTERN_TEMPLATE_OPEN.matcher(pageContents);
Matcher closeMatch = PATTERN_TEMPLATE_CLOSE.matcher(pageContents);
- while(curIndex < pageContents.length()) {
+ while (curIndex < pageContents.length()) {
boolean openFound = openMatch.find(curIndex);
boolean closeFound = closeMatch.find(curIndex);
- if(openFound && (!closeFound || openMatch.start() < closeMatch.start())) {
+ if (openFound && (!closeFound || openMatch.start() < closeMatch.start())) {
braceCount++;
curIndex = openMatch.end();
} else if (closeFound) {
@@ -71,8 +71,8 @@ public class TemplateRemoveModifier extends PageModifier {
}
// Strip trailing whitespace
- while(curIndex < pageContents.length()) {
- if(pageContents.charAt(curIndex) == ' ' || pageContents.charAt(curIndex) == '\n') {
+ while (curIndex < pageContents.length()) {
+ if (pageContents.charAt(curIndex) == ' ' || pageContents.charAt(curIndex) == '\n') {
curIndex++;
} else {
break;
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
index 4cbfe7c3a..fe9700ef5 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
@@ -1,5 +1,7 @@
package fr.free.nrw.commons.mwapi;
+import android.content.Context;
+import android.content.SharedPreferences;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -21,10 +23,14 @@ import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils;
import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -34,12 +40,17 @@ import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
-import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.notification.Notification;
import in.yuvi.http.fluent.Http;
import io.reactivex.Observable;
import io.reactivex.Single;
import timber.log.Timber;
+import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN;
+import static fr.free.nrw.commons.notification.NotificationUtils.getNotificationFromApiResult;
+import static fr.free.nrw.commons.notification.NotificationUtils.getNotificationType;
+import static fr.free.nrw.commons.notification.NotificationUtils.isCommonsNotification;
+
/**
* @author Addshore
*/
@@ -49,17 +60,27 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
private static final String THUMB_SIZE = "640";
private AbstractHttpClient httpClient;
private MWApi api;
+ private Context context;
+ private SharedPreferences sharedPreferences;
- public ApacheHttpClientMediaWikiApi(String apiURL) {
+ public ApacheHttpClientMediaWikiApi(Context context, String apiURL, SharedPreferences sharedPreferences) {
+ this.context = context;
BasicHttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
- params.setParameter(CoreProtocolPNames.USER_AGENT, "Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE);
+ params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
httpClient = new DefaultHttpClient(cm, params);
api = new MWApi(apiURL, httpClient);
+ this.sharedPreferences = sharedPreferences;
+ }
+
+ @Override
+ @NonNull
+ public String getUserAgent() {
+ return "Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
}
@VisibleForTesting
@@ -74,11 +95,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
* @throws IOException On api request IO issue
*/
public String login(String username, String password) throws IOException {
+ String loginToken = getLoginToken();
+ Timber.d("Login token is %s", loginToken);
return getErrorCodeToReturn(api.action("clientlogin")
.param("rememberMe", "1")
.param("username", username)
.param("password", password)
- .param("logintoken", getLoginToken())
+ .param("logintoken", loginToken)
.param("loginreturnurl", "https://commons.wikimedia.org")
.post());
}
@@ -91,12 +114,14 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
* @throws IOException On api request IO issue
*/
public String login(String username, String password, String twoFactorCode) throws IOException {
+ String loginToken = getLoginToken();
+ Timber.d("Login token is %s", loginToken);
return getErrorCodeToReturn(api.action("clientlogin")
- .param("rememberMe", "1")
+ .param("rememberMe", "true")
.param("username", username)
.param("password", password)
- .param("logintoken", getLoginToken())
- .param("logincontinue", "1")
+ .param("logintoken", loginToken)
+ .param("logincontinue", "true")
.param("OATHToken", twoFactorCode)
.post());
}
@@ -121,14 +146,17 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
String status = loginApiResult.getString("/api/clientlogin/@status");
if (status.equals("PASS")) {
api.isLoggedIn = true;
+ setAuthCookieOnLogin(true);
return status;
} else if (status.equals("FAIL")) {
+ setAuthCookieOnLogin(false);
return loginApiResult.getString("/api/clientlogin/@messagecode");
} else if (
status.equals("UI")
&& loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
&& loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
) {
+ setAuthCookieOnLogin(false);
return "2FA";
}
@@ -136,6 +164,18 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return "genericerror-" + status;
}
+ private void setAuthCookieOnLogin(boolean isLoggedIn) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ if (isLoggedIn) {
+ editor.putBoolean("isUserLoggedIn", true);
+ editor.putString("getAuthCookie", api.getAuthCookie());
+ } else {
+ editor.putBoolean("isUserLoggedIn", false);
+ editor.remove("getAuthCookie");
+ }
+ editor.apply();
+ }
+
@Override
public String getAuthCookie() {
return api.getAuthCookie();
@@ -335,7 +375,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
logEvents.add(new LogEventResult.LogEvent(
image.getString("@pageid"),
image.getString("@title"),
- Utils.parseMWDate(image.getString("@timestamp")))
+ parseMWDate(image.getString("@timestamp")))
);
}
return logEvents;
@@ -352,6 +392,42 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.getString("/api/query/pages/page/revisions/rev");
}
+ @Override
+ @NonNull
+ public List getNotifications() {
+ ApiResult notificationNode = null;
+ try {
+ notificationNode = api.action("query")
+ .param("notprop", "list")
+ .param("format", "xml")
+ .param("meta", "notifications")
+ .param("notfilter", "!read")
+ .get()
+ .getNode("/api/query/notifications/list");
+ } catch (IOException e) {
+ Timber.e("Failed to obtain searchCategories", e);
+ }
+
+ if (notificationNode == null) {
+ return new ArrayList<>();
+ }
+
+ List notifications = new ArrayList<>();
+
+ NodeList childNodes = notificationNode.getDocument().getChildNodes();
+
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node node = childNodes.item(i);
+ if (isCommonsNotification(node)
+ && !getNotificationType(node).equals(UNKNOWN)) {
+ notifications.add(getNotificationFromApiResult(context, node));
+ }
+ }
+
+ return notifications;
+ }
+
+
@Override
public boolean existingFile(String fileSha1) throws IOException {
return api.action("query")
@@ -387,17 +463,22 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
@Override
@NonNull
- public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
+ public UploadResult uploadFile(String filename,
+ @NonNull InputStream file,
+ long dataLength,
+ String pageContents,
+ String editSummary,
+ final ProgressListener progressListener) throws IOException {
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress);
- Log.e("WTF", "Result: " +result.toString());
+ Log.e("WTF", "Result: " + result.toString());
String resultStatus = result.getString("/api/upload/@result");
if (!resultStatus.equals("Success")) {
String errorCode = result.getString("/api/error/@code");
return new UploadResult(resultStatus, errorCode);
} else {
- Date dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
+ Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
String imageUrl = result.getString("/api/upload/imageinfo/@url");
return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl);
@@ -423,4 +504,13 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
return Integer.parseInt(uploadCount);
});
}
+
+ private Date parseMWDate(String mwDate) {
+ SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
+ try {
+ return isoFormat.parse(mwDate);
+ } catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java b/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
index d3ba7c0d5..4446da738 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.mwapi;
+import android.content.SharedPreferences;
import android.os.Build;
import fr.free.nrw.commons.Utils;
@@ -15,14 +16,14 @@ public class EventLog {
}
}
- private static LogBuilder schema(String schema, long revision) {
- return new LogBuilder(schema, revision);
+ private static LogBuilder schema(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
+ return new LogBuilder(schema, revision, mwApi, prefs);
}
- public static LogBuilder schema(Object[] scid) {
+ public static LogBuilder schema(Object[] scid, MediaWikiApi mwApi, SharedPreferences prefs) {
if (scid.length != 2) {
throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second");
}
- return schema((String) scid[0], (Long) scid[1]);
+ return schema((String) scid[0], (Long) scid[1], mwApi, prefs);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java b/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
index eabbbf82e..2a2456cc3 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
@@ -3,7 +3,6 @@ package fr.free.nrw.commons.mwapi;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
-import android.preference.PreferenceManager;
import org.json.JSONException;
import org.json.JSONObject;
@@ -12,21 +11,39 @@ import java.net.MalformedURLException;
import java.net.URL;
import fr.free.nrw.commons.BuildConfig;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
+@SuppressWarnings("WeakerAccess")
public class LogBuilder {
- private JSONObject data;
- private long rev;
- private String schema;
+ private final MediaWikiApi mwApi;
+ private final JSONObject data;
+ private final long rev;
+ private final String schema;
+ private final SharedPreferences prefs;
- LogBuilder(String schema, long revision) {
- data = new JSONObject();
+ /**
+ * Main constructor of LogBuilder
+ *
+ * @param schema Log schema
+ * @param revision Log revision
+ * @param mwApi Wiki media API instance
+ * @param prefs Instance of SharedPreferences
+ */
+ LogBuilder(String schema, long revision, MediaWikiApi mwApi, SharedPreferences prefs) {
+ this.prefs = prefs;
+ this.data = new JSONObject();
this.schema = schema;
this.rev = revision;
+ this.mwApi = mwApi;
}
+ /**
+ * Adds data to preferences
+ * @param key Log key
+ * @param value Log object value
+ * @return LogBuilder
+ */
public LogBuilder param(String key, Object value) {
try {
data.put(key, value);
@@ -36,6 +53,10 @@ public class LogBuilder {
return this;
}
+ /**
+ * Encodes JSON object to URL
+ * @return URL to JSON object
+ */
URL toUrl() {
JSONObject fullData = new JSONObject();
try {
@@ -56,14 +77,13 @@ public class LogBuilder {
// Use *only* for tracking the user preference change for EventLogging
// Attempting to use anywhere else will cause kitten explosions
public void log(boolean force) {
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance());
- if (!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
+ if (!prefs.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
return; // User has disabled tracking
}
- LogTask logTask = new LogTask();
+ LogTask logTask = new LogTask(mwApi);
logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
}
-
+
public void log() {
log(false);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java b/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
index ee947afbc..e564a50ab 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
@@ -2,11 +2,26 @@ package fr.free.nrw.commons.mwapi;
import android.os.AsyncTask;
-import fr.free.nrw.commons.CommonsApplication;
-
class LogTask extends AsyncTask {
+
+ private final MediaWikiApi mwApi;
+
+ /**
+ * Main constructor of LogTask
+ *
+ * @param mwApi Media wiki API instance
+ */
+ public LogTask(MediaWikiApi mwApi) {
+ this.mwApi = mwApi;
+ }
+
+ /**
+ * Logs events in background
+ * @param logBuilders LogBuilder instance
+ * @return Background success state ( TRUE or FALSE )
+ */
@Override
protected Boolean doInBackground(LogBuilder... logBuilders) {
- return CommonsApplication.getInstance().getMWApi().logEvents(logBuilders);
+ return mwApi.logEvents(logBuilders);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
index cfddf8c15..f2f34ce6d 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
@@ -4,15 +4,29 @@ public class MediaResult {
private final String wikiSource;
private final String parseTreeXmlSource;
+ /**
+ * Full-fledged constructor of MediaResult
+ *
+ * @param wikiSource Media wiki source
+ * @param parseTreeXmlSource Media tree parsed in XML
+ */
MediaResult(String wikiSource, String parseTreeXmlSource) {
this.wikiSource = wikiSource;
this.parseTreeXmlSource = parseTreeXmlSource;
}
+ /**
+ * Gets wiki source
+ * @return Wiki source
+ */
public String getWikiSource() {
return wikiSource;
}
+ /**
+ * Gets tree parsed in XML
+ * @return XML parsed tree
+ */
public String getParseTreeXmlSource() {
return parseTreeXmlSource;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
index 310c97a8a..da9403b62 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java
@@ -5,11 +5,15 @@ import android.support.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
+import fr.free.nrw.commons.notification.Notification;
import io.reactivex.Observable;
import io.reactivex.Single;
public interface MediaWikiApi {
+ String getUserAgent();
+
String getAuthCookie();
void setAuthCookie(String authCookie);
@@ -43,6 +47,9 @@ public interface MediaWikiApi {
@NonNull
Observable allCategories(String filter, int searchCatsLimit);
+ @NonNull
+ List getNotifications() throws IOException;
+
@NonNull
Observable searchTitles(String title, int searchCatsLimit);
@@ -51,6 +58,8 @@ public interface MediaWikiApi {
boolean existingFile(String fileSha1) throws IOException;
+
+
@NonNull
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/UploadResult.java b/app/src/main/java/fr/free/nrw/commons/mwapi/UploadResult.java
index 34d050b2c..9422497b3 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/UploadResult.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/UploadResult.java
@@ -9,11 +9,24 @@ public class UploadResult {
private String imageUrl;
private String canonicalFilename;
+ /**
+ * Minimal constructor
+ *
+ * @param resultStatus Upload result status
+ * @param errorCode Upload error code
+ */
UploadResult(String resultStatus, String errorCode) {
this.resultStatus = resultStatus;
this.errorCode = errorCode;
}
+ /**
+ * Full-fledged constructor
+ * @param resultStatus Upload result status
+ * @param dateUploaded Uploaded date
+ * @param canonicalFilename Uploaded file name
+ * @param imageUrl Uploaded image file name
+ */
UploadResult(String resultStatus, Date dateUploaded, String canonicalFilename, String imageUrl) {
this.resultStatus = resultStatus;
this.dateUploaded = dateUploaded;
@@ -21,22 +34,42 @@ public class UploadResult {
this.imageUrl = imageUrl;
}
+ /**
+ * Gets uploaded date
+ * @return Upload date
+ */
public Date getDateUploaded() {
return dateUploaded;
}
+ /**
+ * Gets image url
+ * @return Uploaded image url
+ */
public String getImageUrl() {
return imageUrl;
}
+ /**
+ * Gets canonical file name
+ * @return Uploaded file name
+ */
public String getCanonicalFilename() {
return canonicalFilename;
}
+ /**
+ * Gets upload error code
+ * @return Error code
+ */
public String getErrorCode() {
return errorCode;
}
+ /**
+ * Gets upload result status
+ * @return Upload result status
+ */
public String getResultStatus() {
return resultStatus;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
index d0322a187..0e67afc2f 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java
@@ -1,21 +1,15 @@
package fr.free.nrw.commons.nearby;
-import android.Manifest;
-import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.location.LocationManager;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
-import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.view.Menu;
import android.view.MenuInflater;
@@ -29,30 +23,43 @@ import com.google.gson.GsonBuilder;
import java.util.List;
+import javax.inject.Inject;
+
import butterknife.BindView;
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
+import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.UriSerializer;
+import fr.free.nrw.commons.utils.ViewUtil;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
-public class NearbyActivity extends NavigationBaseActivity {
+public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
- @BindView(R.id.progressBar)
- ProgressBar progressBar;
private static final int LOCATION_REQUEST = 1;
private static final String MAP_LAST_USED_PREFERENCE = "mapLastUsed";
- private LocationServiceManager locationManager;
+ @BindView(R.id.progressBar)
+ ProgressBar progressBar;
+
+ @Inject
+ LocationServiceManager locationManager;
+ @Inject
+ NearbyController nearbyController;
+
private LatLng curLatLang;
private Bundle bundle;
- private NearbyAsyncTask nearbyAsyncTask;
private SharedPreferences sharedPreferences;
private NearbyActivityMode viewMode;
+ private Disposable placesDisposable;
+ private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -60,7 +67,6 @@ public class NearbyActivity extends NavigationBaseActivity {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
setContentView(R.layout.activity_nearby);
ButterKnife.bind(this);
- checkLocationPermission();
bundle = new Bundle();
initDrawer();
initViewState();
@@ -92,7 +98,8 @@ public class NearbyActivity extends NavigationBaseActivity {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_refresh:
- refreshView();
+ lockNearbyView(false);
+ refreshView(true);
return true;
case R.id.action_toggle_view:
viewMode = viewMode.toggle();
@@ -104,60 +111,9 @@ public class NearbyActivity extends NavigationBaseActivity {
}
}
- private void startLookingForNearby() {
- locationManager = new LocationServiceManager(this);
- locationManager.registerLocationManager();
- curLatLang = locationManager.getLatestLocation();
- nearbyAsyncTask = new NearbyAsyncTask(this);
- nearbyAsyncTask.execute();
- }
-
- private void checkLocationPermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- startLookingForNearby();
- } else {
- if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.ACCESS_FINE_LOCATION)
- != PackageManager.PERMISSION_GRANTED) {
-
- // Should we show an explanation?
- if (ActivityCompat.shouldShowRequestPermissionRationale(this,
- Manifest.permission.ACCESS_FINE_LOCATION)) {
-
- // Show an explanation to the user *asynchronously* -- don't block
- // this thread waiting for the user's response! After the user
- // sees the explanation, try again to request the permission.
-
- new AlertDialog.Builder(this)
- .setMessage(getString(R.string.location_permission_rationale))
- .setPositiveButton("OK", (dialog, which) -> {
- ActivityCompat.requestPermissions(NearbyActivity.this,
- new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
- LOCATION_REQUEST);
- dialog.dismiss();
- })
- .setNegativeButton("Cancel", null)
- .create()
- .show();
-
- } else {
-
- // No explanation needed, we can request the permission.
-
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
- LOCATION_REQUEST);
-
- // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
- // app-defined int constant. The callback method gets the
- // result of the request.
- }
- }
- }
- } else {
- startLookingForNearby();
+ private void requestLocationPermissions() {
+ if (!isFinishing()) {
+ locationManager.requestPermissions(this);
}
}
@@ -166,16 +122,10 @@ public class NearbyActivity extends NavigationBaseActivity {
switch (requestCode) {
case LOCATION_REQUEST: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- startLookingForNearby();
+ refreshView(false);
} else {
//If permission not granted, go to page that says Nearby Places cannot be displayed
- if (nearbyAsyncTask != null) {
- nearbyAsyncTask.cancel(true);
- }
- if (progressBar != null) {
- progressBar.setVisibility(View.GONE);
- }
-
+ hideProgressBar();
showLocationPermissionDeniedErrorDialog();
}
}
@@ -188,7 +138,7 @@ public class NearbyActivity extends NavigationBaseActivity {
.setCancelable(false)
.setPositiveButton(R.string.give_permission, (dialog, which) -> {
//will ask for the location permission again
- checkLocationPermission();
+ checkGps();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
//dismiss dialog and finish activity
@@ -200,8 +150,7 @@ public class NearbyActivity extends NavigationBaseActivity {
}
private void checkGps() {
- LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
- if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
+ if (!locationManager.isProviderEnabled()) {
Timber.d("GPS is not enabled");
new AlertDialog.Builder(this)
.setMessage(R.string.gps_disabled)
@@ -213,11 +162,48 @@ public class NearbyActivity extends NavigationBaseActivity {
Timber.d("Loaded settings page");
startActivityForResult(callGPSSettingIntent, 1);
})
- .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> dialog.cancel())
+ .setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
+ showLocationPermissionDeniedErrorDialog();
+ dialog.cancel();
+ })
.create()
.show();
} else {
Timber.d("GPS is enabled");
+ checkLocationPermission();
+ }
+ }
+
+ private void checkLocationPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (locationManager.isLocationPermissionGranted()) {
+ refreshView(false);
+ } else {
+ // Should we show an explanation?
+ if (locationManager.isPermissionExplanationRequired(this)) {
+ // Show an explanation to the user *asynchronously* -- don't block
+ // this thread waiting for the user's response! After the user
+ // sees the explanation, try again to request the permission.
+ new AlertDialog.Builder(this)
+ .setMessage(getString(R.string.location_permission_rationale_nearby))
+ .setPositiveButton("OK", (dialog, which) -> {
+ requestLocationPermissions();
+ dialog.dismiss();
+ })
+ .setNegativeButton("Cancel", (dialog, id) -> {
+ showLocationPermissionDeniedErrorDialog();
+ dialog.cancel();
+ })
+ .create()
+ .show();
+
+ } else {
+ // No explanation needed, we can request the permission.
+ requestLocationPermissions();
+ }
+ }
+ } else {
+ refreshView(false);
}
}
@@ -226,104 +212,122 @@ public class NearbyActivity extends NavigationBaseActivity {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
Timber.d("User is back from Settings page");
- refreshView();
+ refreshView(false);
}
}
private void toggleView() {
- if (nearbyAsyncTask != null) {
- if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
- if (viewMode.isMap()) {
- setMapFragment();
- } else {
- setListFragment();
- }
- }
- sharedPreferences.edit().putBoolean(MAP_LAST_USED_PREFERENCE, viewMode.isMap()).apply();
+ if (viewMode.isMap()) {
+ setMapFragment();
+ } else {
+ setListFragment();
+ }
+ sharedPreferences.edit().putBoolean(MAP_LAST_USED_PREFERENCE, viewMode.isMap()).apply();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ locationManager.addLocationListener(this);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ locationManager.removeLocationListener(this);
+ locationManager.unregisterLocationManager();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (placesDisposable != null) {
+ placesDisposable.dispose();
}
}
@Override
protected void onResume() {
super.onResume();
+ lockNearbyView = false;
checkGps();
}
- @Override
- protected void onPause() {
- super.onPause();
- if (nearbyAsyncTask != null) {
- nearbyAsyncTask.cancel(true);
+ /**
+ * This method should be the single point to load/refresh nearby places
+ *
+ * @param isHardRefresh Should display a toast if the location hasn't changed
+ */
+ private void refreshView(boolean isHardRefresh) {
+ if (lockNearbyView) {
+ return;
}
+ locationManager.registerLocationManager();
+ LatLng lastLocation = locationManager.getLastLocation();
+ if (curLatLang != null && curLatLang.equals(lastLocation)) { //refresh view only if location has changed
+ if (isHardRefresh) {
+ ViewUtil.showLongToast(this, R.string.nearby_location_has_not_changed);
+ }
+ return;
+ }
+ curLatLang = lastLocation;
+
+ if (curLatLang == null) {
+ Timber.d("Skipping update of nearby places as location is unavailable");
+ return;
+ }
+
+ progressBar.setVisibility(View.VISIBLE);
+ placesDisposable = Observable.fromCallable(() -> nearbyController
+ .loadAttractionsFromLocation(curLatLang, this))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(this::populatePlaces);
}
- private void refreshView() {
- nearbyAsyncTask = new NearbyAsyncTask(this);
- nearbyAsyncTask.execute();
+ private void populatePlaces(List placeList) {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Uri.class, new UriSerializer())
+ .create();
+ String gsonPlaceList = gson.toJson(placeList);
+ String gsonCurLatLng = gson.toJson(curLatLang);
+
+ if (placeList.size() == 0) {
+ int duration = Toast.LENGTH_SHORT;
+ Toast toast = Toast.makeText(this, R.string.no_nearby, duration);
+ toast.show();
+ }
+
+ bundle.clear();
+ bundle.putString("PlaceList", gsonPlaceList);
+ bundle.putString("CurLatLng", gsonCurLatLng);
+
+ lockNearbyView(true);
+ // Begin the transaction
+ if (viewMode.isMap()) {
+ setMapFragment();
+ } else {
+ setListFragment();
+ }
+
+ hideProgressBar();
}
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (locationManager != null) {
+ private void lockNearbyView(boolean lock) {
+ if (lock) {
+ lockNearbyView = true;
locationManager.unregisterLocationManager();
+ locationManager.removeLocationListener(this);
+ } else {
+ lockNearbyView = false;
+ locationManager.registerLocationManager();
+ locationManager.addLocationListener(this);
}
}
- private class NearbyAsyncTask extends AsyncTask> {
-
- private final Context mContext;
-
- private NearbyAsyncTask(Context context) {
- mContext = context;
- }
-
- @Override
- protected void onProgressUpdate(Integer... values) {
- super.onProgressUpdate(values);
- }
-
- @Override
- protected List doInBackground(Void... params) {
- return NearbyController
- .loadAttractionsFromLocation(curLatLang, CommonsApplication.getInstance()
- );
- }
-
- @Override
- protected void onPostExecute(List placeList) {
- super.onPostExecute(placeList);
-
- if (isCancelled()) {
- return;
- }
-
- Gson gson = new GsonBuilder()
- .registerTypeAdapter(Uri.class, new UriSerializer())
- .create();
- String gsonPlaceList = gson.toJson(placeList);
- String gsonCurLatLng = gson.toJson(curLatLang);
-
- if (placeList.size() == 0) {
- int duration = Toast.LENGTH_SHORT;
- Toast toast = Toast.makeText(mContext, R.string.no_nearby, duration);
- toast.show();
- }
-
- bundle.clear();
- bundle.putString("PlaceList", gsonPlaceList);
- bundle.putString("CurLatLng", gsonCurLatLng);
-
- // Begin the transaction
- if (viewMode.isMap()) {
- setMapFragment();
- } else {
- setListFragment();
- }
-
- if (progressBar != null) {
- progressBar.setVisibility(View.GONE);
- }
+ private void hideProgressBar() {
+ if (progressBar != null) {
+ progressBar.setVisibility(View.GONE);
}
}
@@ -334,7 +338,7 @@ public class NearbyActivity extends NavigationBaseActivity {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragment = new NearbyMapFragment();
fragment.setArguments(bundle);
- fragmentTransaction.replace(R.id.container, fragment);
+ fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().getSimpleName());
fragmentTransaction.commitAllowingStateLoss();
}
@@ -345,12 +349,12 @@ public class NearbyActivity extends NavigationBaseActivity {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragment = new NearbyListFragment();
fragment.setArguments(bundle);
- fragmentTransaction.replace(R.id.container, fragment);
+ fragmentTransaction.replace(R.id.container, fragment, fragment.getClass().getSimpleName());
fragmentTransaction.commitAllowingStateLoss();
}
- public static void startYourself(Context context) {
- Intent settingsIntent = new Intent(context, NearbyActivity.class);
- context.startActivity(settingsIntent);
+ @Override
+ public void onLocationChanged(LatLng latLng) {
+ refreshView(false);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
index 58c8bb8a7..035532c11 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java
@@ -3,7 +3,6 @@ package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
-import android.preference.PreferenceManager;
import android.support.graphics.drawable.VectorDrawableCompat;
import com.mapbox.mapboxsdk.annotations.IconFactory;
@@ -15,7 +14,9 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UiUtils;
@@ -24,45 +25,51 @@ import timber.log.Timber;
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
-
public class NearbyController {
private static final int MAX_RESULTS = 1000;
+ private final NearbyPlaces nearbyPlaces;
+ private final SharedPreferences prefs;
+
+ @Inject
+ public NearbyController(NearbyPlaces nearbyPlaces,
+ @Named("default_preferences") SharedPreferences prefs) {
+ this.nearbyPlaces = nearbyPlaces;
+ this.prefs = prefs;
+ }
/**
* Prepares Place list to make their distance information update later.
+ *
* @param curLatLng current location for user
- * @param context context
+ * @param context context
* @return Place list without distance information
*/
- public static List loadAttractionsFromLocation(LatLng curLatLng, Context context) {
+ public List loadAttractionsFromLocation(LatLng curLatLng, Context context) {
Timber.d("Loading attractions near %s", curLatLng);
if (curLatLng == null) {
return Collections.emptyList();
}
- NearbyPlaces nearbyPlaces = CommonsApplication.getInstance().getNearbyPlaces();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
List places = prefs.getBoolean("useWikidata", true)
? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage())
: nearbyPlaces.getFromWikiNeedsPictures();
- if (curLatLng != null) {
- Timber.d("Sorting places by distance...");
- final Map distances = new HashMap<>();
- for (Place place: places) {
- distances.put(place, computeDistanceBetween(place.location, curLatLng));
- }
- Collections.sort(places,
- (lhs, rhs) -> {
- double lhsDistance = distances.get(lhs);
- double rhsDistance = distances.get(rhs);
- return (int) (lhsDistance - rhsDistance);
- }
- );
+ Timber.d("Sorting places by distance...");
+ final Map distances = new HashMap<>();
+ for (Place place : places) {
+ distances.put(place, computeDistanceBetween(place.location, curLatLng));
}
+ Collections.sort(places,
+ (lhs, rhs) -> {
+ double lhsDistance = distances.get(lhs);
+ double rhsDistance = distances.get(rhs);
+ return (int) (lhsDistance - rhsDistance);
+ }
+ );
return places;
}
/**
* Loads attractions from location for list view, we need to return Place data type.
+ *
* @param curLatLng users current location
* @param placeList list of nearby places in Place data type
* @return Place list that holds nearby places
@@ -71,7 +78,7 @@ public class NearbyController {
LatLng curLatLng,
List placeList) {
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
- for (Place place: placeList) {
+ for (Place place : placeList) {
String distance = formatDistanceBetween(curLatLng, place.location);
place.setDistance(distance);
}
@@ -79,7 +86,8 @@ public class NearbyController {
}
/**
- *Loads attractions from location for map view, we need to return BaseMarkerOption data type.
+ * Loads attractions from location for map view, we need to return BaseMarkerOption data type.
+ *
* @param curLatLng users current location
* @param placeList list of nearby places in Place data type
* @return BaseMarkerOptions list that holds nearby places
@@ -96,26 +104,28 @@ public class NearbyController {
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
- Bitmap icon = UiUtils.getBitmap(
- VectorDrawableCompat.create(
- context.getResources(), R.drawable.ic_custom_map_marker, context.getTheme()
- ));
+ VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(
+ context.getResources(), R.drawable.ic_custom_map_marker, context.getTheme()
+ );
+ if (vectorDrawable != null) {
+ Bitmap icon = UiUtils.getBitmap(vectorDrawable);
- for (Place place: placeList) {
- String distance = formatDistanceBetween(curLatLng, place.location);
- place.setDistance(distance);
+ for (Place place : placeList) {
+ String distance = formatDistanceBetween(curLatLng, place.location);
+ place.setDistance(distance);
- NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
- nearbyBaseMarker.title(place.name);
- nearbyBaseMarker.position(
- new com.mapbox.mapboxsdk.geometry.LatLng(
- place.location.getLatitude(),
- place.location.getLongitude()));
- nearbyBaseMarker.place(place);
- nearbyBaseMarker.icon(IconFactory.getInstance(context)
- .fromBitmap(icon));
+ NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
+ nearbyBaseMarker.title(place.name);
+ nearbyBaseMarker.position(
+ new com.mapbox.mapboxsdk.geometry.LatLng(
+ place.location.getLatitude(),
+ place.location.getLongitude()));
+ nearbyBaseMarker.place(place);
+ nearbyBaseMarker.icon(IconFactory.getInstance(context)
+ .fromBitmap(icon));
- baseMarkerOptions.add(nearbyBaseMarker);
+ baseMarkerOptions.add(nearbyBaseMarker);
+ }
}
return baseMarkerOptions;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java
index 2e677f33d..b383fd9b9 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyInfoDialog.java
@@ -109,7 +109,7 @@ public class NearbyInfoDialog extends OverlayDialog {
NearbyInfoDialog mDialog = new NearbyInfoDialog();
Bundle bundle = new Bundle();
bundle.putString(ARG_TITLE, place.name);
- bundle.putString(ARG_DESC, place.getDescription().getText());
+ bundle.putString(ARG_DESC, place.getLongDescription());
bundle.putDouble(ARG_LATITUDE, place.location.getLatitude());
bundle.putDouble(ARG_LONGITUDE, place.location.getLongitude());
bundle.putParcelable(ARG_SITE_LINK, place.siteLinks);
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
index 00b8a2840..d57a23137 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@@ -17,6 +18,7 @@ import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
+import dagger.android.support.AndroidSupportInjection;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UriDeserializer;
@@ -40,6 +42,12 @@ public class NearbyListFragment extends Fragment {
setRetainInstance(true);
}
+ @Override
+ public void onAttach(Context context) {
+ AndroidSupportInjection.inject(this);
+ super.onAttach(context);
+ }
+
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
@@ -60,7 +68,7 @@ public class NearbyListFragment extends Fragment {
Bundle bundle = this.getArguments();
if (bundle != null) {
- String gsonPlaceList = bundle.getString("PlaceList");
+ String gsonPlaceList = bundle.getString("PlaceList", "[]");
placeList = gson.fromJson(gsonPlaceList, LIST_TYPE);
String gsonLatLng = bundle.getString("CurLatLng");
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java
index d9e1aa633..535198699 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyMapFragment.java
@@ -3,7 +3,6 @@ package fr.free.nrw.commons.nearby;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -77,6 +76,8 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
private void setupMapView(Bundle savedInstanceState) {
MapboxMapOptions options = new MapboxMapOptions()
.styleUrl(Style.OUTDOORS)
+ .logoEnabled(false)
+ .attributionEnabled(false)
.camera(new CameraPosition.Builder()
.target(new LatLng(curLatLng.getLatitude(), curLatLng.getLongitude()))
.zoom(11)
@@ -99,11 +100,8 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
addCurrentLocationMarker(mapboxMap);
});
- if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",false)) {
- mapView.setStyleUrl(getResources().getString(R.string.map_theme_dark));
- } else {
- mapView.setStyleUrl(getResources().getString(R.string.map_theme_light));
- }
+
+ mapView.setStyleUrl("asset://mapstyle.json");
}
/**
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
index 50d661ef6..f13aa1ae3 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java
@@ -34,7 +34,7 @@ public class NearbyPlaces {
public NearbyPlaces() {
try {
- wikidataQuery = FileUtils.readFromResource("/assets/queries/nearby_query.rq");
+ wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
Timber.v(wikidataQuery);
} catch (IOException e) {
throw new RuntimeException(e);
@@ -46,7 +46,7 @@ public class NearbyPlaces {
try {
// increase the radius gradually to find a satisfactory number of nearby places
- while (radius < MAX_RADIUS) {
+ while (radius <= MAX_RADIUS) {
places = getFromWikidataQuery(curLatLng, lang, radius);
Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= MIN_RESULTS) {
@@ -62,6 +62,11 @@ public class NearbyPlaces {
Timber.d("back to initial radius: %f", radius);
radius = INITIAL_RADIUS;
}
+ // make sure we will be able to send at least one request next time
+ if (radius > MAX_RADIUS) {
+ radius = MAX_RADIUS;
+ }
+
return places;
}
@@ -121,7 +126,7 @@ public class NearbyPlaces {
places.add(new Place(
name,
- Place.Description.fromText(type), // list
+ Place.Label.fromText(type), // list
type, // details
Uri.parse(icon),
new LatLng(latitude, longitude, 0),
@@ -183,7 +188,7 @@ public class NearbyPlaces {
places.add(new Place(
name,
- Place.Description.fromText(type), // list
+ Place.Label.fromText(type), // list
type, // details
null,
new LatLng(latitude, longitude, 0),
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java
index 5065a7d93..f08fa6acd 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/NoPermissionsFragment.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby;
+import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
@@ -7,6 +8,7 @@ import android.view.View;
import android.view.ViewGroup;
import butterknife.ButterKnife;
+import dagger.android.support.AndroidSupportInjection;
import fr.free.nrw.commons.R;
import timber.log.Timber;
@@ -18,6 +20,12 @@ public class NoPermissionsFragment extends Fragment {
public NoPermissionsFragment() {
}
+ @Override
+ public void onAttach(Context context) {
+ AndroidSupportInjection.inject(this);
+ super.onAttach(context);
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
index 428fcf6de..e8290c6e2 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java
@@ -13,7 +13,7 @@ import fr.free.nrw.commons.location.LatLng;
public class Place {
public final String name;
- private final Description description;
+ private final Label label;
private final String longDescription;
private final Uri secondaryImageUrl;
public final LatLng location;
@@ -24,18 +24,22 @@ public class Place {
public final Sitelinks siteLinks;
- public Place(String name, Description description, String longDescription,
+ public Place(String name, Label label, String longDescription,
Uri secondaryImageUrl, LatLng location, Sitelinks siteLinks) {
this.name = name;
- this.description = description;
+ this.label = label;
this.longDescription = longDescription;
this.secondaryImageUrl = secondaryImageUrl;
this.location = location;
this.siteLinks = siteLinks;
}
- public Description getDescription() {
- return description;
+ public Label getLabel() {
+ return label;
+ }
+
+ public String getLongDescription() {
+ return longDescription;
}
public void setDistance(String distance) {
@@ -67,10 +71,8 @@ public class Place {
* Most common types of desc: building, house, cottage, farmhouse,
* village, civil parish, church, railway station,
* gatehouse, milestone, inn, secondary school, hotel
- *
- * TODO Give a more accurate class name (see issue #742).
*/
- public enum Description {
+ public enum Label {
BUILDING("building", R.drawable.round_icon_generic_building),
HOUSE("house", R.drawable.round_icon_house),
@@ -95,19 +97,19 @@ public class Place {
WATERFALL("waterfall", R.drawable.round_icon_waterfall),
UNKNOWN("?", R.drawable.round_icon_unknown);
- private static final Map TEXT_TO_DESCRIPTION
- = new HashMap<>(Description.values().length);
+ private static final Map TEXT_TO_DESCRIPTION
+ = new HashMap<>(Label.values().length);
static {
- for (Description description : values()) {
- TEXT_TO_DESCRIPTION.put(description.text, description);
+ for (Label label : values()) {
+ TEXT_TO_DESCRIPTION.put(label.text, label);
}
}
private final String text;
@DrawableRes private final int icon;
- Description(String text, @DrawableRes int icon) {
+ Label(String text, @DrawableRes int icon) {
this.text = text;
this.icon = icon;
}
@@ -121,9 +123,9 @@ public class Place {
return icon;
}
- public static Description fromText(String text) {
- Description description = TEXT_TO_DESCRIPTION.get(text);
- return description == null ? UNKNOWN : description;
+ public static Label fromText(String text) {
+ Label label = TEXT_TO_DESCRIPTION.get(text);
+ return label == null ? UNKNOWN : label;
}
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java
index f4c8a5d61..cf596e36e 100644
--- a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java
+++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceRenderer.java
@@ -43,13 +43,13 @@ class PlaceRenderer extends Renderer {
public void render() {
Place place = getContent();
tvName.setText(place.name);
- String descriptionText = place.getDescription().getText();
+ String descriptionText = place.getLongDescription();
if (descriptionText.equals("?")) {
descriptionText = getContext().getString(R.string.no_description_found);
}
tvDesc.setText(descriptionText);
distance.setText(place.distance);
- icon.setImageResource(place.getDescription().getIcon());
+ icon.setImageResource(place.getLabel().getIcon());
}
interface PlaceClickedListener {
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/MarkReadResponse.java b/app/src/main/java/fr/free/nrw/commons/notification/MarkReadResponse.java
new file mode 100644
index 000000000..03cdd6f88
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/MarkReadResponse.java
@@ -0,0 +1,16 @@
+package fr.free.nrw.commons.notification;
+
+import android.support.annotation.Nullable;
+
+public class MarkReadResponse {
+ @SuppressWarnings("unused") @Nullable
+ private String result;
+
+ public String result() {
+ return result;
+ }
+
+ public static class QueryMarkReadResponse {
+ @SuppressWarnings("unused") @Nullable private MarkReadResponse echomarkread;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/Notification.java b/app/src/main/java/fr/free/nrw/commons/notification/Notification.java
new file mode 100644
index 000000000..e4efbff6c
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/Notification.java
@@ -0,0 +1,21 @@
+package fr.free.nrw.commons.notification;
+
+/**
+ * Created by root on 18.12.2017.
+ */
+
+public class Notification {
+ public NotificationType notificationType;
+ public String notificationText;
+ public String date;
+ public String description;
+ public String link;
+
+ public Notification(NotificationType notificationType, String notificationText, String date, String description, String link) {
+ this.notificationType = notificationType;
+ this.notificationText = notificationText;
+ this.date = date;
+ this.description = description;
+ this.link = link;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java
new file mode 100644
index 000000000..dc9d733f4
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java
@@ -0,0 +1,86 @@
+package fr.free.nrw.commons.notification;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+
+import com.pedrogomez.renderers.RVRendererAdapter;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.theme.NavigationBaseActivity;
+import io.reactivex.Observable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import timber.log.Timber;
+
+/**
+ * Created by root on 18.12.2017.
+ */
+
+public class NotificationActivity extends NavigationBaseActivity {
+ NotificationAdapterFactory notificationAdapterFactory;
+
+ @BindView(R.id.listView) RecyclerView recyclerView;
+
+ @Inject NotificationController controller;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_notification);
+ ButterKnife.bind(this);
+ initListView();
+ initDrawer();
+ }
+
+ private void initListView() {
+ recyclerView = findViewById(R.id.listView);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ addNotifications();
+ }
+
+ @SuppressLint("CheckResult")
+ private void addNotifications() {
+ Timber.d("Add notifications");
+
+ Observable.fromCallable(() -> controller.getNotifications())
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(notificationList -> {
+ Timber.d("Number of notifications is %d", notificationList.size());
+ setAdapter(notificationList);
+ }, throwable -> Timber.e(throwable, "Error occurred while loading notifications"));
+ }
+
+ private void handleUrl(String url) {
+ if (url == null || url.equals("")) {
+ return;
+ }
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ }
+
+ private void setAdapter(List notificationList) {
+ notificationAdapterFactory = new NotificationAdapterFactory(notification -> {
+ Timber.d("Notification clicked %s", notification.link);
+ handleUrl(notification.link);
+ });
+ RVRendererAdapter adapter = notificationAdapterFactory.create(notificationList);
+ recyclerView.setAdapter(adapter);
+ }
+
+ public static void startYourself(Context context) {
+ Intent intent = new Intent(context, NotificationActivity.class);
+ context.startActivity(intent);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationAdapterFactory.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationAdapterFactory.java
new file mode 100644
index 000000000..83a60dcfb
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationAdapterFactory.java
@@ -0,0 +1,30 @@
+package fr.free.nrw.commons.notification;
+
+import android.support.annotation.NonNull;
+
+import com.pedrogomez.renderers.ListAdapteeCollection;
+import com.pedrogomez.renderers.RVRendererAdapter;
+import com.pedrogomez.renderers.RendererBuilder;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by root on 19.12.2017.
+ */
+
+class NotificationAdapterFactory {
+ private NotificationRenderer.NotificationClicked listener;
+
+ NotificationAdapterFactory(@NonNull NotificationRenderer.NotificationClicked listener) {
+ this.listener = listener;
+ }
+
+ public RVRendererAdapter create(List notifications) {
+ RendererBuilder builder = new RendererBuilder()
+ .bind(Notification.class, new NotificationRenderer(listener));
+ ListAdapteeCollection collection = new ListAdapteeCollection<>(
+ notifications != null ? notifications : Collections.emptyList());
+ return new RVRendererAdapter<>(builder, collection);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java
new file mode 100644
index 000000000..b22bafbb5
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationController.java
@@ -0,0 +1,39 @@
+package fr.free.nrw.commons.notification;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
+
+/**
+ * Created by root on 19.12.2017.
+ */
+@Singleton
+public class NotificationController {
+
+ private MediaWikiApi mediaWikiApi;
+ private SessionManager sessionManager;
+
+ @Inject
+ public NotificationController(MediaWikiApi mediaWikiApi, SessionManager sessionManager) {
+ this.mediaWikiApi = mediaWikiApi;
+ this.sessionManager = sessionManager;
+ }
+
+ public List getNotifications() throws IOException {
+ if (mediaWikiApi.validateLogin()) {
+ return mediaWikiApi.getNotifications();
+ } else {
+ Boolean authTokenValidated = sessionManager.revalidateAuthToken();
+ if (authTokenValidated != null && authTokenValidated) {
+ return mediaWikiApi.getNotifications();
+ }
+ }
+ return new ArrayList<>();
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java
new file mode 100644
index 000000000..a5aac0508
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java
@@ -0,0 +1,64 @@
+package fr.free.nrw.commons.notification;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.pedrogomez.renderers.Renderer;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import fr.free.nrw.commons.R;
+
+/**
+ * Created by root on 19.12.2017.
+ */
+
+public class NotificationRenderer extends Renderer {
+ @BindView(R.id.title) TextView title;
+ @BindView(R.id.description) TextView description;
+ @BindView(R.id.time) TextView time;
+ @BindView(R.id.icon) ImageView icon;
+ private NotificationClicked listener;
+
+
+ NotificationRenderer(NotificationClicked listener) {
+ this.listener = listener;
+ }
+
+ @Override
+ protected void setUpView(View view) { }
+
+ @Override
+ protected void hookListeners(View rootView) {
+ rootView.setOnClickListener(v -> listener.notificationClicked(getContent()));
+ }
+
+ @Override
+ protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
+ View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false);
+ ButterKnife.bind(this, inflatedView);
+ return inflatedView;
+ }
+
+ @Override
+ public void render() {
+ Notification notification = getContent();
+ title.setText(notification.notificationText);
+ time.setText(notification.date);
+ description.setText(notification.description);
+ switch (notification.notificationType) {
+ case THANK_YOU_EDIT:
+ icon.setImageResource(R.drawable.ic_edit_black_24dp);
+ break;
+ default:
+ icon.setImageResource(R.drawable.round_icon_unknown);
+ }
+ }
+
+ public interface NotificationClicked{
+ void notificationClicked(Notification notification);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationType.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationType.java
new file mode 100644
index 000000000..b83b23b2a
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationType.java
@@ -0,0 +1,27 @@
+package fr.free.nrw.commons.notification;
+
+public enum NotificationType {
+ THANK_YOU_EDIT("thank-you-edit"),
+ EDIT_USER_TALK("edit-user-talk"),
+ MENTION("mention"),
+ WELCOME("welcome"),
+ UNKNOWN("unknown");
+ private String type;
+
+ NotificationType(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public static NotificationType handledValueOf(String name) {
+ for (NotificationType e : values()) {
+ if (e.getType().equals(name)) {
+ return e;
+ }
+ }
+ return UNKNOWN;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java
new file mode 100644
index 000000000..7f32da126
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java
@@ -0,0 +1,116 @@
+package fr.free.nrw.commons.notification;
+
+import android.content.Context;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.annotation.Nullable;
+
+import fr.free.nrw.commons.BuildConfig;
+import fr.free.nrw.commons.R;
+
+public class NotificationUtils {
+
+ private static final String COMMONS_WIKI = "commonswiki";
+
+ public static boolean isCommonsNotification(Node document) {
+ if (document == null || !document.hasAttributes()) {
+ return false;
+ }
+ Element element = (Element) document;
+ return COMMONS_WIKI.equals(element.getAttribute("wiki"));
+ }
+
+ public static NotificationType getNotificationType(Node document) {
+ Element element = (Element) document;
+ String type = element.getAttribute("type");
+ return NotificationType.handledValueOf(type);
+ }
+
+ public static Notification getNotificationFromApiResult(Context context, Node document) {
+ NotificationType type = getNotificationType(document);
+
+ String notificationText = "";
+ String link = getNotificationLink(document);
+ String description = getNotificationDescription(document);
+ switch (type) {
+ case THANK_YOU_EDIT:
+ notificationText = context.getString(R.string.notifications_thank_you_edit);
+ break;
+ case EDIT_USER_TALK:
+ notificationText = getUserTalkMessage(context, document);
+ break;
+ case MENTION:
+ notificationText = getMentionMessage(context, document);
+ break;
+ case WELCOME:
+ notificationText = getWelcomeMessage(context, document);
+ break;
+ }
+ return new Notification(type, notificationText, getTimestamp(document), description, link);
+ }
+
+ public static String getMentionMessage(Context context, Node document) {
+ String format = context.getString(R.string.notifications_mention);
+ return String.format(format, getAgent(document), getNotificationDescription(document));
+ }
+
+ public static String getUserTalkMessage(Context context, Node document) {
+ String format = context.getString(R.string.notifications_talk_page_message);
+ return String.format(format, getAgent(document));
+ }
+
+ public static String getWelcomeMessage(Context context, Node document) {
+ String welcomeMessageFormat = context.getString(R.string.notifications_welcome);
+ return String.format(welcomeMessageFormat, getAgent(document));
+ }
+
+ private static String getAgent(Node document) {
+ Element agentElement = (Element) getNode(document, "agent");
+ if (agentElement != null) {
+ return agentElement.getAttribute("name");
+ }
+ return "";
+ }
+
+ private static String getTimestamp(Node document) {
+ Element timestampElement = (Element) getNode(document, "timestamp");
+ if (timestampElement != null) {
+ return timestampElement.getAttribute("date");
+ }
+ return "";
+ }
+
+ private static String getNotificationLink(Node document) {
+ String format = "%s%s";
+ Element titleElement = (Element) getNode(document, "title");
+ if (titleElement != null) {
+ String fullName = titleElement.getAttribute("full");
+ return String.format(format, BuildConfig.HOME_URL, fullName);
+ }
+ return "";
+ }
+
+ private static String getNotificationDescription(Node document) {
+ Element titleElement = (Element) getNode(document, "title");
+ if (titleElement != null) {
+ return titleElement.getAttribute("text");
+ }
+ return "";
+ }
+
+ @Nullable
+ public static Node getNode(Node node, String nodeName) {
+ NodeList childNodes = node.getChildNodes();
+ for (int i = 0; i < childNodes.getLength(); i++) {
+ Node nodeItem = childNodes.item(i);
+ Element item = (Element) nodeItem;
+ if (item.getTagName().equals(nodeName)) {
+ return nodeItem;
+ }
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java
index 5814ec904..54b462097 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsActivity.java
@@ -1,62 +1,70 @@
-package fr.free.nrw.commons.settings;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.v7.app.AppCompatDelegate;
-import android.view.MenuItem;
-
-import butterknife.ButterKnife;
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.theme.NavigationBaseActivity;
-
-public class SettingsActivity extends NavigationBaseActivity {
- private AppCompatDelegate settingsDelegate;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- // Check prefs on every activity starts
- if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false)) {
- setTheme(R.style.DarkAppTheme);
- } else {
- setTheme(R.style.LightAppTheme);
- }
-
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_settings);
-
- ButterKnife.bind(this);
- initDrawer();
- }
-
- // Get an action bar
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- if (settingsDelegate == null) {
- settingsDelegate = AppCompatDelegate.create(this, null);
- }
- settingsDelegate.onPostCreate(savedInstanceState);
-
- //Get an up button
- //settingsDelegate.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- //Handle action-bar clicks
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- public static void startYourself(Context context) {
- Intent settingsIntent = new Intent(context, SettingsActivity.class);
- context.startActivity(settingsIntent);
- }
+package fr.free.nrw.commons.settings;
+
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatDelegate;
+import android.view.MenuItem;
+
+import butterknife.ButterKnife;
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.theme.NavigationBaseActivity;
+
+/**
+ * allows the user to change the settings
+ */
+public class SettingsActivity extends NavigationBaseActivity {
+ private AppCompatDelegate settingsDelegate;
+
+ /**
+ * to be called when the activity starts
+ * @param savedInstanceState the previously saved state
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Check prefs on every activity starts
+ if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false)) {
+ setTheme(R.style.DarkAppTheme);
+ } else {
+ setTheme(R.style.LightAppTheme);
+ }
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+
+ ButterKnife.bind(this);
+ initDrawer();
+ }
+
+ // Get an action bar
+ /**
+ * takes care of actions taken after the creation has happened
+ * @param savedInstanceState the saved state
+ */
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ if (settingsDelegate == null) {
+ settingsDelegate = AppCompatDelegate.create(this, null);
+ }
+ settingsDelegate.onPostCreate(savedInstanceState);
+
+ //Get an up button
+ //settingsDelegate.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ /**
+ * Handle action-bar clicks
+ * @param item the selected item
+ * @return true on success, false on failure
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
index 0640ea444..66be88e3f 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java
@@ -1,22 +1,50 @@
package fr.free.nrw.commons.settings;
+import android.Manifest;
import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
+import android.preference.Preference;
import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.FileProvider;
+import android.widget.Toast;
+import java.io.File;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.di.ApplicationlessInjection;
+import fr.free.nrw.commons.utils.FileUtils;
public class SettingsFragment extends PreferenceFragment {
+
+ private static final int REQUEST_CODE_WRITE_EXTERNAL_STORAGE = 100;
+
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ ApplicationlessInjection
+ .getInstance(getActivity().getApplicationContext())
+ .getCommonsApplicationComponent()
+ .inject(this);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
@@ -38,14 +66,17 @@ public class SettingsFragment extends PreferenceFragment {
});
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
- final SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
- int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
+ int uploads = prefs.getInt(Prefs.UPLOADS_SHOWING, 100);
uploadLimit.setText(uploads + "");
uploadLimit.setSummary(uploads + "");
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
- int value = Integer.parseInt(newValue.toString());
- final SharedPreferences.Editor editor = sharedPref.edit();
+ int value;
+ try {
+ value = Integer.parseInt(newValue.toString());
+ } catch(Exception e) {
+ value = 100; //Default number
+ }
+ final SharedPreferences.Editor editor = prefs.edit();
if (value > 500) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.maximum_limit)
@@ -58,14 +89,71 @@ public class SettingsFragment extends PreferenceFragment {
uploadLimit.setSummary(500 + "");
uploadLimit.setText(500 + "");
} else {
- editor.putInt(Prefs.UPLOADS_SHOWING, Integer.parseInt(newValue.toString()));
+ editor.putInt(Prefs.UPLOADS_SHOWING, value);
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
- uploadLimit.setSummary(newValue.toString());
+ uploadLimit.setSummary(String.valueOf(value));
}
editor.apply();
return true;
});
+ Preference sendLogsPreference = findPreference("sendLogFile");
+ sendLogsPreference.setOnPreferenceClickListener(preference -> {
+ //first we need to check if we have the necessary permissions
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (ContextCompat.checkSelfPermission(
+ getActivity(),
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ ==
+ PackageManager.PERMISSION_GRANTED) {
+ sendAppLogsViaEmail();
+ } else {
+ //first get the necessary permission
+ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ REQUEST_CODE_WRITE_EXTERNAL_STORAGE);
+ }
+ } else {
+ sendAppLogsViaEmail();
+ }
+ return true;
+ });
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_CODE_WRITE_EXTERNAL_STORAGE) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ sendAppLogsViaEmail();
+ }
+ }
+ }
+
+ private void sendAppLogsViaEmail() {
+ String appLogs = Utils.getAppLogs();
+ File appLogsFile = FileUtils.createAndGetAppLogsFile(appLogs);
+
+ Context applicationContext = getActivity().getApplicationContext();
+ Uri appLogsFilePath = FileProvider.getUriForFile(
+ getActivity(),
+ applicationContext.getPackageName() + ".provider",
+ appLogsFile
+ );
+
+ Intent feedbackIntent = new Intent(Intent.ACTION_SEND);
+ feedbackIntent.setType("message/rfc822");
+ feedbackIntent.putExtra(Intent.EXTRA_EMAIL,
+ new String[]{CommonsApplication.LOGS_PRIVATE_EMAIL});
+ feedbackIntent.putExtra(Intent.EXTRA_SUBJECT,
+ String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT,
+ BuildConfig.VERSION_NAME));
+ feedbackIntent.putExtra(Intent.EXTRA_STREAM,appLogsFilePath);
+
+ try {
+ startActivity(feedbackIntent);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
+ }
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java
index af4f0eff4..8ef8e84a4 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/BaseActivity.java
@@ -3,18 +3,17 @@ package fr.free.nrw.commons.theme;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v7.app.AppCompatActivity;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity;
-public class BaseActivity extends AppCompatActivity {
+public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
boolean currentTheme;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
- if(Utils.isDarkTheme(this)){
+ boolean currentThemeIsDark = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false);
+ if (currentThemeIsDark){
currentTheme = true;
setTheme(R.style.DarkAppTheme);
} else {
@@ -27,8 +26,8 @@ public class BaseActivity extends AppCompatActivity {
@Override
protected void onResume() {
// Restart activity if theme is changed
- boolean newTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",false);
- if(currentTheme!=newTheme){ //is activity theme changed
+ boolean newTheme = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme", false);
+ if (currentTheme != newTheme) { //is activity theme changed
Intent intent = getIntent();
finish();
startActivity(intent);
diff --git a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
index 3537e4a1d..99c9f253b 100644
--- a/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/theme/NavigationBaseActivity.java
@@ -1,6 +1,9 @@
package fr.free.nrw.commons.theme;
+import android.accounts.Account;
+import android.accounts.AccountManager;
import android.content.ActivityNotFoundException;
+import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
@@ -9,7 +12,9 @@ import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView;
@@ -18,9 +23,11 @@ import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.WelcomeActivity;
+import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.nearby.NearbyActivity;
+import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import timber.log.Timber;
@@ -47,6 +54,22 @@ public abstract class NavigationBaseActivity extends BaseActivity
toggle.setDrawerIndicatorEnabled(true);
toggle.syncState();
setDrawerPaneWidth();
+ setUserName();
+ }
+
+ /**
+ * Set the username in navigationHeader.
+ */
+ private void setUserName() {
+
+ View navHeaderView = navigationView.getHeaderView(0);
+ TextView username = navHeaderView.findViewById(R.id.username);
+
+ AccountManager accountManager = AccountManager.get(this);
+ Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
+ if (allAccounts.length != 0) {
+ username.setText(allAccounts[0].name);
+ }
}
public void initBackButton() {
@@ -70,30 +93,25 @@ public abstract class NavigationBaseActivity extends BaseActivity
@Override
public boolean onNavigationItemSelected(@NonNull final MenuItem item) {
- switch (item.getItemId()) {
+ final int itemId = item.getItemId();
+ switch (itemId) {
case R.id.action_home:
drawerLayout.closeDrawer(navigationView);
- if (!(this instanceof ContributionsActivity)) {
- ContributionsActivity.startYourself(this);
- }
+ startActivityWithFlags(
+ this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
+ Intent.FLAG_ACTIVITY_SINGLE_TOP);
return true;
case R.id.action_nearby:
drawerLayout.closeDrawer(navigationView);
- if (!(this instanceof NearbyActivity)) {
- NearbyActivity.startYourself(this);
- }
+ startActivityWithFlags(this, NearbyActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return true;
case R.id.action_about:
drawerLayout.closeDrawer(navigationView);
- if (!(this instanceof AboutActivity)) {
- AboutActivity.startYourself(this);
- }
+ startActivityWithFlags(this, AboutActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return true;
case R.id.action_settings:
drawerLayout.closeDrawer(navigationView);
- if (!(this instanceof SettingsActivity)) {
- SettingsActivity.startYourself(this);
- }
+ startActivityWithFlags(this, SettingsActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return true;
case R.id.action_introduction:
drawerLayout.closeDrawer(navigationView);
@@ -126,7 +144,12 @@ public abstract class NavigationBaseActivity extends BaseActivity
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.show();
return true;
+ case R.id.action_notifications:
+ drawerLayout.closeDrawer(navigationView);
+ NotificationActivity.startYourself(this);
+ return true;
default:
+ Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
return false;
}
}
@@ -143,4 +166,12 @@ public abstract class NavigationBaseActivity extends BaseActivity
finish();
}
}
+
+ public static void startActivityWithFlags(Context context, Class cls, int... flags) {
+ Intent intent = new Intent(context, cls);
+ for (int flag: flags) {
+ intent.addFlags(flag);
+ }
+ context.startActivity(intent);
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/CompatTextView.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/CompatTextView.java
index 2508877ed..abe2e2554 100644
--- a/app/src/main/java/fr/free/nrw/commons/ui/widget/CompatTextView.java
+++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/CompatTextView.java
@@ -1,73 +1,100 @@
-package fr.free.nrw.commons.ui.widget;
-
-/**
- * Created by mikel on 07/08/2017.
- */
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.AppCompatDrawableManager;
-import android.support.v7.widget.AppCompatTextView;
-import android.util.AttributeSet;
-
-import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.utils.UiUtils;
-
-public class CompatTextView extends AppCompatTextView {
- public CompatTextView(Context context) {
- super(context);
- init(null);
- }
-
- public CompatTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(attrs);
- }
-
- public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init(attrs);
- }
-
- private void init(@Nullable AttributeSet attrs) {
- if (attrs != null) {
- Context context = getContext();
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
-
- // Obtain DrawableManager used to pull Drawables safely, and check if we're in RTL
- AppCompatDrawableManager dm = AppCompatDrawableManager.get();
- boolean rtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
-
- // Grab the compat drawable padding from the XML
- float drawablePadding = a.getDimension(R.styleable.CompatTextView_drawablePadding, 0);
-
- // Grab the compat drawable resources from the XML
- int startDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableStart, 0);
- int topDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableTop, 0);
- int endDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableEnd, 0);
- int bottomDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableBottom, 0);
-
- // Load the used drawables, fall back to whatever was set in an "android:"
- Drawable[] currentDrawables = getCompoundDrawables();
- Drawable left = startDrawableRes != 0
- ? dm.getDrawable(context, startDrawableRes) : currentDrawables[0];
- Drawable right = endDrawableRes != 0
- ? dm.getDrawable(context, endDrawableRes) : currentDrawables[1];
- Drawable top = topDrawableRes != 0
- ? dm.getDrawable(context, topDrawableRes) : currentDrawables[2];
- Drawable bottom = bottomDrawableRes != 0
- ? dm.getDrawable(context, bottomDrawableRes) : currentDrawables[3];
-
- // Account for RTL and apply the compound Drawables
- Drawable start = rtl ? right : left;
- Drawable end = rtl ? left : right;
- setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
- setCompoundDrawablePadding((int) UiUtils.convertDpToPixel(drawablePadding, getContext()));
-
- a.recycle();
- }
- }
-}
+package fr.free.nrw.commons.ui.widget;
+
+/*
+ *Created by mikel on 07/08/2017.
+ */
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.AppCompatDrawableManager;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+
+import fr.free.nrw.commons.R;
+import fr.free.nrw.commons.utils.UiUtils;
+
+/**
+ * a text view compatible with older versions of the platform
+ */
+public class CompatTextView extends AppCompatTextView {
+
+ /**
+ * Constructs a new instance of CompatTextView
+ *
+ * @param context the view context
+ */
+ public CompatTextView(Context context) {
+ super(context);
+ init(null);
+ }
+
+ /**
+ * Constructs a new instance of CompatTextView
+ *
+ * @param context the view context
+ * @param attrs the set of attributes for the view
+ */
+ public CompatTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs);
+ }
+
+ /**
+ * Constructs a new instance of CompatTextView
+ *
+ * @param context
+ * @param attrs
+ * @param defStyleAttr
+ */
+ public CompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(attrs);
+ }
+
+ /**
+ * initializes the view
+ *
+ * @param attrs the attribute set of the view, which can be null
+ */
+ private void init(@Nullable AttributeSet attrs) {
+ if (attrs != null) {
+ Context context = getContext();
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CompatTextView);
+
+ // Obtain DrawableManager used to pull Drawables safely, and check if we're in RTL
+ AppCompatDrawableManager dm = AppCompatDrawableManager.get();
+ boolean rtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
+
+ // Grab the compat drawable padding from the XML
+ float drawablePadding = a.getDimension(R.styleable.CompatTextView_drawablePadding, 0);
+
+ // Grab the compat drawable resources from the XML
+ int startDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableStart, 0);
+ int topDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableTop, 0);
+ int endDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableEnd, 0);
+ int bottomDrawableRes = a.getResourceId(R.styleable.CompatTextView_drawableBottom, 0);
+
+ // Load the used drawables, fall back to whatever was set in an "android:"
+ Drawable[] currentDrawables = getCompoundDrawables();
+ Drawable left = startDrawableRes != 0
+ ? dm.getDrawable(context, startDrawableRes) : currentDrawables[0];
+ Drawable right = endDrawableRes != 0
+ ? dm.getDrawable(context, endDrawableRes) : currentDrawables[1];
+ Drawable top = topDrawableRes != 0
+ ? dm.getDrawable(context, topDrawableRes) : currentDrawables[2];
+ Drawable bottom = bottomDrawableRes != 0
+ ? dm.getDrawable(context, bottomDrawableRes) : currentDrawables[3];
+
+ // Account for RTL and apply the compound Drawables
+ Drawable start = rtl ? right : left;
+ Drawable end = rtl ? left : right;
+ setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
+ setCompoundDrawablePadding((int) UiUtils.convertDpToPixel(drawablePadding, getContext()));
+
+ a.recycle();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java
index 5afa5cac3..4e73776c1 100644
--- a/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java
+++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/HtmlTextView.java
@@ -1,26 +1,51 @@
-package fr.free.nrw.commons.ui.widget;
-
-import android.content.Context;
-import android.support.v7.widget.AppCompatTextView;
-import android.text.method.LinkMovementMethod;
-import android.util.AttributeSet;
-
-import fr.free.nrw.commons.Utils;
-
-/**
- * An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
- * links clickable.
- */
-public class HtmlTextView extends AppCompatTextView {
-
- public HtmlTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- setMovementMethod(LinkMovementMethod.getInstance());
- setText(Utils.fromHtml(getText().toString()));
- }
-
- public void setHtmlText(String newText) {
- setText(Utils.fromHtml(newText));
- }
-}
+package fr.free.nrw.commons.ui.widget;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.widget.AppCompatTextView;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.method.LinkMovementMethod;
+import android.util.AttributeSet;
+
+/**
+ * An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
+ * links clickable.
+ */
+public class HtmlTextView extends AppCompatTextView {
+
+ /**
+ * Constructs a new instance of HtmlTextView
+ * @param context the context of the view
+ * @param attrs the set of attributes for the view
+ */
+ public HtmlTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setMovementMethod(LinkMovementMethod.getInstance());
+ setText(fromHtml(getText().toString()));
+ }
+
+ /**
+ * Sets the text to be displayed
+ * @param newText the text to be displayed
+ */
+ public void setHtmlText(String newText) {
+ setText(fromHtml(newText));
+ }
+
+ /**
+ * Fix Html.fromHtml is deprecated problem
+ *
+ * @param source provided Html string
+ * @return returned Spanned of appropriate method according to version check
+ */
+ private static Spanned fromHtml(String source) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ return Html.fromHtml(source, Html.FROM_HTML_MODE_LEGACY);
+ } else {
+ //noinspection deprecation
+ return Html.fromHtml(source);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java b/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java
index 6b2913d6d..58fccf4d7 100644
--- a/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java
+++ b/app/src/main/java/fr/free/nrw/commons/ui/widget/OverlayDialog.java
@@ -1,46 +1,69 @@
-package fr.free.nrw.commons.ui.widget;
-
-import android.app.Dialog;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
-import android.view.Gravity;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-
-public abstract class OverlayDialog extends DialogFragment {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setStyle(STYLE_NO_FRAME, android.R.style.Theme_Holo_Light);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- setDialogLayoutToFullScreen();
- super.onViewCreated(view, savedInstanceState);
- }
-
- private void setDialogLayoutToFullScreen() {
- Window window = getDialog().getWindow();
- WindowManager.LayoutParams wlp = window.getAttributes();
- window.requestFeature(Window.FEATURE_NO_TITLE);
- wlp.gravity = Gravity.BOTTOM;
- wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
- wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
- window.setAttributes(wlp);
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Dialog dialog = super.onCreateDialog(savedInstanceState);
- Window window = dialog.getWindow();
- window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- return dialog;
- }
-}
\ No newline at end of file
+package fr.free.nrw.commons.ui.widget;
+
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.DialogFragment;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+
+/**
+ * a formatted dialog fragment
+ * This class is used by NearbyInfoDialog
+ */
+public abstract class OverlayDialog extends DialogFragment {
+
+ /**
+ * creates a DialogFragment with the correct style and theme
+ * @param savedInstanceState bundle re-constructed from a previous saved state
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(STYLE_NO_FRAME, android.R.style.Theme_Holo_Light);
+ }
+
+ /**
+ * When the view is created, sets the dialog layout to full screen
+ *
+ * @param view the view being used
+ * @param savedInstanceState bundle re-constructed from a previous saved state
+ */
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ setDialogLayoutToFullScreen();
+ super.onViewCreated(view, savedInstanceState);
+ }
+
+ /**
+ * sets the dialog layout to fullscreen
+ */
+ private void setDialogLayoutToFullScreen() {
+ Window window = getDialog().getWindow();
+ WindowManager.LayoutParams wlp = window.getAttributes();
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ wlp.gravity = Gravity.BOTTOM;
+ wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
+ window.setAttributes(wlp);
+ }
+
+ /**
+ * builds custom dialog container
+ *
+ * @param savedInstanceState the previously saved state
+ * @return the dialog
+ */
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ Window window = dialog.getWindow();
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ return dialog;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java b/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
index b76150643..fee0765a4 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ExistingFileAsync.java
@@ -7,7 +7,6 @@ import android.support.v7.app.AlertDialog;
import java.io.IOException;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
@@ -18,6 +17,7 @@ import timber.log.Timber;
* Displays a warning to the user if the file already exists on Commons
*/
public class ExistingFileAsync extends AsyncTask {
+
interface Callback {
void onResult(Result result);
}
@@ -28,14 +28,16 @@ public class ExistingFileAsync extends AsyncTask {
DUPLICATE_CANCELLED
}
+ private final MediaWikiApi api;
private final String fileSha1;
private final Context context;
private final Callback callback;
- public ExistingFileAsync(String fileSha1, Context context, Callback callback) {
+ public ExistingFileAsync(String fileSha1, Context context, Callback callback, MediaWikiApi mwApi) {
this.fileSha1 = fileSha1;
this.context = context;
this.callback = callback;
+ this.api = mwApi;
}
@Override
@@ -45,7 +47,6 @@ public class ExistingFileAsync extends AsyncTask {
@Override
protected Boolean doInBackground(Void... voids) {
- MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
boolean fileExists;
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
index 3c0775ac5..daa2b6d09 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
@@ -34,7 +34,7 @@ public class FileUtils {
* other file-based ContentProviders.
*
* @param context The context.
- * @param uri The Uri to query.
+ * @param uri The Uri to query.
* @author paulburke
*/
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
@@ -55,29 +55,31 @@ public class FileUtils {
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
- }
- // DownloadsProvider
- else if (isDownloadsDocument(uri)) {
+ } else if (isDownloadsDocument(uri)) { // DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
- }
- // MediaProvider
- else if (isMediaDocument(uri)) {
+ } else if (isMediaDocument(uri)) { // MediaProvider
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
- if ("image".equals(type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ switch (type) {
+ case "image":
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ break;
+ case "video":
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ break;
+ case "audio":
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ break;
+ default:
+ break;
}
final String selection = "_id=?";
@@ -104,7 +106,7 @@ public class FileUtils {
= context.getContentResolver().openFileDescriptor(uri, "r");
if (descriptor != null) {
SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
+ .getDefaultSharedPreferences(context);
boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
if (useExtStorage) {
copyPath = Environment.getExternalStorageDirectory().toString()
@@ -162,8 +164,9 @@ public class FileUtils {
} catch (IllegalArgumentException e) {
Timber.d(e);
} finally {
- if (cursor != null)
+ if (cursor != null) {
cursor.close();
+ }
}
return null;
}
@@ -201,7 +204,8 @@ public class FileUtils {
/**
* Copy content from source file to destination file.
- * @param source stream copied from
+ *
+ * @param source stream copied from
* @param destination stream copied to
* @throws IOException thrown when failing to read source or opening destination file
*/
@@ -214,7 +218,8 @@ public class FileUtils {
/**
* Copy content from source file to destination file.
- * @param source file descriptor copied from
+ *
+ * @param source file descriptor copied from
* @param destination file path copied to
* @throws IOException thrown when failing to read source or opening destination file
*/
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
index e7326246c..b9750e350 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
@@ -8,7 +8,6 @@ import android.location.LocationListener;
import android.location.LocationManager;
import android.media.ExifInterface;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
@@ -16,7 +15,6 @@ import android.support.annotation.RequiresApi;
import java.io.FileDescriptor;
import java.io.IOException;
-import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/**
@@ -26,6 +24,8 @@ import timber.log.Timber;
*/
public class GPSExtractor {
+ private final Context context;
+ private SharedPreferences prefs;
private ExifInterface exif;
private double decLatitude;
private double decLongitude;
@@ -38,9 +38,12 @@ public class GPSExtractor {
/**
* Construct from the file descriptor of the image (only for API 24 or newer).
* @param fileDescriptor the file descriptor of the image
+ * @param context the context
*/
@RequiresApi(24)
- public GPSExtractor(@NonNull FileDescriptor fileDescriptor) {
+ public GPSExtractor(@NonNull FileDescriptor fileDescriptor, Context context, SharedPreferences prefs) {
+ this.context = context;
+ this.prefs = prefs;
try {
exif = new ExifInterface(fileDescriptor);
} catch (IOException | IllegalArgumentException e) {
@@ -51,13 +54,16 @@ public class GPSExtractor {
/**
* Construct from the file path of the image.
* @param path file path of the image
+ * @param context the context
*/
- public GPSExtractor(@NonNull String path) {
+ public GPSExtractor(@NonNull String path, Context context, SharedPreferences prefs) {
+ this.prefs = prefs;
try {
exif = new ExifInterface(path);
} catch (IOException | IllegalArgumentException e) {
Timber.w(e);
}
+ this.context = context;
}
/**
@@ -65,9 +71,7 @@ public class GPSExtractor {
* @return true if enabled, false if disabled
*/
private boolean gpsPreferenceEnabled() {
- SharedPreferences sharedPref
- = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance());
- boolean gpsPref = sharedPref.getBoolean("allowGps", false);
+ boolean gpsPref = prefs.getBoolean("allowGps", false);
Timber.d("Gps pref set to: %b", gpsPref);
return gpsPref;
}
@@ -76,8 +80,7 @@ public class GPSExtractor {
* Registers a LocationManager to listen for current location
*/
protected void registerLocationManager() {
- locationManager = (LocationManager) CommonsApplication.getInstance()
- .getSystemService(Context.LOCATION_SERVICE);
+ locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, true);
myLocationListener = new MyLocationListener();
@@ -110,11 +113,11 @@ public class GPSExtractor {
*/
@Nullable
public String getCoords(boolean useGPS) {
- String latitude = "";
- String longitude = "";
- String latitude_ref = "";
- String longitude_ref = "";
- String decimalCoords = "";
+ String latitude;
+ String longitude;
+ String latitudeRef;
+ String longitudeRef;
+ String decimalCoords;
//If image has no EXIF data and user has enabled GPS setting, get user's location
if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
@@ -147,15 +150,15 @@ public class GPSExtractor {
Timber.d("EXIF data has location info");
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
- latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
+ latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
- longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
+ longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
- if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) {
- Timber.d("Latitude: %s %s", latitude, latitude_ref);
- Timber.d("Longitude: %s %s", longitude, longitude_ref);
+ if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) {
+ Timber.d("Latitude: %s %s", latitude, latitudeRef);
+ Timber.d("Longitude: %s %s", longitude, longitudeRef);
- decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
+ decimalCoords = getDecimalCoords(latitude, latitudeRef, longitude, longitudeRef);
return decimalCoords;
} else {
return null;
@@ -221,7 +224,7 @@ public class GPSExtractor {
return decimalCoords;
}
- private double convertToDegree(String stringDMS){
+ private double convertToDegree(String stringDMS) {
double result;
String[] DMS = stringDMS.split(",", 3);
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
index def318fee..ac0afa979 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleShareActivity.java
@@ -2,10 +2,10 @@ package fr.free.nrw.commons.upload;
import android.Manifest;
import android.app.ProgressDialog;
-import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.net.Uri;
@@ -27,11 +27,14 @@ import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution;
@@ -39,26 +42,36 @@ import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
+import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
-import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
-public class MultipleShareActivity
- extends AuthenticatedActivity
- implements MediaDetailPagerFragment.MediaDetailProvider,
- AdapterView.OnItemClickListener,
- FragmentManager.OnBackStackChangedListener,
- MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
+public class MultipleShareActivity extends AuthenticatedActivity
+ implements MediaDetailPagerFragment.MediaDetailProvider,
+ AdapterView.OnItemClickListener,
+ FragmentManager.OnBackStackChangedListener,
+ MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
OnCategoriesSaveHandler {
- private CommonsApplication app;
+
+ @Inject
+ MediaWikiApi mwApi;
+ @Inject
+ SessionManager sessionManager;
+ @Inject
+ UploadController uploadController;
+ @Inject
+ ModifierSequenceDao modifierSequenceDao;
+ @Inject
+ @Named("default_preferences")
+ SharedPreferences prefs;
+
private ArrayList photosList = null;
private MultipleUploadListFragment uploadsList;
private MediaDetailPagerFragment mediaDetails;
private CategorizationFragment categorizationFragment;
- private UploadController uploadController;
-
private boolean locationPermitted = false;
@Override
@@ -129,7 +142,7 @@ public class MultipleShareActivity
dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size()));
dialog.show();
- for(int i = 0; i < photosList.size(); i++) {
+ for (int i = 0; i < photosList.size(); i++) {
Contribution up = photosList.get(i);
final int uploadCount = i + 1; // Goddamn Java
@@ -137,11 +150,7 @@ public class MultipleShareActivity
dialog.setProgress(uploadCount);
if (uploadCount == photosList.size()) {
dialog.dismiss();
- Toast startingToast = Toast.makeText(
- CommonsApplication.getInstance(),
- R.string.uploading_started,
- Toast.LENGTH_LONG
- );
+ Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
}
});
@@ -168,33 +177,24 @@ public class MultipleShareActivity
@Override
public void onCategoriesSave(List categories) {
if (categories.size() > 0) {
- ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY);
- for(Contribution contribution: photosList) {
+ for (Contribution contribution : photosList) {
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
- categoriesSequence.setContentProviderClient(client);
- categoriesSequence.save();
+ modifierSequenceDao.save(categoriesSequence);
}
}
// FIXME: Make sure that the content provider is up
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
- ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("categories-count", categories.size())
- .param("files-count", photosList.size())
- .param("source", Contribution.SOURCE_EXTERNAL)
- .param("result", "queued")
- .log();
+ ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default!
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
+ switch (item.getItemId()) {
case android.R.id.home:
if (mediaDetails.isVisible()) {
getSupportFragmentManager().popBackStack();
@@ -207,10 +207,8 @@ public class MultipleShareActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- uploadController = new UploadController();
setContentView(R.layout.activity_multiple_uploads);
- app = CommonsApplication.getInstance();
ButterKnife.bind(this);
initDrawer();
@@ -238,7 +236,7 @@ public class MultipleShareActivity
}
private void showDetail(int i) {
- if (mediaDetails == null ||!mediaDetails.isVisible()) {
+ if (mediaDetails == null || !mediaDetails.isVisible()) {
mediaDetails = new MediaDetailPagerFragment(true);
getSupportFragmentManager()
.beginTransaction()
@@ -258,14 +256,14 @@ public class MultipleShareActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
- app.getMWApi().setAuthCookie(authCookie);
+ mwApi.setAuthCookie(authCookie);
Intent intent = getIntent();
- if (intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
+ if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
if (photosList == null) {
photosList = new ArrayList<>();
ArrayList urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
- for(int i=0; i < urisList.size(); i++) {
+ for (int i = 0; i < urisList.size(); i++) {
Contribution up = new Contribution();
Uri uri = urisList.get(i);
up.setLocalUri(uri);
@@ -284,7 +282,7 @@ public class MultipleShareActivity
uploadsList = (MultipleUploadListFragment) getSupportFragmentManager().findFragmentByTag("uploadsList");
if (uploadsList == null) {
- uploadsList = new MultipleUploadListFragment();
+ uploadsList = new MultipleUploadListFragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.uploadsFragmentContainer, uploadsList, "uploadsList")
@@ -302,34 +300,9 @@ public class MultipleShareActivity
finish();
}
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- if (categorizationFragment != null && categorizationFragment.isVisible()) {
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("categories-count", categorizationFragment.getCurrentSelectedCount())
- .param("files-count", photosList.size())
- .param("source", Contribution.SOURCE_EXTERNAL)
- .param("result", "cancelled")
- .log();
- } else {
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
- .param("multiple", true)
- .param("result", "cancelled")
- .log();
- }
- }
-
@Override
public void onBackStackChanged() {
- if (mediaDetails != null && mediaDetails.isVisible()) {
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- } else {
- getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- }
+ getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible());
}
/**
@@ -353,12 +326,12 @@ public class MultipleShareActivity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(imageUri,"r");
if (fd != null) {
- gpsExtractor = new GPSExtractor(fd.getFileDescriptor());
+ gpsExtractor = new GPSExtractor(fd.getFileDescriptor(),this,prefs);
}
} else {
String filePath = FileUtils.getPath(this,imageUri);
if (filePath != null) {
- gpsExtractor = new GPSExtractor(filePath);
+ gpsExtractor = new GPSExtractor(filePath,this,prefs);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java
index 28f88e3d6..0b6e527e5 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java
@@ -28,6 +28,7 @@ import android.widget.TextView;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
+import dagger.android.support.AndroidSupportInjection;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
@@ -56,6 +57,12 @@ public class MultipleUploadListFragment extends Fragment {
private RelativeLayout overlay;
}
+ @Override
+ public void onAttach(Context context) {
+ AndroidSupportInjection.inject(this);
+ super.onAttach(context);
+ }
+
private class PhotoDisplayAdapter extends BaseAdapter {
@Override
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java b/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
index f515f2d0c..a530e79e6 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/MwVolleyApi.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload;
+import android.content.Context;
import android.net.Uri;
import com.android.volley.Cache;
@@ -20,7 +21,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/**
@@ -33,12 +33,14 @@ public class MwVolleyApi {
private static RequestQueue REQUEST_QUEUE;
private static final Gson GSON = new GsonBuilder().create();
- protected static Set categorySet;
+ private static Set categorySet;
private static List categoryList;
private static final String MWURL = "https://commons.wikimedia.org/";
+ private final Context context;
- public MwVolleyApi() {
+ public MwVolleyApi(Context context) {
+ this.context = context;
categorySet = new HashSet<>();
}
@@ -67,7 +69,7 @@ public class MwVolleyApi {
* @param coords Coordinates to build query with
* @return URL for API query
*/
- private String buildUrl (String coords){
+ private String buildUrl(String coords) {
Uri.Builder builder = Uri.parse(MWURL).buildUpon();
@@ -93,7 +95,7 @@ public class MwVolleyApi {
private synchronized RequestQueue getQueue() {
if (REQUEST_QUEUE == null) {
- REQUEST_QUEUE = Volley.newRequestQueue(CommonsApplication.getInstance());
+ REQUEST_QUEUE = Volley.newRequestQueue(context);
}
return REQUEST_QUEUE;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
index fa5f0d18b..a0b9102b6 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
@@ -10,13 +10,13 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.Snackbar;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.view.MenuItem;
import android.view.View;
@@ -30,22 +30,29 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.List;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.ButterKnife;
-import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
-import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
+import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
-import fr.free.nrw.commons.mwapi.EventLog;
+import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED;
@@ -55,10 +62,10 @@ import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
* Activity for the title/desc screen after image is selected. Also starts processing image
* GPS coordinates or user location (if enabled in Settings) for category suggestions.
*/
-public class ShareActivity
- extends AuthenticatedActivity
+public class ShareActivity
+ extends AuthenticatedActivity
implements SingleUploadFragment.OnUploadActionInitiated,
- OnCategoriesSaveHandler {
+ OnCategoriesSaveHandler,SimilarImageDialogFragment.onResponse {
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
@@ -66,7 +73,19 @@ public class ShareActivity
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
private CategorizationFragment categorizationFragment;
- private CommonsApplication app;
+ @Inject
+ MediaWikiApi mwApi;
+ @Inject
+ CacheController cacheController;
+ @Inject
+ SessionManager sessionManager;
+ @Inject
+ UploadController uploadController;
+ @Inject
+ ModifierSequenceDao modifierSequenceDao;
+ @Inject
+ @Named("default_preferences")
+ SharedPreferences prefs;
private String source;
private String mimeType;
@@ -75,11 +94,10 @@ public class ShareActivity
private Contribution contribution;
private SimpleDraweeView backgroundImageView;
- private UploadController uploadController;
-
private boolean cacheFound;
private GPSExtractor imageObj;
+ private GPSExtractor tempImageObj;
private String decimalCoords;
private boolean useNewPermissions = false;
@@ -90,7 +108,7 @@ public class ShareActivity
private String description;
private Snackbar snackbar;
private boolean duplicateCheckPassed = false;
-
+ private boolean haveCheckedForOtherImages = false;
/**
* Called when user taps the submit button.
*/
@@ -117,7 +135,7 @@ public class ShareActivity
@RequiresApi(16)
private boolean needsToRequestStoragePermission() {
// We need to ask storage permission when
- // the file is not owned by this app, (e.g. shared from the Gallery)
+ // the file is not owned by this application, (e.g. shared from the Gallery)
// and permission is not obtained.
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
@@ -127,16 +145,12 @@ public class ShareActivity
private void uploadBegins() {
getFileMetadata(locationPermitted);
- Toast startingToast = Toast.makeText(
- CommonsApplication.getInstance(),
- R.string.uploading_started,
- Toast.LENGTH_LONG
- );
+ Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
if (!cacheFound) {
//Has to be called after apiCall.request()
- app.getCacheData().cacheCategory();
+ cacheController.cacheCategory();
Timber.d("Cache the categories found");
}
@@ -162,21 +176,13 @@ public class ShareActivity
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
- categoriesSequence.setContentProviderClient(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
- categoriesSequence.save();
+ modifierSequenceDao.save(categoriesSequence);
}
// FIXME: Make sure that the content provider is up
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
- ContentResolver.setSyncAutomatically(app.getCurrentAccount(), ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
+ ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, true); // Enable sync by default!
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("categories-count", categories.size())
- .param("files-count", 1)
- .param("source", contribution.getSource())
- .param("result", "queued")
- .log();
finish();
}
@@ -188,31 +194,9 @@ public class ShareActivity
}
}
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- if (categorizationFragment != null && categorizationFragment.isVisible()) {
- EventLog.schema(CommonsApplication.EVENT_CATEGORIZATION_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("categories-count", categorizationFragment.getCurrentSelectedCount())
- .param("files-count", 1)
- .param("source", contribution.getSource())
- .param("result", "cancelled")
- .log();
- } else {
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE))
- .param("multiple", true)
- .param("result", "cancelled")
- .log();
- }
- }
-
@Override
protected void onAuthCookieAcquired(String authCookie) {
- app.getMWApi().setAuthCookie(authCookie);
-
+ mwApi.setAuthCookie(authCookie);
}
@Override
@@ -225,11 +209,10 @@ public class ShareActivity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- uploadController = new UploadController();
+
setContentView(R.layout.activity_share);
ButterKnife.bind(this);
initBack();
- app = CommonsApplication.getInstance();
backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage);
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
.newInstance(getResources())
@@ -242,7 +225,7 @@ public class ShareActivity
//Receive intent from ContributionController.java when user selects picture to upload
Intent intent = getIntent();
- if (intent.getAction().equals(Intent.ACTION_SEND)) {
+ if (Intent.ACTION_SEND.equals(intent.getAction())) {
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
@@ -379,7 +362,7 @@ public class ShareActivity
try {
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
Timber.d("Input stream created from %s", mediaUri.toString());
- String fileSHA1 = Utils.getSHA1(inputStream);
+ String fileSHA1 = getSHA1(inputStream);
Timber.d("File SHA1 is: %s", fileSHA1);
ExistingFileAsync fileAsyncTask =
@@ -387,7 +370,7 @@ public class ShareActivity
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
duplicateCheckPassed = (result == DUPLICATE_PROCEED
|| result == NO_DUPLICATE);
- });
+ }, mwApi);
fileAsyncTask.execute();
} catch (IOException e) {
Timber.d(e, "IO Exception: ");
@@ -424,9 +407,7 @@ public class ShareActivity
ParcelFileDescriptor descriptor
= getContentResolver().openFileDescriptor(mediaUri, "r");
if (descriptor != null) {
- SharedPreferences sharedPref = PreferenceManager
- .getDefaultSharedPreferences(CommonsApplication.getInstance());
- boolean useExtStorage = sharedPref.getBoolean("useExternalStorage", true);
+ boolean useExtStorage = prefs.getBoolean("useExternalStorage", true);
if (useExtStorage) {
copyPath = Environment.getExternalStorageDirectory().toString()
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
@@ -467,12 +448,12 @@ public class ShareActivity
= getContentResolver().openFileDescriptor(mediaUri, "r");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (descriptor != null) {
- imageObj = new GPSExtractor(descriptor.getFileDescriptor());
+ imageObj = new GPSExtractor(descriptor.getFileDescriptor(), this, prefs);
}
} else {
String filePath = getPathOfMediaOrCopy();
if (filePath != null) {
- imageObj = new GPSExtractor(filePath);
+ imageObj = new GPSExtractor(filePath, this, prefs);
}
}
}
@@ -480,13 +461,93 @@ public class ShareActivity
if (imageObj != null) {
// Gets image coords from exif data or user location
decimalCoords = imageObj.getCoords(gpsEnabled);
- useImageCoords();
+ if(decimalCoords==null || !imageObj.imageCoordsExists){
+// Check if the location is from GPS or EXIF
+// Find other photos taken around the same time which has gps coordinates
+ Timber.d("EXIF:false");
+ Timber.d("EXIF call"+(imageObj==tempImageObj));
+ if(!haveCheckedForOtherImages)
+ findOtherImages(gpsEnabled);// Do not do repeat the process
+ }
+ else {
+// As the selected image has GPS data in EXIF go ahead with the same.
+ useImageCoords();
+ }
}
} catch (FileNotFoundException e) {
Timber.w("File not found: " + mediaUri, e);
}
}
+ private void findOtherImages(boolean gpsEnabled) {
+ Timber.d("filePath"+getPathOfMediaOrCopy());
+ String filePath = getPathOfMediaOrCopy();
+ long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
+ File folder = new File(filePath.substring(0,filePath.lastIndexOf('/')));
+ File[] files = folder.listFiles();
+ Timber.d("folderTime Number:"+files.length);
+
+ for(File file : files){
+ if(file.lastModified()-timeOfCreation<=(120*1000) && file.lastModified()-timeOfCreation>=-(120*1000)){
+ //Make sure the photos were taken within 20seconds
+ Timber.d("fild date:"+file.lastModified()+ " time of creation"+timeOfCreation);
+ tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
+ ParcelFileDescriptor descriptor
+ = null;
+ try {
+ descriptor = getContentResolver().openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (descriptor != null) {
+ tempImageObj = new GPSExtractor(descriptor.getFileDescriptor(),this, prefs);
+ }
+ } else {
+ if (filePath != null) {
+ tempImageObj = new GPSExtractor(file.getAbsolutePath(), this, prefs);
+ }
+ }
+
+ if(tempImageObj!=null){
+ Timber.d("not null fild EXIF"+tempImageObj.imageCoordsExists +" coords"+tempImageObj.getCoords(gpsEnabled));
+ if(tempImageObj.getCoords(gpsEnabled)!=null && tempImageObj.imageCoordsExists){
+// Current image has gps coordinates and it's not current gps locaiton
+ Timber.d("This fild has image coords:"+ file.getAbsolutePath());
+// Create a dialog fragment for the suggestion
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
+ Bundle args = new Bundle();
+ args.putString("originalImagePath",filePath);
+ args.putString("possibleImagePath",file.getAbsolutePath());
+ newFragment.setArguments(args);
+ newFragment.show(fragmentManager, "dialog");
+ break;
+ }
+
+ }
+
+ }
+ }
+ haveCheckedForOtherImages = true; //Finished checking for other images
+ return;
+ }
+
+ @Override
+ public void onPostiveResponse() {
+ imageObj = tempImageObj;
+ decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data
+ Timber.d("EXIF from tempImageObj");
+ useImageCoords();
+ }
+
+ @Override
+ public void onNegativeResponse() {
+ Timber.d("EXIF from imageObj");
+ useImageCoords();
+
+ }
+
/**
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
@@ -494,17 +555,18 @@ public class ShareActivity
public void useImageCoords() {
if (decimalCoords != null) {
Timber.d("Decimal coords of image: %s", decimalCoords);
+ Timber.d("is EXIF data present:"+imageObj.imageCoordsExists+" from findOther image:"+(imageObj==tempImageObj));
// Only set cache for this point if image has coords
if (imageObj.imageCoordsExists) {
double decLongitude = imageObj.getDecLongitude();
double decLatitude = imageObj.getDecLatitude();
- app.getCacheData().setQtPoint(decLongitude, decLatitude);
+ cacheController.setQtPoint(decLongitude, decLatitude);
}
- MwVolleyApi apiCall = new MwVolleyApi();
+ MwVolleyApi apiCall = new MwVolleyApi(this);
- List displayCatList = app.getCacheData().findCategory();
+ List displayCatList = cacheController.findCategory();
boolean catListEmpty = displayCatList.isEmpty();
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
@@ -517,7 +579,10 @@ public class ShareActivity
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
MwVolleyApi.setGpsCat(displayCatList);
}
+ }else{
+ Timber.d("EXIF: no coords");
}
+
}
@Override
@@ -550,4 +615,41 @@ public class ShareActivity
}
return super.onOptionsItemSelected(item);
}
+
+ // Get SHA1 of file from input stream
+ private String getSHA1(InputStream is) {
+
+ MessageDigest digest;
+ try {
+ digest = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ Timber.e(e, "Exception while getting Digest");
+ return "";
+ }
+
+ byte[] buffer = new byte[8192];
+ int read;
+ try {
+ while ((read = is.read(buffer)) > 0) {
+ digest.update(buffer, 0, read);
+ }
+ byte[] md5sum = digest.digest();
+ BigInteger bigInt = new BigInteger(1, md5sum);
+ String output = bigInt.toString(16);
+ // Fill to 40 chars
+ output = String.format("%40s", output).replace(' ', '0');
+ Timber.i("File SHA1: %s", output);
+
+ return output;
+ } catch (IOException e) {
+ Timber.e(e, "IO Exception");
+ return "";
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ Timber.e(e, "Exception on closing MD5 input stream");
+ }
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java
new file mode 100644
index 000000000..a8f336927
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java
@@ -0,0 +1,112 @@
+package fr.free.nrw.commons.upload;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.graphics.drawable.VectorDrawableCompat;
+import android.support.v4.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.Button;
+
+import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
+import com.facebook.drawee.view.SimpleDraweeView;
+import com.facebook.imagepipeline.listener.RequestListener;
+import com.facebook.imagepipeline.listener.RequestLoggingListener;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+import fr.free.nrw.commons.R;
+
+/**
+ * Created by harisanker on 14/2/18.
+ */
+
+public class SimilarImageDialogFragment extends DialogFragment {
+ SimpleDraweeView originalImage;
+ SimpleDraweeView possibleImage;
+ Button positiveButton;
+ Button negativeButton;
+ onResponse mOnResponse;//Implemented interface from shareActivity
+ Boolean gotResponse = false;
+ public SimilarImageDialogFragment() {
+ }
+ public interface onResponse{
+ public void onPostiveResponse();
+ public void onNegativeResponse();
+ }
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
+ Set requestListeners = new HashSet<>();
+ requestListeners.add(new RequestLoggingListener());
+
+ originalImage =(SimpleDraweeView) view.findViewById(R.id.orginalImage);
+ possibleImage =(SimpleDraweeView) view.findViewById(R.id.possibleImage);
+ positiveButton = (Button) view.findViewById(R.id.postive_button);
+ negativeButton = (Button) view.findViewById(R.id.negative_button);
+
+ originalImage.setHierarchy(GenericDraweeHierarchyBuilder
+ .newInstance(getResources())
+ .setPlaceholderImage(VectorDrawableCompat.create(getResources(),
+ R.drawable.ic_image_black_24dp,getContext().getTheme()))
+ .setFailureImage(VectorDrawableCompat.create(getResources(),
+ R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
+ .build());
+ possibleImage.setHierarchy(GenericDraweeHierarchyBuilder
+ .newInstance(getResources())
+ .setPlaceholderImage(VectorDrawableCompat.create(getResources(),
+ R.drawable.ic_image_black_24dp,getContext().getTheme()))
+ .setFailureImage(VectorDrawableCompat.create(getResources(),
+ R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
+ .build());
+
+ originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath"))));
+ possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath"))));
+
+ negativeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mOnResponse.onNegativeResponse();
+ gotResponse = true;
+ dismiss();
+ }
+ });
+ positiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mOnResponse.onPostiveResponse();
+ gotResponse = true;
+ dismiss();
+ }
+ });
+ return view;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mOnResponse = (onResponse) getActivity();//Interface Implementation
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ return dialog;
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+// I user dismisses dialog by pressing outside the dialog.
+ if(!gotResponse)
+ mOnResponse.onNegativeResponse();
+ super.onDismiss(dialog);
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java
index 579352991..099790495 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java
@@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload;
+import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -7,10 +8,11 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.v4.app.Fragment;
+import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -28,6 +30,9 @@ import android.widget.TextView;
import java.util.ArrayList;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@@ -35,13 +40,14 @@ import butterknife.OnItemSelected;
import butterknife.OnTouch;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
-public class SingleUploadFragment extends Fragment {
+public class SingleUploadFragment extends CommonsDaggerSupportFragment {
@BindView(R.id.titleEdit) EditText titleEdit;
@BindView(R.id.descEdit) EditText descEdit;
@@ -49,7 +55,8 @@ public class SingleUploadFragment extends Fragment {
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
- private SharedPreferences prefs;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+
private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler;
private TitleTextWatcher textWatcher = new TitleTextWatcher();
@@ -72,11 +79,10 @@ public class SingleUploadFragment extends Fragment {
String desc = descEdit.getText().toString();
//Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these
- SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
- SharedPreferences.Editor editor = titleDesc.edit();
- editor.putString("Title", title);
- editor.putString("Desc", desc);
- editor.apply();
+ prefs.edit()
+ .putString("Title", title)
+ .putString("Desc", desc)
+ .apply();
uploadActionInitiatedHandler.uploadActionInitiated(title, desc);
return true;
@@ -91,7 +97,6 @@ public class SingleUploadFragment extends Fragment {
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView);
-
ArrayList licenseItems = new ArrayList<>();
licenseItems.add(getString(R.string.license_name_cc0));
licenseItems.add(getString(R.string.license_name_cc_by));
@@ -99,7 +104,6 @@ public class SingleUploadFragment extends Fragment {
licenseItems.add(getString(R.string.license_name_cc_by_four));
licenseItems.add(getString(R.string.license_name_cc_by_sa_four));
- prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
// check if this is the first time we have uploaded
@@ -134,11 +138,29 @@ public class SingleUploadFragment extends Fragment {
titleEdit.addTextChangedListener(textWatcher);
+ titleEdit.setOnFocusChangeListener((v, hasFocus) -> {
+ if (!hasFocus) {
+ hideKeyboard(v);
+ }
+ });
+
+ descEdit.setOnFocusChangeListener((v, hasFocus) -> {
+ if(!hasFocus){
+ hideKeyboard(v);
+ }
+ });
+
setLicenseSummary(license);
return rootView;
}
+ public void hideKeyboard(View view) {
+ Log.i("hide", "hideKeyboard: ");
+ InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
+ inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+
@Override
public void onDestroyView() {
titleEdit.removeTextChangedListener(textWatcher);
@@ -172,9 +194,9 @@ public class SingleUploadFragment extends Fragment {
}
setLicenseSummary(license);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putString(Prefs.DEFAULT_LICENSE, license);
- editor.commit();
+ prefs.edit()
+ .putString(Prefs.DEFAULT_LICENSE, license)
+ .commit();
}
@OnTouch(R.id.share_license_summary)
@@ -182,7 +204,7 @@ public class SingleUploadFragment extends Fragment {
if (motionEvent.getActionMasked() == ACTION_DOWN) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(Utils.licenseUrlFor(license)));
+ intent.setData(Uri.parse(licenseUrlFor(license)));
startActivity(intent);
return true;
} else {
@@ -193,9 +215,8 @@ public class SingleUploadFragment extends Fragment {
@OnClick(R.id.titleDescButton)
void setTitleDescButton() {
//Retrieve last title and desc entered
- SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
- String title = titleDesc.getString("Title", "");
- String desc = titleDesc.getString("Desc", "");
+ String title = prefs.getString("Title", "");
+ String desc = prefs.getString("Desc", "");
Timber.d("Title: %s, Desc: %s", title, desc);
titleEdit.setText(title);
@@ -263,6 +284,23 @@ public class SingleUploadFragment extends Fragment {
}
}
+ @NonNull
+ private String licenseUrlFor(String license) {
+ switch (license) {
+ case Prefs.Licenses.CC_BY_3:
+ return "https://creativecommons.org/licenses/by/3.0/";
+ case Prefs.Licenses.CC_BY_4:
+ return "https://creativecommons.org/licenses/by/4.0/";
+ case Prefs.Licenses.CC_BY_SA_3:
+ return "https://creativecommons.org/licenses/by-sa/3.0/";
+ case Prefs.Licenses.CC_BY_SA_4:
+ return "https://creativecommons.org/licenses/by-sa/4.0/";
+ case Prefs.Licenses.CC0:
+ return "https://creativecommons.org/publicdomain/zero/1.0/";
+ }
+ throw new RuntimeException("Unrecognized license value: " + license);
+ }
+
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
index 4a41fc4d1..32554da0f 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
@@ -1,74 +1,103 @@
package fr.free.nrw.commons.upload;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.IBinder;
-import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.TextUtils;
+import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Date;
import java.util.concurrent.Executors;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService;
-import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class UploadController {
private UploadService uploadService;
- private final CommonsApplication app;
+ private SessionManager sessionManager;
+ private Context context;
+ private SharedPreferences prefs;
public interface ContributionUploadProgress {
void onUploadStarted(Contribution contribution);
}
- public UploadController() {
- app = CommonsApplication.getInstance();
+ /**
+ * Constructs a new UploadController.
+ */
+ public UploadController(SessionManager sessionManager, Context context, SharedPreferences sharedPreferences) {
+ this.sessionManager = sessionManager;
+ this.context = context;
+ this.prefs = sharedPreferences;
}
private boolean isUploadServiceConnected;
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
- uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
+ uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
isUploadServiceConnected = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
- throw 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!"));
}
};
+ /**
+ * Prepares the upload service.
+ */
public void prepareService() {
- Intent uploadServiceIntent = new Intent(app, UploadService.class);
+ Intent uploadServiceIntent = new Intent(context, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
- app.startService(uploadServiceIntent);
- app.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
+ context.startService(uploadServiceIntent);
+ context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
}
+ /**
+ * Disconnects the upload service.
+ */
public void cleanup() {
- if(isUploadServiceConnected) {
- app.unbindService(uploadServiceConnection);
+ if (isUploadServiceConnected) {
+ context.unbindService(uploadServiceConnection);
}
}
+ /**
+ * Starts a new upload task.
+ *
+ * @param title the title of the contribution
+ * @param mediaUri the media URI of the contribution
+ * @param description the description of the contribution
+ * @param mimeType the MIME type of the contribution
+ * @param source the source of the contribution
+ * @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
+ * @param onComplete the progress tracker
+ */
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) {
Contribution contribution;
//TODO: Modify this to include coords
- contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
+ contribution = new Contribution(mediaUri, null, title, description, -1,
+ null, null, sessionManager.getCurrentAccount().name,
+ CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
@@ -77,16 +106,19 @@ public class UploadController {
startUpload(contribution, onComplete);
}
+ /**
+ * Starts a new upload task.
+ *
+ * @param contribution the contribution object
+ * @param onComplete the progress tracker
+ */
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
-
//Set creator, desc, and license
- if(TextUtils.isEmpty(contribution.getCreator())) {
- contribution.setCreator(app.getCurrentAccount().name);
+ if (TextUtils.isEmpty(contribution.getCreator())) {
+ contribution.setCreator(sessionManager.getCurrentAccount().name);
}
- if(contribution.getDescription() == null) {
+ if (contribution.getDescription() == null) {
contribution.setDescription("");
}
@@ -102,17 +134,20 @@ public class UploadController {
@Override
protected Contribution doInBackground(Void... voids /* stare into you */) {
long length;
+ ContentResolver contentResolver = context.getContentResolver();
try {
- if(contribution.getDataLength() <= 0) {
- length = app.getContentResolver()
- .openAssetFileDescriptor(contribution.getLocalUri(), "r")
- .getLength();
- if(length == -1) {
- // Let us find out the long way!
- length = Utils.countBytes(app.getContentResolver()
- .openInputStream(contribution.getLocalUri()));
+ if (contribution.getDataLength() <= 0) {
+ AssetFileDescriptor assetFileDescriptor = contentResolver
+ .openAssetFileDescriptor(contribution.getLocalUri(), "r");
+ if (assetFileDescriptor != null) {
+ length = assetFileDescriptor.getLength();
+ if (length == -1) {
+ // Let us find out the long way!
+ length = countBytes(contentResolver
+ .openInputStream(contribution.getLocalUri()));
+ }
+ contribution.setDataLength(length);
}
- contribution.setDataLength(length);
}
} catch (IOException e) {
Timber.e(e, "IO Exception: ");
@@ -122,11 +157,11 @@ public class UploadController {
Timber.e(e, "Security Exception: ");
}
- String mimeType = (String)contribution.getTag("mimeType");
+ String mimeType = (String) contribution.getTag("mimeType");
Boolean imagePrefix = false;
if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
- mimeType = app.getContentResolver().getType(contribution.getLocalUri());
+ mimeType = contentResolver.getType(contribution.getLocalUri());
}
if (mimeType != null) {
@@ -137,7 +172,7 @@ public class UploadController {
if (imagePrefix && contribution.getDateCreated() == null) {
Timber.d("local uri " + contribution.getLocalUri());
- Cursor cursor = app.getContentResolver().query(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) {
cursor.moveToFirst();
@@ -165,4 +200,21 @@ public class UploadController {
}
}.executeOnExecutor(Executors.newFixedThreadPool(1)); // TODO remove this by using a sensible thread handling strategy
}
+
+
+ /**
+ * Counts the number of bytes in {@code stream}.
+ *
+ * @param stream the stream
+ * @return the number of bytes in {@code stream}
+ * @throws IOException if an I/O error occurs
+ */
+ private long countBytes(InputStream stream) throws IOException {
+ long count = 0;
+ BufferedInputStream bis = new BufferedInputStream(stream);
+ while (bis.read() != -1) {
+ count++;
+ }
+ return count;
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
index f6e4ee6a2..94c005256 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadService.java
@@ -4,10 +4,10 @@ import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
@@ -22,15 +22,18 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import fr.free.nrw.commons.CommonsApplication;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
+import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
+import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
-import fr.free.nrw.commons.mwapi.EventLog;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.mwapi.UploadResult;
import timber.log.Timber;
@@ -45,12 +48,13 @@ public class UploadService extends HandlerService {
public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source";
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
+ @Inject MediaWikiApi mwApi;
+ @Inject SessionManager sessionManager;
+ @Inject @Named("default_preferences") SharedPreferences prefs;
+ @Inject ContributionDao contributionDao;
+
private NotificationManager notificationManager;
- private ContentProviderClient contributionsProviderClient;
- private CommonsApplication app;
-
private NotificationCompat.Builder curProgressNotification;
-
private int toUpload;
// The file names of unfinished uploads, used to prevent overwriting
@@ -101,7 +105,7 @@ public class UploadService extends HandlerService {
startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
contribution.setTransferred(transferred);
- contribution.save();
+ contributionDao.save(contribution);
}
}
@@ -109,7 +113,6 @@ public class UploadService extends HandlerService {
@Override
public void onDestroy() {
super.onDestroy();
- contributionsProviderClient.release();
Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
}
@@ -118,8 +121,6 @@ public class UploadService extends HandlerService {
super.onCreate();
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- app = CommonsApplication.getInstance();
- contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
}
@Override
@@ -141,9 +142,7 @@ public class UploadService extends HandlerService {
contribution.setState(Contribution.STATE_QUEUED);
contribution.setTransferred(0);
- contribution.setContentProviderClient(contributionsProviderClient);
-
- contribution.save();
+ contributionDao.save(contribution);
toUpload++;
if (curProgressNotification != null && toUpload != 1) {
curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
@@ -162,13 +161,13 @@ public class UploadService extends HandlerService {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent.getAction().equals(ACTION_START_SERVICE) && freshStart) {
+ if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
ContentValues failedValues = new ContentValues();
- failedValues.put(Contribution.Table.COLUMN_STATE, Contribution.STATE_FAILED);
+ failedValues.put(ContributionDao.Table.COLUMN_STATE, Contribution.STATE_FAILED);
int updated = getContentResolver().update(ContributionsContentProvider.BASE_URI,
failedValues,
- Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?",
+ ContributionDao.Table.COLUMN_STATE + " = ? OR " + ContributionDao.Table.COLUMN_STATE + " = ?",
new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) }
);
Timber.d("Set %d uploads to failed", updated);
@@ -180,9 +179,7 @@ public class UploadService extends HandlerService {
@SuppressLint("StringFormatInvalid")
private void uploadContribution(Contribution contribution) {
- MediaWikiApi api = app.getMWApi();
-
- InputStream file = null;
+ InputStream file;
String notificationTag = contribution.getLocalUri().toString();
@@ -193,6 +190,14 @@ public class UploadService extends HandlerService {
Timber.d("File not found");
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
fileNotFound.show();
+ return;
+ }
+
+ //As the file is null there's no point in continuing the upload process
+ //mwapi.upload accepts a NonNull input stream
+ if(file == null) {
+ Timber.d("File not found");
+ return;
}
Timber.d("Before execution!");
@@ -220,9 +225,9 @@ public class UploadService extends HandlerService {
filename = findUniqueFilename(filename);
unfinishedUploads.add(filename);
}
- if (!api.validateLogin()) {
+ if (!mwApi.validateLogin()) {
// Need to revalidate!
- if (app.revalidateAuthToken()) {
+ if (sessionManager.revalidateAuthToken()) {
Timber.d("Successfully revalidated token!");
} else {
Timber.d("Unable to revalidate :(");
@@ -238,7 +243,7 @@ public class UploadService extends HandlerService {
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
contribution
);
- UploadResult uploadResult = api.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
+ UploadResult uploadResult = mwApi.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
Timber.d("Response is %s", uploadResult.toString());
@@ -247,27 +252,12 @@ public class UploadService extends HandlerService {
String resultStatus = uploadResult.getResultStatus();
if (!resultStatus.equals("Success")) {
showFailedNotification(contribution);
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("source", contribution.getSource())
- .param("multiple", contribution.getMultiple())
- .param("result", uploadResult.getErrorCode())
- .param("filename", contribution.getFilename())
- .log();
} else {
contribution.setFilename(uploadResult.getCanonicalFilename());
contribution.setImageUrl(uploadResult.getImageUrl());
contribution.setState(Contribution.STATE_COMPLETED);
contribution.setDateUploaded(uploadResult.getDateUploaded());
- contribution.save();
-
- EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
- .param("username", app.getCurrentAccount().name)
- .param("source", contribution.getSource()) //FIXME
- .param("filename", contribution.getFilename())
- .param("multiple", contribution.getMultiple())
- .param("result", "success")
- .log();
+ contributionDao.save(contribution);
}
} catch (IOException e) {
Timber.d("I have a network fuckup");
@@ -279,7 +269,7 @@ public class UploadService extends HandlerService {
toUpload--;
if (toUpload == 0) {
// Sync modifications right after all uplaods are processed
- ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
+ ContentResolver.requestSync(sessionManager.getCurrentAccount(), ModificationsContentProvider.MODIFICATIONS_AUTHORITY, new Bundle());
stopForeground(true);
}
}
@@ -298,11 +288,10 @@ public class UploadService extends HandlerService {
notificationManager.notify(NOTIFICATION_UPLOAD_FAILED, failureNotification);
contribution.setState(Contribution.STATE_FAILED);
- contribution.save();
+ contributionDao.save(contribution);
}
private String findUniqueFilename(String fileName) throws IOException {
- MediaWikiApi api = app.getMWApi();
String sequenceFileName;
for (int sequenceNumber = 1; true; sequenceNumber++) {
if (sequenceNumber == 1) {
@@ -318,7 +307,7 @@ public class UploadService extends HandlerService {
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
}
}
- if (!api.fileExistsWithName(sequenceFileName)
+ if (!mwApi.fileExistsWithName(sequenceFileName)
&& !unfinishedUploads.contains(sequenceFileName)) {
break;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java
new file mode 100644
index 000000000..6b3bf0377
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/DateUtils.java
@@ -0,0 +1,36 @@
+package fr.free.nrw.commons.utils;
+
+import java.util.Calendar;
+import java.util.Date;
+
+public class DateUtils {
+ public static String getTimeAgo(Date currDate, Date itemDate) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(currDate);
+ int yearNow = c.get(Calendar.YEAR);
+ int monthNow = c.get(Calendar.MONTH);
+ int dayNow = c.get(Calendar.DAY_OF_MONTH);
+ int hourNow = c.get(Calendar.HOUR_OF_DAY);
+ int minuteNow = c.get(Calendar.MINUTE);
+ c.setTime(itemDate);
+ int videoYear = c.get(Calendar.YEAR);
+ int videoMonth = c.get(Calendar.MONTH);
+ int videoDays = c.get(Calendar.DAY_OF_MONTH);
+ int videoHour = c.get(Calendar.HOUR_OF_DAY);
+ int videoMinute = c.get(Calendar.MINUTE);
+
+ if (yearNow != videoYear) {
+ return (String.valueOf(yearNow - videoYear) + "-" + "years");
+ } else if (monthNow != videoMonth) {
+ return (String.valueOf(monthNow - videoMonth) + "-" + "months");
+ } else if (dayNow != videoDays) {
+ return (String.valueOf(dayNow - videoDays) + "-" + "days");
+ } else if (hourNow != videoHour) {
+ return (String.valueOf(hourNow - videoHour) + "-" + "hours");
+ } else if (minuteNow != videoMinute) {
+ return (String.valueOf(minuteNow - videoMinute) + "-" + "minutes");
+ } else {
+ return "0-seconds";
+ }
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java
index 3992324b5..78c1ca155 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java
@@ -11,6 +11,11 @@ import timber.log.Timber;
public class DialogUtil {
+ /**
+ * Dismisses a dialog safely.
+ * @param activity the activity
+ * @param dialog the dialog to be dismissed
+ */
public static void dismissSafely(@Nullable Activity activity, @Nullable DialogFragment dialog) {
boolean isActivityDestroyed = false;
@@ -33,6 +38,11 @@ public class DialogUtil {
}
}
+ /**
+ * Shows a dialog safely.
+ * @param activity the activity
+ * @param dialog the dialog to be shown
+ */
public static void showSafely(Activity activity, Dialog dialog) {
if (activity == null || dialog == null) {
Timber.d("Show called with null activity / dialog. Ignoring.");
@@ -54,6 +64,11 @@ public class DialogUtil {
}
}
+ /**
+ * Shows a dialog safely.
+ * @param activity the activity
+ * @param dialog the dialog to be shown
+ */
public static void showSafely(FragmentActivity activity, DialogFragment dialog) {
boolean isActivityDestroyed = false;
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java
index df67cfea7..57ed86eeb 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ExecutorUtils.java
@@ -15,6 +15,8 @@ public class ExecutorUtils {
}
};
- public static Executor uiExecutor () { return uiExecutor;}
+ public static Executor uiExecutor() {
+ return uiExecutor;
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
index 1c816b9e1..d56a7b608 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
@@ -1,29 +1,37 @@
package fr.free.nrw.commons.utils;
+import android.os.Environment;
+
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
-import fr.free.nrw.commons.CommonsApplication;
+import timber.log.Timber;
public class FileUtils {
/**
* Read and return the content of a resource file as string.
*
- * @param fileName asset file's path (e.g. "/assets/queries/nearby_query.rq")
+ * @param fileName asset file's path (e.g. "/queries/nearby_query.rq")
* @return the content of the file
*/
public static String readFromResource(String fileName) throws IOException {
StringBuilder buffer = new StringBuilder();
BufferedReader reader = null;
try {
- reader = new BufferedReader(
- new InputStreamReader(
- CommonsApplication.class.getResourceAsStream(fileName), "UTF-8"));
+ InputStream inputStream = FileUtils.class.getResourceAsStream(fileName);
+ if (inputStream == null) {
+ throw new FileNotFoundException(fileName);
+ }
+ reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
- buffer.append(line + "\n");
+ buffer.append(line).append("\n");
}
} finally {
if (reader != null) {
@@ -53,5 +61,32 @@ public class FileUtils {
return deletedAll;
}
+ public static File createAndGetAppLogsFile(String logs) {
+ try {
+ File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
+ if (!commonsAppDirectory.exists()) {
+ commonsAppDirectory.mkdir();
+ }
+ File logsFile = new File(commonsAppDirectory,"logs.txt");
+ if (logsFile.exists()) {
+ //old logs file is useless
+ logsFile.delete();
+ }
+
+ logsFile.createNewFile();
+
+ FileOutputStream outputStream = new FileOutputStream(logsFile);
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
+ outputStreamWriter.append(logs);
+ outputStreamWriter.close();
+ outputStream.flush();
+ outputStream.close();
+
+ return logsFile;
+ } catch (IOException ioe) {
+ Timber.e(ioe);
+ return null;
+ }
+ }
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/FragmentUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/FragmentUtils.java
index aa79513e8..d0e432c33 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/FragmentUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/FragmentUtils.java
@@ -23,8 +23,8 @@ public class FragmentUtils {
.commitNow();
return true;
} catch (IllegalStateException e) {
- Timber.e(e, "Could not add & commit fragment. " +
- "Did you mean to call commitAllowingStateLoss?");
+ Timber.e(e, "Could not add & commit fragment. "
+ + "Did you mean to call commitAllowingStateLoss?");
}
return false;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java
index 6fd9f9612..8b687164b 100644
--- a/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java
@@ -37,6 +37,13 @@ public class LengthUtils {
return computeAngleBetween(from, to) * 6371009.0D; // Earth's radius in meter
}
+ /**
+ * Computes angle between two points
+ *
+ * @param from Point A
+ * @param to Point B
+ * @return Angle in radius
+ */
private static double computeAngleBetween(LatLng from, LatLng to) {
return distanceRadians(Math.toRadians(from.getLatitude()),
Math.toRadians(from.getLongitude()),
@@ -44,18 +51,43 @@ public class LengthUtils {
Math.toRadians(to.getLongitude()));
}
+ /**
+ * Computes arc length between 2 points
+ * @param lat1 Latitude of point A
+ * @param lng1 Longitude of point A
+ * @param lat2 Latitude of point B
+ * @param lng2 Longitude of point B
+ * @return Arc length between the points
+ */
private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) {
return arcHav(havDistance(lat1, lat2, lng1 - lng2));
}
+ /**
+ * Computes inverse of haversine
+ * @param x Angle in radian
+ * @return Inverse of haversine
+ */
private static double arcHav(double x) {
return 2.0D * Math.asin(Math.sqrt(x));
}
+ /**
+ * Computes distance between two points that are on same Longitude
+ * @param lat1 Latitude of point A
+ * @param lat2 Latitude of point B
+ * @param longitude Longitude on which they lie
+ * @return Arc length between points
+ */
private static double havDistance(double lat1, double lat2, double longitude) {
return hav(lat1 - lat2) + hav(longitude) * Math.cos(lat1) * Math.cos(lat2);
}
+ /**
+ * Computes haversine
+ * @param x Angle in radians
+ * @return Haversine of x
+ */
private static double hav(double x) {
double sinHalf = Math.sin(x * 0.5D);
return sinHalf * sinHalf;
diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java
new file mode 100644
index 000000000..f2a02398f
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/utils/ViewUtil.java
@@ -0,0 +1,12 @@
+package fr.free.nrw.commons.utils;
+
+import android.content.Context;
+import android.support.annotation.StringRes;
+import android.widget.Toast;
+
+public class ViewUtil {
+
+ public static void showLongToast(final Context context, @StringRes final int stringResId) {
+ ExecutorUtils.uiExecutor().execute(() -> Toast.makeText(context, context.getString(stringResId), Toast.LENGTH_LONG).show());
+ }
+}
diff --git a/app/src/main/res/drawable-hdpi/welcome_copyright.png b/app/src/main/res/drawable-hdpi/welcome_copyright.png
deleted file mode 100644
index 48f90bfde..000000000
Binary files a/app/src/main/res/drawable-hdpi/welcome_copyright.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/welcome_copyright.webp b/app/src/main/res/drawable-hdpi/welcome_copyright.webp
new file mode 100644
index 000000000..34b5327cf
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/welcome_copyright.webp differ
diff --git a/app/src/main/res/drawable-hdpi/welcome_wikipedia.png b/app/src/main/res/drawable-hdpi/welcome_wikipedia.png
deleted file mode 100644
index 6e1a8ae8c..000000000
Binary files a/app/src/main/res/drawable-hdpi/welcome_wikipedia.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/welcome_wikipedia.webp b/app/src/main/res/drawable-hdpi/welcome_wikipedia.webp
new file mode 100644
index 000000000..96ce4e276
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/welcome_wikipedia.webp differ
diff --git a/app/src/main/res/drawable-ldpi/welcome_copyright.png b/app/src/main/res/drawable-ldpi/welcome_copyright.png
deleted file mode 100644
index 6cdf2520e..000000000
Binary files a/app/src/main/res/drawable-ldpi/welcome_copyright.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/welcome_copyright.webp b/app/src/main/res/drawable-ldpi/welcome_copyright.webp
new file mode 100644
index 000000000..c1bd8a976
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/welcome_copyright.webp differ
diff --git a/app/src/main/res/drawable-ldpi/welcome_wikipedia.png b/app/src/main/res/drawable-ldpi/welcome_wikipedia.png
deleted file mode 100644
index e55b56421..000000000
Binary files a/app/src/main/res/drawable-ldpi/welcome_wikipedia.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/welcome_wikipedia.webp b/app/src/main/res/drawable-ldpi/welcome_wikipedia.webp
new file mode 100644
index 000000000..dd8090b75
Binary files /dev/null and b/app/src/main/res/drawable-ldpi/welcome_wikipedia.webp differ
diff --git a/app/src/main/res/drawable-mdpi/welcome_copyright.png b/app/src/main/res/drawable-mdpi/welcome_copyright.png
deleted file mode 100644
index 65462b967..000000000
Binary files a/app/src/main/res/drawable-mdpi/welcome_copyright.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/welcome_copyright.webp b/app/src/main/res/drawable-mdpi/welcome_copyright.webp
new file mode 100644
index 000000000..69e238747
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/welcome_copyright.webp differ
diff --git a/app/src/main/res/drawable-mdpi/welcome_wikipedia.png b/app/src/main/res/drawable-mdpi/welcome_wikipedia.png
deleted file mode 100644
index 8d1e8e322..000000000
Binary files a/app/src/main/res/drawable-mdpi/welcome_wikipedia.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/welcome_wikipedia.webp b/app/src/main/res/drawable-mdpi/welcome_wikipedia.webp
new file mode 100644
index 000000000..5a50f5757
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/welcome_wikipedia.webp differ
diff --git a/app/src/main/res/drawable-xhdpi/welcome_copyright.png b/app/src/main/res/drawable-xhdpi/welcome_copyright.png
deleted file mode 100644
index 973fb4598..000000000
Binary files a/app/src/main/res/drawable-xhdpi/welcome_copyright.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/welcome_copyright.webp b/app/src/main/res/drawable-xhdpi/welcome_copyright.webp
new file mode 100644
index 000000000..2feb197d2
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/welcome_copyright.webp differ
diff --git a/app/src/main/res/drawable-xhdpi/welcome_wikipedia.png b/app/src/main/res/drawable-xhdpi/welcome_wikipedia.png
deleted file mode 100644
index 2f8dfb364..000000000
Binary files a/app/src/main/res/drawable-xhdpi/welcome_wikipedia.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/welcome_wikipedia.webp b/app/src/main/res/drawable-xhdpi/welcome_wikipedia.webp
new file mode 100644
index 000000000..48e83d760
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/welcome_wikipedia.webp differ
diff --git a/app/src/main/res/drawable/blue_rinse_circle.xml b/app/src/main/res/drawable/blue_rinse_circle.xml
new file mode 100644
index 000000000..e63317a5b
--- /dev/null
+++ b/app/src/main/res/drawable/blue_rinse_circle.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/empty_photo.png b/app/src/main/res/drawable/empty_photo.png
deleted file mode 100644
index 2ad718194..000000000
Binary files a/app/src/main/res/drawable/empty_photo.png and /dev/null differ
diff --git a/app/src/main/res/drawable/empty_photo.webp b/app/src/main/res/drawable/empty_photo.webp
new file mode 100644
index 000000000..3f749936a
Binary files /dev/null and b/app/src/main/res/drawable/empty_photo.webp differ
diff --git a/app/src/main/res/drawable/ic_action_facebook.xml b/app/src/main/res/drawable/ic_action_facebook.xml
new file mode 100644
index 000000000..e17144640
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_facebook.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_action_github.xml b/app/src/main/res/drawable/ic_action_github.xml
new file mode 100644
index 000000000..569994ebf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_github.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_action_website.xml b/app/src/main/res/drawable/ic_action_website.xml
new file mode 100644
index 000000000..bad3beb94
--- /dev/null
+++ b/app/src/main/res/drawable/ic_action_website.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_chat_bubble_black_24px.xml b/app/src/main/res/drawable/ic_chat_bubble_black_24px.xml
new file mode 100644
index 000000000..8d40c6d63
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chat_bubble_black_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_edit_black_24dp.xml b/app/src/main/res/drawable/ic_edit_black_24dp.xml
new file mode 100644
index 000000000..2ab2fb753
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_message_black_24dp.xml b/app/src/main/res/drawable/ic_message_black_24dp.xml
new file mode 100644
index 000000000..d2876bfad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_message_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml
new file mode 100644
index 000000000..7009a6763
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/llamas.png b/app/src/main/res/drawable/llamas.png
deleted file mode 100644
index fc3e258d5..000000000
Binary files a/app/src/main/res/drawable/llamas.png and /dev/null differ
diff --git a/app/src/main/res/drawable/llamas.webp b/app/src/main/res/drawable/llamas.webp
new file mode 100644
index 000000000..6e9b013d6
Binary files /dev/null and b/app/src/main/res/drawable/llamas.webp differ
diff --git a/app/src/main/res/drawable/mount_zao.png b/app/src/main/res/drawable/mount_zao.png
deleted file mode 100644
index 86b98661e..000000000
Binary files a/app/src/main/res/drawable/mount_zao.png and /dev/null differ
diff --git a/app/src/main/res/drawable/mount_zao.webp b/app/src/main/res/drawable/mount_zao.webp
new file mode 100644
index 000000000..83fd07b30
Binary files /dev/null and b/app/src/main/res/drawable/mount_zao.webp differ
diff --git a/app/src/main/res/drawable/rainbow_bridge.png b/app/src/main/res/drawable/rainbow_bridge.png
deleted file mode 100644
index a3f78efe6..000000000
Binary files a/app/src/main/res/drawable/rainbow_bridge.png and /dev/null differ
diff --git a/app/src/main/res/drawable/rainbow_bridge.webp b/app/src/main/res/drawable/rainbow_bridge.webp
new file mode 100644
index 000000000..890ebbca5
Binary files /dev/null and b/app/src/main/res/drawable/rainbow_bridge.webp differ
diff --git a/app/src/main/res/drawable/selfie_x.png b/app/src/main/res/drawable/selfie_x.png
deleted file mode 100644
index c049e2f18..000000000
Binary files a/app/src/main/res/drawable/selfie_x.png and /dev/null differ
diff --git a/app/src/main/res/drawable/selfie_x.webp b/app/src/main/res/drawable/selfie_x.webp
new file mode 100644
index 000000000..35c6c0c25
Binary files /dev/null and b/app/src/main/res/drawable/selfie_x.webp differ
diff --git a/app/src/main/res/drawable/sydney_opera_house.png b/app/src/main/res/drawable/sydney_opera_house.png
deleted file mode 100644
index fc1009cd1..000000000
Binary files a/app/src/main/res/drawable/sydney_opera_house.png and /dev/null differ
diff --git a/app/src/main/res/drawable/sydney_opera_house.webp b/app/src/main/res/drawable/sydney_opera_house.webp
new file mode 100644
index 000000000..fd7453004
Binary files /dev/null and b/app/src/main/res/drawable/sydney_opera_house.webp differ
diff --git a/app/src/main/res/drawable/tulip.png b/app/src/main/res/drawable/tulip.png
deleted file mode 100644
index 6467a0701..000000000
Binary files a/app/src/main/res/drawable/tulip.png and /dev/null differ
diff --git a/app/src/main/res/drawable/tulip.webp b/app/src/main/res/drawable/tulip.webp
new file mode 100644
index 000000000..21a8a21df
Binary files /dev/null and b/app/src/main/res/drawable/tulip.webp differ
diff --git a/app/src/main/res/layout-land/activity_login.xml b/app/src/main/res/layout-land/activity_login.xml
new file mode 100644
index 000000000..9ecaf9855
--- /dev/null
+++ b/app/src/main/res/layout-land/activity_login.xml
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-land/welcome_do_upload.xml b/app/src/main/res/layout-land/welcome_do_upload.xml
index e5c7bb3fb..1f02c3183 100644
--- a/app/src/main/res/layout-land/welcome_do_upload.xml
+++ b/app/src/main/res/layout-land/welcome_do_upload.xml
@@ -5,16 +5,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0c609c"
- android:gravity="center"
- >
+ android:gravity="center">
@@ -69,8 +68,8 @@
android:text="@string/tutorial_2_text"
android:layout_gravity="center"
android:textStyle="bold"
+ android:textSize="@dimen/normal_text"
android:textAlignment="center"
- android:paddingTop="24dp"
android:gravity="center_horizontal"
android:textColor="@android:color/white"/>
@@ -81,7 +80,7 @@
android:text="@string/tutorial_2_subtext"
android:layout_gravity="center"
android:textAlignment="textStart"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="start"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/layout-land/welcome_dont_upload.xml b/app/src/main/res/layout-land/welcome_dont_upload.xml
index 7ac63e560..1d4d0db80 100644
--- a/app/src/main/res/layout-land/welcome_dont_upload.xml
+++ b/app/src/main/res/layout-land/welcome_dont_upload.xml
@@ -12,24 +12,25 @@
android:layout_gravity="center"
android:layout_width="240dp"
android:layout_height="wrap_content"
- android:layout_marginRight="10dp"
- android:layout_marginLeft="10dp"
+ android:layout_marginEnd="@dimen/standard_gap"
+ android:layout_marginRight="@dimen/standard_gap"
>
@@ -50,7 +51,7 @@
android:layout_gravity="center"
android:textStyle="bold"
android:textAlignment="center"
- android:paddingTop="24dp"
+ android:textSize="@dimen/normal_text"
android:gravity="center_horizontal"
android:textColor="@android:color/white"/>
@@ -61,7 +62,7 @@
android:text="@string/tutorial_3_subtext"
android:layout_gravity="center"
android:textAlignment="textStart"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="start"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/layout-land/welcome_final.xml b/app/src/main/res/layout-land/welcome_final.xml
index a9d8c01af..0b99b481f 100644
--- a/app/src/main/res/layout-land/welcome_final.xml
+++ b/app/src/main/res/layout-land/welcome_final.xml
@@ -43,14 +43,15 @@
android:text="@string/welcome_final_text"
android:layout_gravity="center"
android:textStyle="bold"
+ android:textSize="@dimen/normal_text"
android:textAlignment="center"
android:gravity="center_horizontal"
android:textColor="@android:color/white"/>
@@ -45,7 +45,7 @@
android:text="@string/tutorial_4_subtext"
android:layout_gravity="center"
android:textAlignment="textStart"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="start"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/layout-land/welcome_wikipedia.xml b/app/src/main/res/layout-land/welcome_wikipedia.xml
index 1a1531f4c..b4fbe9048 100644
--- a/app/src/main/res/layout-land/welcome_wikipedia.xml
+++ b/app/src/main/res/layout-land/welcome_wikipedia.xml
@@ -13,6 +13,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/welcome_wikipedia"
+ android:layout_marginEnd="@dimen/standard_gap"
+ android:layout_marginRight="@dimen/standard_gap"
android:adjustViewBounds="true"
android:contentDescription="@string/welcome_image_welcome_wikipedia"
/>
@@ -29,8 +31,8 @@
android:text="@string/tutorial_1_text"
android:layout_gravity="center"
android:textStyle="bold"
+ android:textSize="@dimen/normal_text"
android:textAlignment="center"
- android:paddingTop="24dp"
android:gravity="center_horizontal"
android:textColor="@android:color/white"/>
@@ -41,7 +43,7 @@
android:text="@string/tutorial_1_subtext"
android:layout_gravity="center"
android:textAlignment="center"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="center_horizontal"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/layout-xlarge/activity_login.xml b/app/src/main/res/layout-xlarge/activity_login.xml
new file mode 100644
index 000000000..32cd47451
--- /dev/null
+++ b/app/src/main/res/layout-xlarge/activity_login.xml
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
index 89049d10d..76c51a4aa 100644
--- a/app/src/main/res/layout/activity_about.xml
+++ b/app/src/main/res/layout/activity_about.xml
@@ -15,12 +15,14 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
-
@@ -47,7 +49,7 @@
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/standard_gap"
android:gravity="center"
android:text="@string/about_license" />
@@ -56,16 +58,58 @@
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
+ android:layout_marginTop="@dimen/small_gap"
android:gravity="center"
android:text="@string/about_improve" />
+
+
+
+
+
+
+
+
+
+
+
@@ -74,7 +118,7 @@
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="@dimen/standard_gap"
android:gravity="center"
android:text="@string/about_credits" />
@@ -83,7 +127,7 @@
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="32dp"
+ android:layout_marginTop="@dimen/large_gap"
android:alpha="0.2"
android:gravity="center" />
diff --git a/app/src/main/res/layout/activity_contributions.xml b/app/src/main/res/layout/activity_contributions.xml
index 9065618f2..51a48f0a5 100644
--- a/app/src/main/res/layout/activity_contributions.xml
+++ b/app/src/main/res/layout/activity_contributions.xml
@@ -15,9 +15,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
-
+ android:layout_height="match_parent">
-
+ android:layout_marginTop="@dimen/small_gap">
-
-
-
+ android:layout_marginBottom="@dimen/standard_gap"
+ android:layout_marginEnd="@dimen/standard_gap"
+ android:layout_marginLeft="@dimen/standard_gap"
+ android:layout_marginRight="@dimen/standard_gap"
+ android:layout_marginStart="@dimen/standard_gap"
+ android:layout_marginTop="@dimen/large_gap"
+ app:cardCornerRadius="4dp"
+ app:cardElevation="4dp">
-
+ android:gravity="center"
+ android:orientation="vertical">
-
+
-
+
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
diff --git a/app/src/main/res/layout/activity_multiple_uploads.xml b/app/src/main/res/layout/activity_multiple_uploads.xml
index 8095a5dce..2e35444c0 100644
--- a/app/src/main/res/layout/activity_multiple_uploads.xml
+++ b/app/src/main/res/layout/activity_multiple_uploads.xml
@@ -15,12 +15,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
-
+ android:orientation="vertical" />
-
+ android:layout_height="wrap_content" />
+ android:layout_height="match_parent" />
diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml
new file mode 100644
index 000000000..b2eb38475
--- /dev/null
+++ b/app/src/main/res/layout/activity_notification.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_share.xml b/app/src/main/res/layout/activity_share.xml
index a75012077..e915517b5 100644
--- a/app/src/main/res/layout/activity_share.xml
+++ b/app/src/main/res/layout/activity_share.xml
@@ -9,14 +9,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
+ android:background="@color/primaryColor">
+ android:fadingEdge="none" />
+ android:layout_gravity="bottom" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/detail_category_item.xml b/app/src/main/res/layout/detail_category_item.xml
index 89197db5d..51302fd0d 100644
--- a/app/src/main/res/layout/detail_category_item.xml
+++ b/app/src/main/res/layout/detail_category_item.xml
@@ -13,17 +13,17 @@
android:background="?attr/subBackground"
android:foreground="?attr/selectableItemBackground"
android:gravity="center_vertical"
- android:minHeight="48dp"
- android:padding="12dp"
+ android:minHeight="@dimen/overflow_button_dimen"
+ android:padding="@dimen/quarter_standard_height"
android:textColor="@android:color/white"
- android:textSize="14sp"
- app:drawablePadding="6dp"
+ android:textSize="@dimen/description_text_size"
+ app:drawablePadding="@dimen/small_gap"
app:drawableStart="@drawable/ic_info_outline_white_24dp"
/>
diff --git a/app/src/main/res/layout/dialog_nearby_info.xml b/app/src/main/res/layout/dialog_nearby_info.xml
index e34c01c70..4010d3615 100644
--- a/app/src/main/res/layout/dialog_nearby_info.xml
+++ b/app/src/main/res/layout/dialog_nearby_info.xml
@@ -41,16 +41,18 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:layout_marginLeft="12dp"
- android:layout_marginRight="12dp"
+ android:layout_marginLeft="@dimen/quarter_standard_height"
+ android:layout_marginRight="@dimen/quarter_standard_height"
+ android:layout_marginStart="@dimen/quarter_standard_height"
+ android:layout_marginEnd="@dimen/quarter_standard_height"
android:layout_weight="1"
android:ellipsize="end"
android:fontFamily="serif"
android:lineSpacingMultiplier="0.9"
android:maxLines="3"
- android:paddingBottom="4dp"
+ android:paddingBottom="@dimen/tiny_gap"
android:textColor="@android:color/black"
- android:textSize="20sp"
+ android:textSize="@dimen/subheading_text_size"
tools:text="Lorem ipsum" />
@@ -67,8 +69,8 @@
@@ -103,9 +105,9 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:paddingBottom="16dp"
- android:paddingTop="16dp"
- android:text="GET DIRECTIONS"
+ android:paddingBottom="@dimen/standard_gap"
+ android:paddingTop="@dimen/standard_gap"
+ android:text="@string/get_directions"
android:textColor="@android:color/black" />
diff --git a/app/src/main/res/layout/drawer_header.xml b/app/src/main/res/layout/drawer_header.xml
index 5ea1160d9..9bd3ae3a3 100644
--- a/app/src/main/res/layout/drawer_header.xml
+++ b/app/src/main/res/layout/drawer_header.xml
@@ -1,8 +1,30 @@
-
+ android:background="@android:color/darker_gray">
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_categorization.xml b/app/src/main/res/layout/fragment_categorization.xml
index 4cdb9604c..1c7b0a9f7 100644
--- a/app/src/main/res/layout/fragment_categorization.xml
+++ b/app/src/main/res/layout/fragment_categorization.xml
@@ -4,12 +4,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/fragmentCategorisationBackground"
- android:paddingBottom="8dip"
- android:paddingLeft="16dip"
- android:paddingStart="16dip"
- android:paddingRight="16dip"
- android:paddingEnd="16dip"
- android:paddingTop="8dip"
+ android:paddingBottom="@dimen/small_gap"
+ android:paddingLeft="@dimen/standard_gap"
+ android:paddingStart="@dimen/standard_gap"
+ android:paddingRight="@dimen/standard_gap"
+ android:paddingEnd="@dimen/standard_gap"
+ android:paddingTop="@dimen/small_gap"
android:theme="@style/DarkAppTheme"
>
@@ -24,7 +24,7 @@
android:layout_width="match_parent"
android:hint="@string/categories_search_text_hint"
android:maxLines="1"
- android:inputType="text"
+ android:inputType="textCapWords"
android:imeOptions="flagNoExtractUi"
/>
@@ -34,9 +34,9 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:indeterminateOnly="true"
- android:layout_marginRight="4dp"
- android:layout_marginEnd="4dp"
- android:layout_gravity="center_vertical|right|end"
+ android:layout_marginRight="@dimen/tiny_gap"
+ android:layout_marginEnd="@dimen/tiny_gap"
+ android:layout_gravity="center_vertical|end"
style="?android:progressBarStyleSmall"
android:visibility="gone"
/>
@@ -54,7 +54,7 @@
android:id="@+id/categoriesExplanation"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:layout_marginTop="48dp"
+ android:layout_marginTop="@dimen/huge_gap"
android:gravity="center"
android:focusable="true"
android:text="@string/categories_skip_explanation"
diff --git a/app/src/main/res/layout/fragment_contributions.xml b/app/src/main/res/layout/fragment_contributions.xml
index 2be422005..fa53d9721 100644
--- a/app/src/main/res/layout/fragment_contributions.xml
+++ b/app/src/main/res/layout/fragment_contributions.xml
@@ -1,5 +1,6 @@
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml
index 35bcade7a..cffca14c6 100644
--- a/app/src/main/res/layout/fragment_media_detail.xml
+++ b/app/src/main/res/layout/fragment_media_detail.xml
@@ -43,29 +43,29 @@
+ android:layout_height="@dimen/standard_gap" />
+ android:padding="@dimen/standard_gap">
+ android:padding="@dimen/standard_gap">
+ android:textSize="@dimen/description_text_size" />
+ android:layout_height="@dimen/small_gap" />
+ android:padding="@dimen/standard_gap">
+ android:textSize="@dimen/description_text_size" />
+ android:layout_height="@dimen/small_gap" />
+ android:padding="@dimen/standard_gap">
@@ -154,15 +154,15 @@
android:layout_height="wrap_content"
android:background="?attr/subBackground"
android:orientation="vertical"
- android:padding="16dp">
+ android:padding="@dimen/standard_gap">
+ android:layout_height="@dimen/small_gap" />
+ android:layout_height="@dimen/small_gap" />
+ android:padding="@dimen/standard_gap">
+ android:textSize="@dimen/description_text_size" />
diff --git a/app/src/main/res/layout/fragment_no_permissions.xml b/app/src/main/res/layout/fragment_no_permissions.xml
index 8ea6f3a45..6dd2a6811 100644
--- a/app/src/main/res/layout/fragment_no_permissions.xml
+++ b/app/src/main/res/layout/fragment_no_permissions.xml
@@ -9,11 +9,11 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
- android:paddingLeft="30dp"
- android:paddingRight="30dp"
+ android:paddingLeft="@dimen/large_gap"
+ android:paddingRight="@dimen/large_gap"
android:text="@string/nearby_needs_permissions"
android:textAlignment="center"
- android:textSize="18sp" />
+ android:textSize="@dimen/subheading_text_size" />
diff --git a/app/src/main/res/layout/fragment_similar_image_dialog.xml b/app/src/main/res/layout/fragment_similar_image_dialog.xml
new file mode 100644
index 000000000..90fec6bcf
--- /dev/null
+++ b/app/src/main/res/layout/fragment_similar_image_dialog.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_single_upload.xml b/app/src/main/res/layout/fragment_single_upload.xml
index 4e65667a2..87285fa5d 100644
--- a/app/src/main/res/layout/fragment_single_upload.xml
+++ b/app/src/main/res/layout/fragment_single_upload.xml
@@ -5,13 +5,15 @@
android:layout_gravity="fill"
android:orientation="vertical"
android:background="?attr/fragmentCategorisationBackground"
- android:paddingBottom="8dip"
- android:paddingLeft="16dip"
- android:paddingStart="16dip"
- android:paddingRight="16dip"
- android:paddingEnd="16dip"
- android:paddingTop="8dip"
+ android:paddingBottom="@dimen/small_gap"
+ android:paddingLeft="@dimen/standard_gap"
+ android:paddingStart="@dimen/standard_gap"
+ android:paddingRight="@dimen/standard_gap"
+ android:paddingEnd="@dimen/standard_gap"
+ android:paddingTop="@dimen/small_gap"
android:theme="@style/DarkAppTheme"
+ android:clickable="true"
+ android:focusableInTouchMode="true"
>
diff --git a/app/src/main/res/layout/item_notification.xml b/app/src/main/res/layout/item_notification.xml
new file mode 100644
index 000000000..d8f4dd8d4
--- /dev/null
+++ b/app/src/main/res/layout/item_notification.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_place.xml b/app/src/main/res/layout/item_place.xml
index 88784857b..1a87f9d43 100644
--- a/app/src/main/res/layout/item_place.xml
+++ b/app/src/main/res/layout/item_place.xml
@@ -10,9 +10,9 @@
android:id="@+id/icon"
android:layout_width="40dp"
android:layout_height="40dp"
- android:layout_marginLeft="16dp"
- android:layout_marginStart="16dp"
- android:layout_marginTop="16dp"
+ android:layout_marginLeft="@dimen/standard_gap"
+ android:layout_marginStart="@dimen/standard_gap"
+ android:layout_marginTop="@dimen/standard_gap"
android:background="@android:color/white"
android:contentDescription="@string/no_image_found"
android:scaleType="centerCrop"
@@ -25,9 +25,9 @@
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
- android:layout_marginTop="16dp"
+ android:layout_marginLeft="@dimen/standard_gap"
+ android:layout_marginRight="@dimen/standard_gap"
+ android:layout_marginTop="@dimen/standard_gap"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="@string/placeholder_place_distance"
/>
@@ -37,8 +37,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="@id/distance"
- android:layout_marginLeft="16dp"
- android:layout_marginStart="16dp"
+ android:layout_marginLeft="@dimen/standard_gap"
+ android:layout_marginStart="@dimen/standard_gap"
android:layout_toEndOf="@id/icon"
android:layout_toLeftOf="@id/distance"
android:layout_toRightOf="@id/icon"
@@ -58,7 +58,7 @@
android:layout_alignRight="@id/distance"
android:layout_alignStart="@id/tvName"
android:layout_below="@id/tvName"
- android:layout_marginBottom="16dp"
+ android:layout_marginBottom="@dimen/standard_gap"
android:ellipsize="end"
android:maxLines="4"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
diff --git a/app/src/main/res/layout/layout_categories_item.xml b/app/src/main/res/layout/layout_categories_item.xml
index f9b84fb73..d301e9098 100644
--- a/app/src/main/res/layout/layout_categories_item.xml
+++ b/app/src/main/res/layout/layout_categories_item.xml
@@ -6,7 +6,7 @@
android:checkMark="?android:attr/textCheckMark"
android:checked="false"
android:gravity="center_vertical"
- android:padding="4dp"
+ android:padding="@dimen/tiny_gap"
android:theme="@style/DarkAppTheme">
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_contribution.xml b/app/src/main/res/layout/layout_contribution.xml
index 2baf33554..87e7fe9e3 100644
--- a/app/src/main/res/layout/layout_contribution.xml
+++ b/app/src/main/res/layout/layout_contribution.xml
@@ -31,7 +31,7 @@
android:layout_gravity="center|bottom"
android:background="#AA000000"
android:orientation="vertical"
- android:padding="8dp"
+ android:padding="@dimen/small_gap"
>
@@ -62,20 +60,21 @@
android:text="@string/tutorial_2_text"
android:layout_gravity="center"
android:textStyle="bold"
+ android:textSize="@dimen/normal_text"
android:textAlignment="center"
- android:paddingTop="24dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="center_horizontal"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/layout/welcome_dont_upload.xml b/app/src/main/res/layout/welcome_dont_upload.xml
index de748ce68..8d352547e 100644
--- a/app/src/main/res/layout/welcome_dont_upload.xml
+++ b/app/src/main/res/layout/welcome_dont_upload.xml
@@ -9,10 +9,9 @@
diff --git a/app/src/main/res/layout/welcome_final.xml b/app/src/main/res/layout/welcome_final.xml
index 175ddda64..7f323253b 100644
--- a/app/src/main/res/layout/welcome_final.xml
+++ b/app/src/main/res/layout/welcome_final.xml
@@ -9,10 +9,9 @@
@@ -29,7 +27,8 @@
android:layout_width="160dp"
android:layout_height="120dp"
android:layout_gravity="center"
- android:layout_marginLeft="10dp"
+ android:layout_marginLeft="@dimen/standard_gap"
+ android:layout_marginStart="@dimen/standard_gap"
android:contentDescription="@string/welcome_image_welcome_copyright"
/>
@@ -42,6 +41,7 @@
android:text="@string/welcome_final_text"
android:layout_gravity="center"
android:textStyle="bold"
+ android:textSize="@dimen/normal_text"
android:textAlignment="center"
android:gravity="center_horizontal"
android:textColor="@android:color/white"
@@ -49,10 +49,11 @@
@@ -38,7 +39,7 @@
android:text="@string/tutorial_4_subtext"
android:layout_gravity="center"
android:textAlignment="textStart"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="start"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/layout/welcome_wikipedia.xml b/app/src/main/res/layout/welcome_wikipedia.xml
index ea0b6a92a..ece52628e 100644
--- a/app/src/main/res/layout/welcome_wikipedia.xml
+++ b/app/src/main/res/layout/welcome_wikipedia.xml
@@ -24,7 +24,8 @@
android:layout_gravity="center"
android:textStyle="bold"
android:textAlignment="center"
- android:paddingTop="24dp"
+ android:textSize="@dimen/normal_text"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="center_horizontal"
android:textColor="@android:color/white"
/>
@@ -36,7 +37,7 @@
android:text="@string/tutorial_1_subtext"
android:layout_gravity="center"
android:textAlignment="center"
- android:paddingTop="16dp"
+ android:paddingTop="@dimen/standard_gap"
android:gravity="center_horizontal"
android:textColor="@android:color/white"
/>
diff --git a/app/src/main/res/menu/activity_share.xml b/app/src/main/res/menu/activity_share.xml
index 076caf775..67b23a77e 100644
--- a/app/src/main/res/menu/activity_share.xml
+++ b/app/src/main/res/menu/activity_share.xml
@@ -2,7 +2,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
diff --git a/app/src/main/res/menu/drawer.xml b/app/src/main/res/menu/drawer.xml
index f0a0a5e29..83c1bf0ad 100644
--- a/app/src/main/res/menu/drawer.xml
+++ b/app/src/main/res/menu/drawer.xml
@@ -35,4 +35,9 @@
android:icon="@drawable/ic_exit_to_app_black_24dp"
android:title="@string/navigation_item_logout"/>
+
+
diff --git a/app/src/main/res/menu/fragment_categorization.xml b/app/src/main/res/menu/fragment_categorization.xml
index 0b941eb28..de0a87f8c 100644
--- a/app/src/main/res/menu/fragment_categorization.xml
+++ b/app/src/main/res/menu/fragment_categorization.xml
@@ -4,6 +4,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
\ No newline at end of file
diff --git a/app/src/main/res/menu/fragment_contributions_list.xml b/app/src/main/res/menu/fragment_contributions_list.xml
index 2372e2bd2..b623447fd 100644
--- a/app/src/main/res/menu/fragment_contributions_list.xml
+++ b/app/src/main/res/menu/fragment_contributions_list.xml
@@ -3,12 +3,12 @@
diff --git a/app/src/main/res/menu/fragment_image_detail.xml b/app/src/main/res/menu/fragment_image_detail.xml
index 5b3f3e6a9..e864dddb2 100644
--- a/app/src/main/res/menu/fragment_image_detail.xml
+++ b/app/src/main/res/menu/fragment_image_detail.xml
@@ -18,7 +18,7 @@
diff --git a/app/src/main/res/menu/fragment_multiple_upload_list.xml b/app/src/main/res/menu/fragment_multiple_upload_list.xml
index 288d55332..caa00661f 100644
--- a/app/src/main/res/menu/fragment_multiple_upload_list.xml
+++ b/app/src/main/res/menu/fragment_multiple_upload_list.xml
@@ -6,6 +6,6 @@
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_nearby.xml b/app/src/main/res/menu/menu_nearby.xml
index 0143b9ef1..3cc771ea9 100644
--- a/app/src/main/res/menu/menu_nearby.xml
+++ b/app/src/main/res/menu/menu_nearby.xml
@@ -4,7 +4,7 @@
+
+ Аиҧҟьара
+ Иҭабуп!
+
diff --git a/app/src/main/res/values-ab/strings.xml b/app/src/main/res/values-ab/strings.xml
new file mode 100644
index 000000000..326ee585f
--- /dev/null
+++ b/app/src/main/res/values-ab/strings.xml
@@ -0,0 +1,105 @@
+
+
+ Архиарақәа
+ Ахархәаҩ ихьӡ
+ Ажәамаӡа
+ Аҭаларҭа
+ Иҟаҵатәуп арегистрациа
+ Асистемахь аҭаларҭа
+ Шәааҧшы ҧыҭрак...
+ Аҭалара қәҿиарала имҩаҧысит!
+ Асистемахь аҭалараан агха!
+ Афаил ҧшаам. Даҽа фаилк шәахәаҧш.
+ Аутентификациа агха!
+ Аҭагалара иалагоуп!
+ %1$s иҭагалоуп!
+ Шәақәыӷәӷәа иҭагалоу афаил ахәаҧшраз
+ Аҭагалара %1$s иалагоуп
+ %1$s иаҿуп аҭагалара
+ Аҭагалара ахыркәшамҭа %1$s
+ Аҭагалара %1$s амуӡеит
+ Шәақәыӷәӷәа ахәаҧшразы
+ Аҭагалара агха.
+ Инагӡоуп %1$d%%
+ Аҭагалара
+ Агалереиа аҟынтәи
+ Афото ҭыхтәуп
+ Сара сҭагаламҭақәа
+ Иаарттәуп абраузер аҟны
+ Ахьӡ
+ Ахҳәаа
+ Асистемахь аҭалара агха!
+ Аҭагалара
+ Аҧсахрақәа
+ Аҭагалара
+ Актегориа алхра
+ Еиқәырхатәуп
+ Ирҿыцтәуп
+ GPS аҿыхуп шәара шәҟны. Ишәҭахума иаҿашәкыр?
+ Иаҿактәуп GPS
+ Аҭагаламҭақәа макьана иҟам
+ Акатегориақәа
+ Архиарақәа
+ Арегистрациа ҟаҵатәуп
+ Еиҭаҟаҵатәуп
+ Аҟәыхра
+ Иҭыгатәуп
+ Алицензиа
+ Уахынлатәи арежим
+ Ихархәатәуп еиқәаҵәоу атема
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
+ CC0
+ CC BY-SA 3.0
+ CC BY-SA 3.0 (Австриа)
+ CC BY-SA 3.0 (Германиа)
+ CC BY-SA 3.0 (Естониа)
+ CC BY-SA 3.0 (Испаниа)
+ CC BY-SA 3.0 (Хорватиа)
+ CC-BY-SA 3.0 (Лиуксембург)
+ CC-BY-SA 3.0 (Нидерланды)
+ CC BY-SA 3.0 (Норвегиа)
+ CC BY-SA 3.0 (Польша)
+ CC BY-SA 3.0 (Румыниа)
+ CC BY 3.0
+ CC BY-SA 4.0
+ CC BY 4.0
+ CC Zero
+ Аҭагалара аҿырҧштәы:
+ Ари шәара еилышәкаама?
+ Ааи!
+ Акатегориақәа
+ Аҭагалара...
+ Акагь алхӡам
+ Иҟам ахҳәаа
+ Идырым алицензиа
+ Ирҿыцтәуп
+ OK
+ Агәаҽанҵара
+ Ааи
+ Мап
+ Ахьӡ
+ Ахҳәаа
+ Алицензиа
+ Акоординатқәа
+ 2ФА Акод
+ Имаксималу алимит
+ Иауам 500 иреиҳаны раарҧшра
+ Аҿаҧшыратә сахьа
+ Асахьа ҧшаам
+ Иҭагалатәуп асахьа
+ Ашьха Зао
+ Атиульпан
+ Аселфи ыҟаӡам
+ Бзиала шәаабеит Википедиа ахь
+ Сиднеи аопера атеатр
+ Аҟәыхра
+ Иаарттәуп
+ Иарктәуп
+ Иҭагалатәуп
+ Архиарақәа
+ Иҭыҵтәуп
+ ахҳаа ҧшаам
+
diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml
index 8d9b4ead4..5b536aa1d 100644
--- a/app/src/main/res/values-af/strings.xml
+++ b/app/src/main/res/values-af/strings.xml
@@ -6,7 +6,7 @@
WagwoordMeld aanBesig met aanmelding
- Wag asseblief…
+ Wag asseblief…Aanmelding geslaagdAanmelding gefaalVerifikasie het gefaal!
@@ -18,7 +18,7 @@
Voltooi oplaai van %1$sOplaai van %1$s het gefaalVat om te kyk
- My oplaaie
+ My oplaaieGewaglysGefaal%1$d%% voltooi
@@ -43,10 +43,7 @@
Soek kategorieëStoorGeen kategorieë met \"%1$s\" gevind nie
- Voeg kategorieë by om dit makliker te maak om u beelde op te spoor.
-
-Begin tik om kategorieë by te sit.
-Tik die boodskap (of terug) om die stap oor te slaan.
+ Voeg kategorieë by om dit makliker te maak om u beelde op te spoor.\n\nBegin tik om kategorieë by te sit.\nTik die boodskap (of terug) om die stap oor te slaan.KategorieëVoorkeureOor ons
@@ -58,7 +55,7 @@ Tik die boodskap (of terug) om die stap oor te slaan.LisensieJa!Kategorieë
- Laai…
+ Laai…Niks gekies nieGeen beskrywingOnbekende lisensie
diff --git a/app/src/main/res/values-ais/error.xml b/app/src/main/res/values-ais/error.xml
new file mode 100644
index 000000000..85a4b4366
--- /dev/null
+++ b/app/src/main/res/values-ais/error.xml
@@ -0,0 +1,4 @@
+
+
+ kukay
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 9700f1bb1..0049119be 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -20,7 +20,10 @@
اكتمال رفع %1$sفشل رفع %1$sانقر لتشاهد
- متبقى %d
+
+ %1$d رفع ملف
+ %1$d رفع ملفات
+ مرفوعاتي الأخيرةفي قائمة الانتظارفشل
@@ -39,6 +42,7 @@
لا يمكن تسجيل الدخول - فضلا تحقق من كلمة السرالكثير من المحاولات غير الناجحة. الرجاء المحاولة مرة أخرى في بضع دقائق.عذراً، لقد تم منع هذا المستخدم على كومنز
+ يجب توفير رمز التحقق المزدوج.فشل تسجيل الدخولارفعاسم هذه المجموعة
@@ -47,10 +51,12 @@
تصنيفات البحثاحفظأنعش
+ عطل نظام الملاحة العالمي GPS بجهازك. أترغب في التنشيط؟تفعيل GPSلا مرفوعات بعد
+ {{جمع|واحد=%1$d رفع|%1$d رفع}}لا توجد تصنيفات تطابق %1$s
- أضف التصانيف لتسهل اكتشاف صورك على ويكيميديا كومنز.\n\nابدأ الكتاب لتضيف التصانيف.\nانقر هذه الرسالة لتتجاوز هذه الخطوة.
+ أضف التصانيف لتسهل اكتشاف صورك على ويكيميديا كومنز.\n\nابدأ الكتاب لتضيف التصانيف.\nانقر هذه الرسالة لتتجاوز هذه الخطوة.تصنيفاتالإعداداتسجّل
@@ -61,6 +67,7 @@
<a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">CREDITS</a>حولإرسال ملاحظات (عبر البريد الإلكتروني)
+ عميل البريد الإلكتروني غير مثبتالتصانيف المستخدمة مؤخراينتظر أول مزامنة…لم ترفع بعد أية صور.
@@ -70,6 +77,10 @@
نزّلالرخصةالوضع الليلي
+ استخدم النمط الغامق
+ الرجاء عدم رفع
+ - صور ذاتية أو صور لأصدقاء\n- صور محملة من الانترنت\n- لقطات شاشة ذات ملكية خاصة
+ مثال رفع:ساهم بصورك. أنعش مقالات ويكيبيديا!تأتي صور ويكيبيديا من ويكيميديا كومنز.صورك تساعد الناس في شتى أنحاء العالم على التعلم.
@@ -84,21 +95,35 @@
تحديثموافقالأماكن القريبة
+ الأماكن القريبة غير متوفرةتحذيرنعملاالعنوانعنوان الوسيطالوصف
+ تاريخ الرفعالترخيصالإحداثيات
+ غير متوفر
+ انضم لمختبري اصدارات Beta (بيتا)استخدم ويكي بيانات
+ رمز التحقق المزدوج 2FA
+ حد الرفع الحاليالحد الأقصى
+ لا يمكن عرض أكثر من 500
+ تحديد حد الرفع الحالي
+ التحقق المزدودج 2FA غير مدعوم حاليا
+ أترغب فعلا في الخروج؟الرئيسيةرفعبالقرب من هناحولالإعداداتتسجيل الخروج
+ إشعاراتصفحة ملف كومنز
+ مرحبا بكم في ويكيمديا كومنز، %1$s! نحن سعداء لأنك هنا.
+ %1$s رسالة على صفحة الحديث
+ %1$s ذكر لك على %2$s.
diff --git a/app/src/main/res/values-arc/error.xml b/app/src/main/res/values-arc/error.xml
new file mode 100644
index 000000000..ce31ff09b
--- /dev/null
+++ b/app/src/main/res/values-arc/error.xml
@@ -0,0 +1,4 @@
+
+
+ ܬܘܕܝ ܠܟ!
+
diff --git a/app/src/main/res/values-as/strings.xml b/app/src/main/res/values-as/strings.xml
new file mode 100644
index 000000000..d66590636
--- /dev/null
+++ b/app/src/main/res/values-as/strings.xml
@@ -0,0 +1,52 @@
+
+
+ কমন্স
+ ছেটিং
+ সদস্যনাম
+ গুপ্তশব্দ
+ প্ৰৱেশ
+ পঞ্জীয়ন কৰক
+ প্ৰৱেশ হৈ আছে
+ অনুগ্ৰহ কৰি অপেক্ষা কৰক...
+ প্ৰৱেশ সফল হ\'ল!
+ প্ৰৱেশ বিফল হৈছে!
+ ফাইল পোৱা নগ\'ল। অনুগ্ৰহ কৰি আন এটা ফাইল চেষ্টা কৰক।
+ প্ৰমাণীকৰণ ব্যৰ্থ হৈছে!
+ আপল\'ড আৰম্ভ হৈছে!
+ %1$s আপল\'ড হৈছে!
+ আপোনাৰ আপল\'ড চাবলৈ টেপ্ কৰক
+ %1$s আপল\'ড আৰম্ভ হৈছে
+ %1$s আপল\'ড হৈছে
+ %1$s আপল\'ড শেষ হৈছে
+ %1$s আপল\'ড ব্যৰ্থ হৈছে
+ চাবৰ বাবে স্পৰ্শ কৰক
+ মোৰ শেহতীয়া আপল\'ড
+ অপেক্ষাৰত
+ ব্যৰ্থ হৈছে
+ %1$d%% সম্পূৰ্ণ হৈছে
+ আপল’ড কৰি থকা হৈছে
+ গেলাৰীৰ পৰা
+ ছবি তোলক
+ কাষৰীয়া
+ মোৰ আপল\'ডসমূহ
+ শ্বেয়াৰ কৰক
+ ব্ৰাউজাৰত চাওক
+ শিৰোনামা
+ বিৱৰণ
+ প্ৰৱেশ ব্যৰ্থ হৈছে
+ আপল’ড কৰক
+ পৰিৱৰ্তনসমূহ
+ আপল’ড কৰক
+ শ্ৰেণী সন্ধান কৰক
+ সাঁচি থওক
+ ৰিফ্ৰেচ
+ আপোনাৰ সঁজুলিত GPS নিষ্ক্ৰিয় হৈ আছে। আপুনি ইয়াক সক্ৰিয় কৰিব খুজিছে নেকি?
+ GPS সক্ৰিয় কৰক
+ এতিয়ালৈকে কোনো আপল\'ড নাই
+ শ্ৰেণী
+ ছেটিংছ
+ পঞ্জীয়ন কৰক
+ বিষয়ে
+ পুনৰ চেষ্টা কৰক
+ বাতিল কৰক
+
diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml
index 0a9e52d6e..204fd3767 100644
--- a/app/src/main/res/values-ast/strings.xml
+++ b/app/src/main/res/values-ast/strings.xml
@@ -22,7 +22,7 @@
Toque pa veloXubiendo un ficheru
- Xubiendo %d ficheros
+ Xubiendo %1$d ficherosLes mios xubes reciénEn cola
@@ -56,26 +56,26 @@
Inda nun hai xubes\@string/contributions_subtitle_zero
- 1 carga
- %d cargues
+ %1$d carga
+ %1$d cargues
- Principiando 1 carga
- Principiando %d cargues
+ Principiando %1$d carga
+ Principiando %1$d cargues
- 1 carga
- %d cargues
+ %1$d carga
+ %1$d carguesNun s\'alcontró denguna categoría que case con %1$s
- Amieste categoríes pa facer les imaxes más fáciles d\'alcontrar en Wikimedia Commons.\n\nPrincipie a escribir p\'amestar categoríes.\nToque nesti mensaxe (o calque atrás) pa saltar esti pasu.
+ Amiesta categoríes pa facer les imaxes más fáciles d\'alcontrar en Wikimedia Commons.\nPrincipia a escribir p\'amestar categoríes.CategoríesConfiguraciónDate d\'altaTocante a
- Software de códigu abiertu lliberáu baxo la <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Llicencia Apache v2</a>. %1$s ya\'l so logotipu son marques rexistraes de la Fundación Wikimedia y utilícense col so permisu. Nun tamos acreditaos pola Fundación Wikimedia nin tamos afiliaos con ella.
- El <a href=\"https://github.com/commons-app/apps-android-commons\">códigu fonte</a> ya\'l <a href=\"https://commons-app.github.io/\">sitiu web</a> tán en GitHub. Crea una nueva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia en GitHub</a> pa informar de problemes y suxerencies.
- Wikimedia:Commons-android-strings-about privacy policy/ast
+ La app de Wikimedia Commons ye software de códigu abiertu, creáu y calteníu por becaos y voluntarios de la comunidá de Wikimedia. La Fundación Wikimedia nun participa na creación, desendolcu nin caltenimientu de la app.
+ Crea una nueva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia en GitHub</a> pa informar de problemes y suxerencies.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Política d\'intimidá</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Créditos</a>Tocante aUnviar comentarios (per corréu)
@@ -121,7 +121,7 @@
NUN xubas:- Selfies o semeyes de los tos amigos\n- Imáxenes que descargasti d\'Internet\n- Imáxenes de pantalla d\'aplicaciones propietariesExemplu de carga:
- - Títulu: Ópera de Sidney\n- Descripción: Teatru de la Ópera de Sidney vistu dende l\'otru llau de la badea\n- Categoríes: Ópera de Sidney, Ópera de Sidney dende l\'oeste, Vistes a distancia de la Ópera de Sidney
+ - Títulu: Ópera de Sidney\n- Descripción: Teatru de la Ópera de Sidney vistu dende l\'otru llau de la badea\n- Categoríes: Ópera de Sidney dende l\'oeste, Vistes a distancia de la Ópera de SidneyCollabore coles sos imaxes. ¡Ayude a que los artículos de Wikipedia tengan vida!Les imaxes de Wikipedia vienen de\nWikimedia Commons.Les sos imaxes ayuden a educar a la xente alredor del mundu.
@@ -147,7 +147,7 @@
TítuluTítulu del mediuDescripción
- Equí va la descripción del mediu. Posiblemente pué ser llargo enforma, y necesitará espardese per delles llinies. Sicasí, esperamos que se vea guapo.
+ Equí va la descripción del mediu. Esto pué ser llargo enforma, y necesitará espardese per delles llinies. Sicasí, esperamos que se vea bien.Data d\'unviuLlicenciaCoordenaes
@@ -164,6 +164,9 @@
Anguaño nun hai encontu pa autenticación de dos factores.¿Confirmes que quies salir?Logo de Commons
+ Sitiu web de Commons
+ Páxina de Facebook de Commons
+ Códigu fonte de Commons en GitHubImaxe de fonduFalló la imaxe multimediaNun s\'atopó nenguna imaxe
@@ -188,6 +191,7 @@
La to opiniónSalirTutorial
+ AvisosLos sitios cercanos nun pueden amosase ensin los permisos d\'allugamientunun s\'atoparon descripcionesPáxina del ficheru en Commons
@@ -198,4 +202,17 @@
Dar permisuUsar almacenamientu esternuGuardar nel preséu les imaxes tomaes cola cámara de la app
+ Unviar ficheru de rexistru
+ Unviar ficheru de rexistru a los desendolcadores per corréu electrónicu
+ Anicia sesión na to cuenta
+ L\'allugamientu nun camudó.
+ L\'allugamientu nun ta disponible.
+ Ríquese permisu p\'amosar una llista de llugares cercanos
+ CÓMO LLEGAR
+ LLEER L\'ARTÍCULU
+ ¡Afáyate\'n Wikimedia Commons, %1$s! Préstanos que teas equí.
+ %1$s dexó un mensaxe na to páxina d\'alderique
+ Gracies por facer una edición
+ %1$s te mentó en %2$s.
+ Alternar vista
diff --git a/app/src/main/res/values-b+be+tarask/error.xml b/app/src/main/res/values-b+be+tarask/error.xml
new file mode 100644
index 000000000..d39823584
--- /dev/null
+++ b/app/src/main/res/values-b+be+tarask/error.xml
@@ -0,0 +1,4 @@
+
+
+ Дзякуем!
+
diff --git a/app/src/main/res/values-b+kk+Cyrl/error.xml b/app/src/main/res/values-b+kk+Cyrl/error.xml
new file mode 100644
index 000000000..1b0279528
--- /dev/null
+++ b/app/src/main/res/values-b+kk+Cyrl/error.xml
@@ -0,0 +1,4 @@
+
+
+ Рақмет!
+
diff --git a/app/src/main/res/values-b+nds+NL/strings.xml b/app/src/main/res/values-b+nds+NL/strings.xml
index 073375e8d..04637a333 100644
--- a/app/src/main/res/values-b+nds+NL/strings.xml
+++ b/app/src/main/res/values-b+nds+NL/strings.xml
@@ -43,15 +43,13 @@
Kategorieën zeukenOpslaonDer bin gien kategorieën mit \"%1$s\" evunnen
- Zet der kategorieën bie um joew aofbeeldingen makkeliker viendbaor te maken op Wikimedia Commons.\n\nBegin mit schrieven um kategorieën derbie te zetten.\n\nKlik op dit bericht (of gao weerumme) um disse stap over te slaon.
+ Zet der kategorieën bie um joew aofbeeldingen makkeliker viendbaor te maken op Wikimedia Commons.\n\nBegin mit schrieven um kategorieën derbie te zetten.\n\nKlik op dit bericht (of gao weerumme) um disse stap over te slaon.Kategorieën
- Gebruuksrapporten
- Gebruuksrapporten naor Wikimedia sturen um ons te helpen de applikasie te verbeterenVeurkeurenInformasieOpen Source-programmatuur uutgeven onder de linsensie <a href=\" https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>Bronkode op <a href=\"https://github.com/commons-app/android-commons\">GitHub</a>. Fouten bie <a href=\"https://bugzilla.wikimedia.org/enter_bug.cgi?product=Commons%20App\">Bugzilla</a>.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Gegevensbeleid</a>
+ <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Gegevensbeleid</a>InformasieWat wi\'j ons vertellen? (per netpost)Kortens gebruukten kategorieën
diff --git a/app/src/main/res/values-b+sr+Latn/error.xml b/app/src/main/res/values-b+sr+Latn/error.xml
new file mode 100644
index 000000000..80d668c8b
--- /dev/null
+++ b/app/src/main/res/values-b+sr+Latn/error.xml
@@ -0,0 +1,7 @@
+
+
+ Ostava se srušila
+ Ups! Nešto je pošlo naopako.
+ Recite nam šta ste radili pa to saznanje podelite s nama, putem e-pošte. Time ćete nam pomoći da rešimo problem!
+ Hvala vam!
+
diff --git a/app/src/main/res/values-b+sr+Latn/strings.xml b/app/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 000000000..be209bfa5
--- /dev/null
+++ b/app/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,209 @@
+
+
+ Ostava
+ Podešavanja
+ Korisničko ime
+ Lozinka
+ Prijavi me
+ Otvori nalog
+ Prijavljivanje
+ Sačekajte…
+ Uspešno ste prijavljeni.
+ Prijavljivanje nije uspelo.
+ Datoteka nije pronađena. Pokušajte sa drugom datotekom.
+ Provera identiteta nije uspela.
+ Otpremanje je započeto.
+ Datoteka „%1$s“ je otpremljena.
+ Tapnite da biste videli otpremanje
+ Počinjem sa otpremanjem datoteke „%1$s“
+ Otpremanje datoteke „%1$s“
+ Završavam sa otpremanjem datoteke „%1$s“
+ Ne mogu da otpremim „%1$s“
+ Tapnite da biste videli
+
+ %1$d datoteka se otprema
+ %1$d datoteke se otpremaju
+
+ Moja skorašnja otpremanja
+ Na čekanju
+ Nije uspelo
+ %1$d%% otpremljeno
+ Otpremam
+ Iz galerije
+ Fotografiši
+ U blizini
+ Moja otpremanja
+ Deli
+ Otvori u pregledaču
+ Naslov
+ Opis
+ Ne mogu da vas prijavim – mreža ne radi
+ Ne mogu da vas prijavim – proverite svoje korisničko ime
+ Ne mogu da vas prijavim – proverite svoju lozinku
+ Previše neuspešnih pokušaja. Probajte ponovo za nekoliko minuta.
+ Nažalost, korisnik je blokiran na Ostavi
+ Morate uneti svoj dvofaktorski kod za autentifikaciju.
+ Prijava nije uspela
+ Otpremi
+ Dajte ime ovom kompletu
+ Izmene
+ Otpremi
+ Pretraži kategorije
+ Sačuvaj
+ Osveži
+ GPS je onemogućen na Vašem uređaju. Želite li ga omogućiti?
+ Omogući GPS
+ Još uvek nema otpremanja
+
+ \@string/contributions_subtitle_zero
+ %1$d otpremanje
+ %1$d otpremanja
+
+
+ Započni %1$d otpremanje
+ Započni %1$d otpremanja
+
+
+ %1$d otpremanje
+ %1$d otpremanja
+
+ Nema kategorija koje odgovaraju %1$s
+ Dodajte kategorije na slike da biste olakšali korisnicima njihovo pronalaženje na Ostavi.\n\nDa biste dodali kategoriju, počnite sa pisanjem njenog imena.
+ Kategorije
+ Postavke
+ Otvori nalog
+ O aplikaciji
+ Softver otvorenog koda dostupan pod licencom <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache ver. 2</a> Vikimedijina Ostava i njen logo su zaštitni znaci Vikimedijine Fondacije i koriste se sa dozvolom Vikimedijine Fondacine. Mi ne odobravamo ili podržavmo Vikimedijinu Fondaciju.\n\nAplikacija za Vikimedijinu ostavu je aplikacija otvorenog koda koja je napravljena i koja se održava pomoću grantova i volontera Vikimedijine zajednice. Zadužbina Vikimedija nije uključena u stvaranje, razvoj ili održavanje aplikacije.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Izvorni kôd</a> i <a href=\"https://commons-app.github.io/\">veb-sajt</a> na GitHub-u. Napravite novi <a href=\"https://github.com/commons-app/apps-android-commons/issues\">zahtev na GitHub-u</a> da biste prijavili greške ili dali predloge.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Politika privatnosti</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Zasluge</a>
+ O aplikaciji
+ Pošaljite povratne informacije (putem e-pošte)
+ Nije instaliran imejl klijent
+ Nedavno korišćene kategorije
+ Čekam na prvu sinhronizaciju…
+ Još niste otpremili nijednu fotografiju.
+ Pokušaj ponovo
+ Otkaži
+ Slika će se voditi pod licencom %1$s
+ Slanjem ove slike, ja tvrdim da je u pitanju moj rad, da ne sadrži materijal ili selfije zaštićene autorskim pravima, te da je na ostale načine u skladu sa <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">smernicama Vikimedijine ostave</a>.
+ Preuzmi
+ Licenca
+ Koristi prethodan naslov/opis
+ Automatski detektuj trenutnu lokaciju
+ Primi trenutnu lokaciju da bi predložili kategoriju ako slika nije geografski označena
+ Noćni režim
+ Koristiti tamnu temu
+ Autorstvo-Deliti pod istim uslovima 4.0
+ Autorstvo 4.0
+ Autorstvo-Deliti pod istim uslovimau 3.0
+ Autorstvo 3.0
+ CC0
+ CC BY-SA 3.0
+ CC BY-SA 3.0 (Austrija)
+ CC BY-SA 3.0 (Nemačka)
+ CC BY-SA 3.0 (Estonija)
+ CC BY-SA 3.0 (Španija)
+ CC BY-SA 3.0 (Hrvatska)
+ CC BY-SA 3.0 (Luksemburg)
+ CC BY-SA 3.0 (Holandija)
+ CC BY-SA 3.0 (Norveška)
+ CC BY-SA 3.0 (Poljska)
+ CC BY-SA 3.0 (Rumunija)
+ CC BY 3.0
+ CC BY-SA 4.0
+ CC BY 4.0
+ CC Nula
+ Vikimedijina Ostava sadrži većinu slika koja se koristi na Vikipediji.
+ Vaše slike pomažu u obrazovanju ljudi širom sveta.
+ Molimo Vas da postavite slike koje ste preuzeli ili kreirali u potpunosti sami:
+ - Prirodne objekte (cveće, životinje, planine)\n- Korisne objekte (bicikle, železničke stanice)\n- Poznate ljude (vaš gradonačelnik, Olimpijce koje ste sreli)
+ Molimo NE otpremajte:
+ - Selfije i slike tvojih prijatelja\n- Slike koje ste preuzeli sa interneta\n- Skrinšotove iz sopstvenih aplikacija
+ Primer otpremanja:
+ — Naslov: Sidnejska opera\n— Opis: Sidnejska opera, pogled preko zaliva\n— Kategorije: Sidnejska opera sa zapada, pogledi na Sidnejksu operu iz daljine
+ Delite svoje slike. Oživite članke na Vikipediji!
+ Slike na Vikipediji dolaze iz Ostave.
+ Sa vašim slikama pomažete u obrazovanju ljudi širom sveta.
+ Izbegavajte materijale koje ste našli na internetu, kao i slike plakata, korica knjiga itd.
+ Jeste li razumeli?
+ Jesam!
+ Kategorije
+ Učitavam…
+ Ništa nije izabrano
+ Nema opisa
+ Nepoznata licenca
+ Osveži
+ Potrebna dozvola: Provera spoljašnje memorije. Aplikacija bez ovoga ne može da funkcioniše.
+ Neophodna dozvola: Pisanje spoljašnjeg skladišta. Aplikacija ne može da funkcioniše bez ovoga.
+ Opciona dozvola: Preuzmi trenutnu lokaciju za predloge kategorija
+ U redu
+ Mesta u blizini
+ Nisu pronađena obližnja mesta
+ Upozorenje
+ Ova datoteka je već dostupna na Ostavi. Da li ste sigurni da želite da nastavite?
+ Da
+ Ne
+ Naslov
+ Naslov medija
+ Opis
+ Opis datoteke ide ovde. Može da bude poprilično dug i prikazivaće se u više redova. Nadamo se da će izgledati lepo.
+ Datum otpremanja
+ Licenca
+ Koordinate
+ Ništa nije uneto
+ Postani Beta Tester
+ Priključite se na naš beta kanal na Gugl pleju i pristupajte novim informacijama i popravkama bagova
+ Koristi Vikipodatke
+ (Upozorenje: onemogućavanjem ovoga može se izazvati velika potrošnja mobilnih podataka)
+ 2FA kod
+ Moj limit za skorašnja otpremanja
+ Maksimalni limit
+ Nije moguće prikazati više od 500
+ Postavi limit za skorašnja otpremanja
+ Dvofaktorska autentifikacija trenutno nije podržana.
+ Zaista želite da se odjavite?
+ Logo Ostave
+ Pozadinska slika
+ Medijska slika neuspešna
+ Slika nije pronađena
+ Otpremi sliku
+ Planina Zao
+ Lame
+ Dugin most
+ Tulipan
+ Bez selfija
+ Vlasnička slika
+ Dobrodošlica Vikipediji
+ Dobrodošlica za autorska prava
+ Sidnejska opera
+ Otkaži
+ Otvori
+ Zatvori
+ Početna
+ Otpremanje
+ U blizini
+ O nama
+ Podešavanja
+ Povratne informacije
+ Odjavi me
+ Tutorijal
+ Obaveštenja
+ Obližnja mesta ne mogu da se prikazuju bez dozvola za lokaciju
+ opis nije pronađen
+ Stranica datoteke na Ostavi
+ Stavka na Vikipodacima
+ Greška pri keširanju slika
+ Jedinstven opisni naslov za datoteku, koji će biti ime datoteke. Možete da koristite obični jezik sa razmacima. Ne treba unositi ekstenziju datoteke
+ Molimo da opišete datoteku koliko je to moguće: Gde je napravljena? Šta prikazuje? Šta je kontekst? Opišite objekte i/ili osobe. Otkrijte informacije koje se ne mogu lako pogoditi, na primer doba dana ako je u pitanju pejzaž. Ako datoteka prikazuje nešto neobično, molimo da objasnite šta je to čini neobičnom.
+ Davanje dozvole
+ Upotreba spoljašnjeg skladišta
+ Spremanje slika napravljenih kamerom aplikacije na Vašem uređaju
+ Pošalji dnevničku datoteku
+ Pošalji dnevničku datoteku developerima preko imejla
+ Prijavite se na svoj nalog
+ Lokacija nije promenjena.
+ Lokacija nije dostupna.
+ Potrebna je dozvola za prikazivanje liste lokacija u blizini
+ Dobrodošli na Wikimedia Commons, %1$s! Drago nam je da ste ovdje.
+
diff --git a/app/src/main/res/values-b+tg+Cyrl/error.xml b/app/src/main/res/values-b+tg+Cyrl/error.xml
new file mode 100644
index 000000000..478aa98ea
--- /dev/null
+++ b/app/src/main/res/values-b+tg+Cyrl/error.xml
@@ -0,0 +1,4 @@
+
+
+ Ташаккур!
+
diff --git a/app/src/main/res/values-b+tg+Cyrl/strings.xml b/app/src/main/res/values-b+tg+Cyrl/strings.xml
index 0785618e9..31706ad98 100644
--- a/app/src/main/res/values-b+tg+Cyrl/strings.xml
+++ b/app/src/main/res/values-b+tg+Cyrl/strings.xml
@@ -29,7 +29,6 @@
Ҷустуҷӯи гурӯҳҳоЗахираГурӯҳҳо
- Ҳисоботи истифодаТанзимотДар бораиДар бораи
@@ -53,7 +52,7 @@
CC BY-SA 3.0 (Руминия)CC BY 3.0CC Zero
- Викианбор дорои бисёр файлҳое аст, ки тарафи Википедиа истифода мешаванд.
+ Викианбор дорои бисёр парвандаҳое аст, ки тарафи Википедия истифода мешаванд.Аксҳои шумо барои таълими одамон аз тамоми ҷаҳон кӯмак мерасонанд.Фикр мекунед дарк кардед?Бале!
@@ -63,7 +62,7 @@
Иҷозатномаи ношиносИҷозатномаКоординатҳо
- Истифодаи Викимаълумот
- Элементи Викимаълумот
+ Истифодаи Викидода
+ Унсури ВикидодаХатогӣ ҳангоми кэшкунии тасвир
diff --git a/app/src/main/res/values-ba/error.xml b/app/src/main/res/values-ba/error.xml
new file mode 100644
index 000000000..d57fd8936
--- /dev/null
+++ b/app/src/main/res/values-ba/error.xml
@@ -0,0 +1,4 @@
+
+
+ Рәхмәт!
+
diff --git a/app/src/main/res/values-bcl/strings.xml b/app/src/main/res/values-bcl/strings.xml
index 0ac6fa3a2..fd196dbec 100644
--- a/app/src/main/res/values-bcl/strings.xml
+++ b/app/src/main/res/values-bcl/strings.xml
@@ -43,15 +43,13 @@
Hanapa an mga kategoriyaItagamaMayong mga kategoriya an mainukno sa %1$s an nanagboan
- Magpoon sa pagtipa tanganing magdugang nin mga kategoriya.\nTaptapon ining mensahe (o pinduta an ibalik) tanganing luksuhan ining panuntungan.
+ Magpoon sa pagtipa tanganing magdugang nin mga kategoriya.\nTaptapon ining mensahe (o pinduta an ibalik) tanganing luksuhan ining panuntungan.Mga Kategoriya
- Mga talaan nin paggagamit
- Ipadara an mga talaan nin paggagamit sa Wikimidya sa pagtabang samuya na mapakarhay an appMga PanuytoyPanunungodBukas na Ginikanang panuklob ipinaghaya na sa lindong kan <a href=\" https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>Ginikanan sa <a href=\"https://github.com/commons-app/android-commons\">GitHub</a>. Mga kubol sa <a href=\"https://bugzilla.wikimedia.org/enter_bug.cgi?product=Commons%20App\">Bugzilla</a>.
- Ano ini?
+ Ano ini?PanunungodIpadara an balik-simbag (sa paagi nin E-surat)Dae pa sana nahaloy na pinaggamit na mga kategoriya
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index 2f874d462..abb15ac3d 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -8,8 +8,8 @@
РегистриранеВлизане в систематаИзчакайте…
- Успешно вписване.
- Неуспешно вписване!
+ Успешно влизане.
+ Неуспешно влизане!Файлът не е намерен. Моля, опитайте с друг файл.Неуспешен опит за удостоверяване!Качването започна!
@@ -32,8 +32,8 @@
КачванеСъхраняване
- %d качване
- %d качвания
+ %1$d качване
+ %1$d качванияКатегорииНастройки
diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml
index f8a770c5c..d8e927712 100644
--- a/app/src/main/res/values-bn/strings.xml
+++ b/app/src/main/res/values-bn/strings.xml
@@ -21,8 +21,8 @@
%1$s আপলোড ব্যর্থদেখার জন্য টোকা দিন
- %dটি ফাইল আপলোড হচ্ছে
- %dটি ফাইল আপলোড হচ্ছে
+ %1$dটি ফাইল আপলোড হচ্ছে
+ %1$dটি ফাইল আপলোড হচ্ছেআমার সাম্প্রতিক আপলোডঅপেক্ষারত
@@ -42,6 +42,7 @@
প্রবেশ করা যাচ্ছে না - অনুগ্রহ করে আপনার পাসওয়ার্ড পরীক্ষা করুনখুব বেশি অসফল প্রচেষ্টা। অনুগ্রহ করে কয়েক মিনিট পরে আবারও চেষ্টা করুন।দুঃখিত, এই ব্যবহারকারীকে কমন্সে বাধা দেয়া হয়েছে
+ অাপনাকে অবশ্যই অাপনার দু\'স্তরের সত্যায়নকরণ কোড দিতে হবে।প্রবেশ ব্যর্থআপলোডএই সেটের নাম
@@ -55,25 +56,25 @@
এখনো কোন আপলোড নেই\@string/contributions_subtitle_zero
- %dটি আপলোড
- %dটি আপলোড
+ %1$dটি আপলোড
+ %1$dটি আপলোড
- %dটি আপলোড শুরু হয়েছে
- %dটি আপলোড শুরু হয়েছে
+ %1$dটি আপলোড শুরু হয়েছে
+ %1$dটি আপলোড শুরু হয়েছে
- %dটি আপলোড
- %dটি আপলোড
+ %1$dটি আপলোড
+ %1$dটি আপলোড%1$s-এর সাথে মিলে এমন কোন বিষয়শ্রেণী পাওয়া যায়নি
- উইকিমিডিয়া কমন্স থেকে আপনার মিডিয়া ফাইলটি সহজে খুঁজে পেতে বিষয়শ্রেণী যুক্ত করুন।\n\nবিষয়শ্রেণী যোগ করার জন্য টাইপিং শুরু করুন।\nএই ধাপটি বাতিল করতে এই বার্তাতে (বা ফিরে যান) টোকা দিন।
+ উইকিমিডিয়া কমন্স থেকে আপনার মিডিয়া ফাইলটি সহজে খুঁজে পেতে বিষয়শ্রেণী যুক্ত করুন।\n\nবিষয়শ্রেণী যোগ করার জন্য টাইপিং শুরু করুন।বিষয়শ্রেণীসমূহসেটিংনিবন্ধন করুনপরিচিতি
- ওপেন সোর্স সফটওয়্যার <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">এ্যাপাচি লাইসেন্স v2</a> অধীনে প্রকাশিত
- <a href=\"https://github.com/commons-app/apps-android-commons\">উৎস</a> ও <a href=\"https://commons-app.github.io/\">ওয়েবসাইট</a> GitHub এ</a>। কোন সমস্যা ও পরামর্শের জন্য <a href=\"https://github.com/commons-app/apps-android-commons/issues\">গিটহাব ইস্যু</a> তৈরি করুন।
+ উইকিমিডিয়া কমন্স অ্যাপ হচ্ছে একটি উন্মুক্ত উৎস সম্বলিত অ্যাপ যা উইকিমিডিয়া সম্প্রদায়ের ব্যবহারকারী ও সেচ্ছাসেবকবৃন্দ কর্তৃক তৈরিকৃত এবং পরিচালিত। উইকিমিডিয়া ফাউন্ডেশন এই অ্যাপ তৈরি, উন্নয়ন বা রক্ষণাবেক্ষণে জড়িত নয়।
+ কোন সমস্যা ও পরামর্শের জন্য <a href=\"https://github.com/commons-app/apps-android-commons/issues\">গিটহাব ইস্যু</a> তৈরি করুন।<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">গোপনীয়তার নীতি</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">কৃতিত্ব</a>পরিচিতি
@@ -120,7 +121,7 @@
দয়া করে আপলোড করবেন না:u2022 আপনার বন্ধুর সেলফি বা ছবি \nu2022 ইন্টারনেট থেকে ডাউনলোড করা ছবি \nu2022 মালিকানাযুক্ত অ্যাপসমূহের স্ক্রীনশটআপলোডের উদাহরণ:
- u2022 শিরোনাম: সিডনি অপেরা হাউস \nu2022 বিবরণ: উপসাগর থেকে দেখা সিডনি অপেরা হাউস\nu2022 বিষয়শ্রেণী: সিডনি অপেরা হাউস, পশ্চিম দিক থেকে সিডনি অপেরা হাউস, সিডনি অপেরা হাউসের দূরবর্তী দৃশ্য
+ - শিরোনাম: সিডনি অপেরা হাউস \n- বিবরণ: উপসাগর থেকে দেখা সিডনি অপেরা হাউস\n- বিষয়শ্রেণী: সিডনি অপেরা হাউস, পশ্চিম দিক থেকে সিডনি অপেরা হাউস, সিডনি অপেরা হাউসের দূরবর্তী দৃশ্যআপনার ছবি দিয়ে অবদান রাখুন। উইকিপিডিয়ার নিবন্ধগুলিকে নতুন রূপ দিতে সাহায্য করুন!উইকিপিডিয়াতে চিত্র উইকিমিডিয়া কমন্স থেকে এসেছে।আপনার ছবি সারা বিশ্বের শিক্ষিত মানুষকে সাহায্য করবে।
@@ -134,6 +135,7 @@
অজানা লাইসেন্সপুনঃসতেজপ্রয়োজনীয় অনুমতি: বহিঃস্ত সঞ্চয়স্থান পড়া। এটি ছাড়া অ্যাপ কাজ করবে না।
+ অনুমতি প্রয়োজন: অালাদাভাবে সংযুক্ত স্টোরেজ লিখুন। এটি ছাড়া অ্যাপটি চলতে পারেনা।ঐচ্ছিক অনুমতি: বিষয়শ্রেণী পরামর্শের জন্য বর্তমান অবস্থান নেয়ঠিক আছেকাছাকাছি স্থান
@@ -151,14 +153,20 @@
স্থানাঙ্কসমূহকিছু দেয়া হয়নিবিটা টেস্টার হোন
+ গুগল প্লেতে আমাদের বেটা চ্যানেলে যুক্ত হন! এতে নতুন বৈশিষ্ট্যের পাশাপাশি পুরানো বাগগুলো\'র সংশোধিত রুপ সবার আগে ব্যবহারের সুযোগ পাবেনউইকিউপাত্ত ব্যবহার করুন(সতর্কতা: এটি নিষ্ক্রিয় করা অধিক পরিমাণে মোবাইল ডেটা খরচ হওয়ার কারণ হতে পারে)
+ 2FA কোডআমার সাম্প্রতিক আপলোড সীমাসর্বোচ্চ সীমা৫০০টির বেশি প্রদর্শন করা সম্ভব নয়সাম্প্রতিক আপলোড সীমা নির্ধারন করুন
+ দুটি স্তরের প্রমাণীকরণ বর্তমানে সমর্থিত নয়।আপনি কি সত্যিই প্রস্থান করতে চান?কমন্সের লোগো
+ কমন্স ওয়েবসাইট
+ কমন্সের ফেসবুক পাতা
+ কমন্স গিটহাব উৎস কোডপটভূমির চিত্রমিডিয়া চিত্র ব্যর্থ হয়েছেকোন চিত্র পাওয়া যায়নি
@@ -183,10 +191,27 @@
প্রতিক্রিয়াপ্রস্থানভূমিকা
+ বিজ্ঞপ্তিঅবস্থানের অনুমতি ছাড়া কাছাকাছি জায়গাগুলি প্রদর্শন করা যাবে নাকোন বিবরণ পাওয়া যায়নিকমন্সে ফাইলের পাতাউইকিউপাত্ত পদ
+ ছবি আনার সময় ত্রুটি
+ ফাইলের একটি স্বতন্ত্র বর্ণনামূলক নাম যা ফাইলের নাম হিসাবে কাজ করবে। অাপনি সাধারণ ভাষা ব্যবহার করতে পারেন শূন্যস্থানসহ। ফাইলের এক্সটেনশন যুক্ত করবেন না।
+ যতটা সম্ভব মিডিয়াটি বর্ণনা করুন: এটি কোথায় ধারণ করা হয়েছিল? এটি কি প্রদর্শন করে? এটির প্রসঙ্গ কি? ধারণকৃত বস্তু অথবা ব্যক্তির বর্ণনা করুন। সহজে অনুমান করা যায়না সেরকম তথ্য উদঘাটন করুন, উদাহরণস্বরূপ, যদি ল্যান্ডস্কেপ হয় তাহলে দিবসকালের সময় দিন।অনুমতি দিনবাহ্যিক সঞ্চয়স্থান ব্যবহার করুন
+ অাপনার ডিভাইসের নিজস্ব ক্যামেরায় ধারণকৃত ছবি সংরক্ষণ করুন
+ লগ ফাইল পাঠান
+ ইমেইলের মাধ্যমে উন্নয়নকারীর কাছে লগ ফাইল পাঠান
+ আপনার অ্যাকাউন্টে প্রবেশ করুন
+ অবস্থান পরিবর্তন হয়নি।
+ অবস্থান উপলব্ধ নয়।
+ কাছাকাছি স্থানসমূহের একটি তালিকা প্রদর্শন করতে অনুমতি প্রয়োজন
+ নির্দেশনা পান
+ নিবন্ধ পড়ুন
+ উইকিমিডিয়া কমন্সে স্বাগতম, %1$s! আপনাকে এখানে পেয়ে আমরা আনন্দিত।
+ %1$s আপনার আলাপ পাতায় একটি বার্তা দিয়েছেন
+ একটি সম্পাদনা করার জন্য আপনাকে ধন্যবাদ
+ %1$s আপনাকে %2$s-এ উল্লেখ করেছেন।
diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml
index e3ce21691..7258215aa 100644
--- a/app/src/main/res/values-br/strings.xml
+++ b/app/src/main/res/values-br/strings.xml
@@ -21,8 +21,8 @@
C\'hwitet en deus enporzhiañ %1$sPouezit evit diskwel
- %d restr o vezañ karget
- %d restr o vezañ karget
+ %1$d restr o vezañ karget
+ %1$d restr o vezañ kargetMa enporzhiadennoù nevezLakaet el lostenn
@@ -56,26 +56,26 @@
Enporzhiadenn ebet c\'hoazh !Pellgardenn ebet c\'hoazh
- %d bellgargadenn
- %d Pellgargadennoù
+ %1$d bellgargadenn
+ %1$d Pellgargadennoù
- %d bellgargadenn loc\'het
- %d pellgargadennoù loc\'het
+ %1$d bellgargadenn loc\'het
+ %1$d pellgargadennoù loc\'het
- %d bellgargadenn
- %d pellgargadennoù
+ %1$d bellgargadenn
+ %1$d pellgargadennoùN\'eus bet kavet rummad ebet o klotañ gant %1$s
- Ouzhpennit rummadoù evit ma vo aesoc\'h kavout ho skeudennoù war Wikimedia Commons.\n\nKrogit da ouzhpennañ rummadoù.\nPouezit war ar c\'hemenn-mañ (pe distroit en a-raok) evit mont dreist ar bazenn-mañ.
+ Ouzhpennit rummadoù evit ma vo aesoc\'h kavout ho skeudennoù war Wikimedia Commons.\n\nKrogit da skrivañ evit ouzhpennañ rummadoù.RummadoùArventennoùEn em enskrivañDiwar-bennMeziant frank a wirioù embannet dindan <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">an Aotre-implijout Apache v2</a>. Merkoù kenwerzhel eus Diazezadur Wikimedia eo Wikimedia Commons hag e logoioù. Gallout a reont bezañ implijet gant aotre an Diazezadur. N\'omp nag aprouet gant Diazezadur Wiikimedia na stag outañ.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Mammenn</a> ha <a href=\"https://commons-app.github.io/\">lec\'hienn</a> war GitHub. Krouiñ ur <a href=\"https://github.com/commons-app/apps-android-commons/issues\">gemennadenn GitHub nevez</a> evit kelaouiñ a-zivout un draen bennak pe bet kinnigoù.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Reolennoù prevezded</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Mammenn</a> ha <a href=\"https://commons-app.github.io/\">lec\'hienn</a> war GitHub. Krouiñ ur <a href=\"https://github.com/commons-app/apps-android-commons/issues\">gemennadenn GitHub nevez</a> evit kelaouiñ a-zivout un draen bennak pe bet kinnigoù.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy_policy\">Reolennoù prevezded</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Trugarekadennoù</a>Diwar-bennKas ho soñj (dre bostel)
@@ -160,6 +160,9 @@
Ne vez ket embreget an dilesadur gant daou faktor evit ar mare.Sur oc\'h e fell deoc\'h digevreañ ?Logo Commons
+ Lec\'hienn web Commons
+ Pajenn Facebook Commons
+ Kod mammenn Github CommonsN\'eus bet kavet skeudenn ebetKargañ ur skeudennMenez Zao
@@ -182,8 +185,16 @@
EvezhiadennoùDigevreañTutorial
+ KemennoùNe c\'haller ket diskwel al lec\'hioù tost ma ne rannit ket ho lec\'hiadurN\'eus bet kavet deskrivadur ebet
- Pennad Commons
+ Pajennad restroù CommonsElfenn Wikidata
+ Reiñ ar gwir
+ Implijout ar stokañ diavaez
+ Enrollit ar skeudennoù tennet gant luc\'hskeudennerez ho penveg
+ Kevreit ouzh ho kont
+ N\'eo ket cheñchet al lec\'hiadur.
+ Kaout urzhioù
+ Lenn ar pennad
diff --git a/app/src/main/res/values-bs/strings.xml b/app/src/main/res/values-bs/strings.xml
index 5ef4b1708..05bacb4de 100644
--- a/app/src/main/res/values-bs/strings.xml
+++ b/app/src/main/res/values-bs/strings.xml
@@ -21,8 +21,8 @@
Postavljanje datoteke %1$s nije uspjeloDodirnite da biste vidjeli
- postavlja se %d datoteka
- postavlja se %d datoteka
+ postavlja se %1$d datoteka
+ postavlja se %1$d datotekaMoje nedavno postavljene datotekeNa čekanju
@@ -54,27 +54,27 @@
GPS je onemogućen na Vašem uređaju. Želite li ga omogućiti?Omogući GPSZasad nema postavljenih datoteka
-
+ \@string/contributions_subtitle_zero
- postavljena %d datoteka
- postavljenih datoteka: %d
+ postavljena %1$d datoteka
+ postavljenih datoteka: %1$d
-
- Započinjem postavljanje %d datoteke
- Započinjem postavljanje %d datoteka/-e
+
+ Započinjem postavljanje %1$d datoteke
+ Započinjem postavljanje %1$d datoteka/-e
-
- %d postavljanje
- %d postavljanja
+
+ %1$d postavljanje
+ %1$d postavljanjaNema kategorija koje odgovoraju %1$s
- Dodajte kategorije da biste korisnicima olakšali pronalaženje slika na Wikimedia Commonsu.\n\nPočnite pisati da biste dodali kategoriju.\nDodirnite ovu poruku (ili pritisnite dugme \"nazad\") da biste preskočili ovaj korak.
+ Dodajte kategorije da biste korisnicima olakšali pronalaženje slika na Wikimedia Commonsu.\n\nPočnite pisati da biste dodali kategoriju.\nDodirnite ovu poruku (ili pritisnite dugme \"nazad\") da biste preskočili ovaj korak.KategorijePostavkeRegistracijaOProgram otvorenog kod objavljen pod licencom <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache v2</a>. Wikimedia Commons i njen logo zaštitni su znaci Zadužbine Wikimedia i koriste se s njenom dozvolom. Nismo povezani niti nas podržava Zadužbina Wikimedia.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Izvorni kod</a> i <a href=\"https://commons-app.github.io/\">veb-sajt</a> na GitHubu. Započnite novi <a href=\"https://github.com/commons-app/apps-android-commons/issues\">slučaj na GitHubu</a> da biste prijavili greške i dali prijedloge.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Izvorni kod</a> i <a href=\"https://commons-app.github.io/\">veb-sajt</a> na GitHubu. Započnite novi <a href=\"https://github.com/commons-app/apps-android-commons/issues\">slučaj na GitHubu</a> da biste prijavili greške i dali prijedloge.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Politika privatnosti</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Zasluge</a>O
@@ -120,7 +120,7 @@
NE postavljajte:– Vlastite slike ili slike svojih prijatelja\n– Slike koje ste preuzeli s interneta\n– Slike ekrana neslobodnih aplikacijaPrimjer postavljanja:
- – Naziv: Sidnejska opera\n– Opis: Pogled na Sidnejsku operu sa zaliva\n– Kategorije: Sidnejska opera, Sidnejska opera sa zapada, Sidnejska opera iz daleka
+ – Naziv: Sidnejska opera\n– Opis: Pogled na Sidnejsku operu sa zaliva\n– Kategorije: Sidnejska opera, Sidnejska opera sa zapada, Sidnejska opera iz dalekaPodijelite vaše slike. Pomozite člancima na Wikipediji da zažive!Slike na Wikipediji se uzimaju sa Wikimedia Commonsa.Vaše slike pomažu u obrazovanju ljudi širom svijeta.
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 7c3f933cb..e678dfc26 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -13,7 +13,7 @@
No s\'ha trobat el fitxer. Proveu-ho amb un altre fitxer.L\'autenticació ha fallat!Ha començat la càrrega!
- S\'ha carregat %1$s !
+ S’ha pujat %1$s.Prem per veure la teva càrregaComençant la càrrega al servidor de %1$sCarregant %1$s
@@ -22,9 +22,9 @@
Prem per veure-hos\'està carregant 1 fitxer
- s\'estan carregant %d fitxers
+ s\'estan carregant %1$d fitxers
- Les meves càrregues
+ Les meves càrregues recentsEn cuaHa fallat%1$d%% completat
@@ -42,6 +42,7 @@
No s’ha pogut iniciar la sessió. Comproveu la vostra contrasenyaMassa intents erronis – Proveu-ho de nou d\'aquí uns minuts.Ho sentim, aquest usuari ha estat blocat a Commons
+ Heu de proporcionar el vostre codi d\'autenticació de dos factors.Ha fallat l\'inici de sessióCarregaAnomena aquest conjunt
@@ -50,19 +51,21 @@
Categories de cercaDesaRefresca
+ El vostre dispositiu no té el GPS habilitat. El voleu habilitar?
+ Habilita el GPSEncara no hi ha cap càrrega
-
- No hi ha cap càrrega encara
- 1 càrrega
- %d càrregues
+
+ \@string/contributions_subtitle_zero
+ %1$d càrrega
+ %1$d càrregues
-
- S\'està iniciant 1 càrrega
- S\'estan iniciant %d càrregues
+
+ S\'està iniciant %1$d càrrega
+ S\'estan iniciant %1$d càrregues1 càrrega
- %d càrregues
+ %1$d càrreguesNo s\'ha trobat cap categoria que coincideixi amb %1$sAfegiu categories per fer que les vostres imatges siguin més fàcils de trobar a Wikimedia Commons.
@@ -70,11 +73,12 @@
ParàmetresRegistreQuant a
- Programari de codi obert distribuït sota la <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">llicència Apache v2</a>
+ L’aplicació de codi obert Wikimedia Commons fou creada per, i rep manteniment de, cessionaris i voluntaris de la comunitat de Wikimedia. La Fundació Wikimedia no està involucrada en la creació, el desenvolupament ni el manteniment de l’aplicació.Codi a <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>. Informes d\'error a <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a>.Wikimedia:Commons-android-texts-sobre privacitat/caQuant aEnvia comentaris (per correu)
+ No hi ha cap client de correu instal·latCategories usades recentmentS’està esperant la primera sincronització…Encara no heu carregat cap foto.
@@ -83,10 +87,13 @@
Aquesta imatge es llicenciarà sota %1$sBaixaLlicència
+ Utilitza títol i descripció anteriorsMode nocturnUtilitza el tema fosc
- CC Reconeixement-CompartirIgual 3.0
- CC Reconeixement 3.0
+ Reconeixement-CompartirIgual 4.0
+ Reconeixement 4.0
+ Reconeixement-CompartirIgual 3.0
+ Reconeixement 3.0CC0CC BY-SA 3.0CC BY-SA 3.0 (Àustria)
@@ -100,7 +107,10 @@
CC BY-SA 3.0 (Polònia)CC BY-SA 3.0 (Romania)CC BY 3.0
+ CC BY-SA 4.0
+ CC BY 4.0CC Zero
+ NO carregueu:Exemple de càrrega:Doneu les vostres imatges. Ajudeu a donar vida als articles de la Viquipèdia!Les imatges de la Viquipèdia vénen de la Wikimedia Commons.
@@ -116,21 +126,32 @@
RefrescaD\'acordLlocs propers
+ No s\'han trobat llocs propersAvís
+ El fitxer ja existeix a Commons. Segur que voleu procedir?SíNoTítolTítol del fitxer multimèdiaDescripcióData de càrrega
+ Llicència
+ Coordenades
+ No s\'ha proporcionat capUtilitza Wikidata
+ Codi 2FALímit màximRealment voleu finalitzar la sessió?Logo de Commons
+ Lloc web del Commons
+ Pàgina del Facebook del Commons
+ Codi font del Commons a GitHubNo s’ha trobat cap imatgeCarrega la imatgeLlamesImatge privativa
+ Benvinguts a la Viquipèdia
+ Casa d\'Òpera de SydneyCancel·laObreTanca
@@ -141,7 +162,21 @@
ParàmetresComentarisFinalitza la sessió
+ Tutorial
+ Notificacionsno s\'ha trobat cap descripció
- Article al Commons
+ Article del fitxer a CommonsElement del Wikidata
+ Error mentre es carregaven les fotografies
+ Dóna permís
+ Utilitza l’emmagatzematge extern
+ Envia el fitxer de registre
+ Envia el registre als desenvolupadors per correu
+ Entreu en el vostre compte
+ La ubicació no ha canviat.
+ La ubicació no és disponible.
+ COM ANAR-HI
+ LLEGIU L’ARTICLE
+ Gràcies per fer una modificació
+ %1$s us ha mencionat a %2$s.
diff --git a/app/src/main/res/values-ce/error.xml b/app/src/main/res/values-ce/error.xml
new file mode 100644
index 000000000..f8667a37d
--- /dev/null
+++ b/app/src/main/res/values-ce/error.xml
@@ -0,0 +1,4 @@
+
+
+ Баркалла!
+
diff --git a/app/src/main/res/values-cs/error.xml b/app/src/main/res/values-cs/error.xml
index 66ac4f378..050ae78d7 100644
--- a/app/src/main/res/values-cs/error.xml
+++ b/app/src/main/res/values-cs/error.xml
@@ -1,7 +1,7 @@
Commons spadly
- Ups. Něco je špatně!
+ Něco se pokazilo!Řekněte nám, co jste dělali a dejte nám to vědět e-mailem. Pomůže sjednat nápravu!Děkujeme vám!
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 5ac77fa54..77fdac33c 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -21,8 +21,8 @@
Načítání souboru %1$s se nezdařiloKlepnutím zobrazíte
- %d soubor se nahrává
- %d souborů se nahrává
+ %1$d soubor se nahrává
+ %1$d souborů se nahráváMoje nedávné obrázkyVe frontě
@@ -55,17 +55,17 @@
Spustit GPSŽádné nahrané soubory
- Žádné soubory
+ \@string/contributions_subtitle_zero1 soubor
- %d souborů
+ %1$d souborů
- Spouští se nahrávání %d souboru
- Spouští se nahrávání %d souborů
+ Spouští se nahrávání %1$d souboru
+ Spouští se nahrávání %1$d souborů
- %d nahrávání
- %d nahrávání
+ %1$d nahrávání
+ %1$d nahráváníŽádné kategorie neodpovídají „%1$s“Přidejte kategorie, aby bylo vaše obrázky možno na Wikimedia Commons najít.
@@ -73,9 +73,9 @@
NastaveníZaregistrovat seO aplikaci
- Open Source software dostupný za podmínek <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. %1$s a logo %1$s jsou ochranné známky Wikimedia Foundation a jsou použity se svolením Wikimedia Foundation. Nejsme podporováni Wikimedia Foundation ani nejsme její součástí.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Zdrojový kód</a>, <a href=\"https://commons-app.github.io/\">webová stránka</a> a <a href=\"https://github.com/commons-app/apps-android-commons/issues\">hlášení chyb a sdílení nápadů</a> na GitHubu.
- <a href=\"https://wikimediafoundation.org/wiki/Ochrana_osobních_údajů\">Ochrana osobních údajů</a>
+ Aplikace Wikimedia Commons je open source software vytvářený a spravovaný členy komunity Wikimedia. Nadace Wikimedia se neúčastní tvorby, vývoje či správy aplikace.
+ Otevřete nové <a href=\"https://github.com/commons-app/apps-android-commons/issues\">hlášení na GitHubu</a> pro nahlášení chyb a návrhů.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Ochrana osobních údajů</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Autoři</a>O aplikaciPoslat názory (e-mailem)
@@ -86,6 +86,7 @@
OpakovatZrušitTento obrázek bude zveřejněn pod licencí %1$s
+ Odesláním tohoto souboru potvrzuji, že je mým vlastním dílem, neobsahuje autorskoprávně chráněný materiál a nejedná se o selfie a ani jinak neporušuje <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">pravidla a doporučení Wikimedia Commons</a>.StáhnoutLicencePoužít předchozí název a popis
@@ -120,7 +121,7 @@
Prosím NENAHRÁVEJTE:- Selfie nebo obrázky vašich přátel\n- Obrázky stažené z Internetu\n- Screenshoty z proprietárních aplikacíPříklad nahraného souboru:
- - Název: Opera v Sydney\n- Popis: Opra v Sydney, pohled přes záliv\n- Kategorie: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Název: Opera v Sydney\n- Popis: Opera v Sydney, pohled přes záliv\n- Kategorie: Sydney Opera House from the west, Sydney Opera House remote viewsPřispějte svými obrázky. Pomozte oživit články na Wikipedii.Obrázky na Wikipedii pochází \nz Wikimedia Commons.Vaše obrázky pomáhají vzdělávat lidi po celém světě.
@@ -134,6 +135,7 @@
Neznámá licenceObnovitPožadováno oprávnění ke čtení externího úložiště. Aplikace bez toho nemůže pracovat.
+ Požadováno oprávnění k zápisu do externího úložiště. Aplikace bez toho nemůže pracovat.Volitelně: Umožněte aplikaci, aby získávala aktuální polohu a nabízela na jejím základě kategorieOKMísta v okolí
@@ -147,6 +149,9 @@
PopisSem patří popis média. Může být potenciálně velmi dlouhý, takže zabere několik řádků. Přesto doufáme, že to vypadá hezky.Datum nahrání souboru
+ Licence
+ Souřadnice
+ Žádné vložené souřadniceStaňte se beta testeryPřihlásit se do našeho beta kanálu na Google Play a dostávat včasný přístup k novinkám a opravám chybPoužít Wikidata
@@ -159,7 +164,11 @@
Tato aplikace zatím nepodporuje tzv. dvoufázové ověření.Opravdu se chcete odhlásit?Logo Wikimedia Commons
+ Stránka Commons
+ Facebooková stránka Commons
+ Zdrojový kód Commons na GitHubuObrázek na pozadí
+ ObrázekNebyl nalezen žádný obrázekNahrát obrázekHora Zao
@@ -182,8 +191,28 @@
Zpětná vazbaOdhlášeníPrůvodce
+ UpozorněníMísta poblíž nebude možné zobrazit, neuvedete-li svojí polohu.nebyl nalezen žádný popisekStránka souboru na CommonsPoložka Wikidat
+ Chyba při meziukládání obrázků
+ Unikátní a popisný název pro daný soubor, který bude sloužit jako název souboru. Můžete použít běžný psaný jazyk s mezerami; nezahrnujte koncovku souboru.
+ Popište prosím obrázek, jak jen to je možné: Kde byl pořízen? Co znázorňuje? Jaký je kontext obrázku? Popisujte prosím významné předměty nebo osoby na obrázku a nezapomeňte na informace, které není možné snadno odhadnout ze samotného obrázku, jako je například denní doba, pokud jde o krajinu. Pokud je na obrázku něco neobvyklého, popište, co to dělá neobvyklým.
+ Dát povolení
+ Použít externí úložiště
+ Uložit obrázky pořízené fotoaparátem, jenž je součástí této aplikace
+ Odeslat log
+ Odeslat log vývojářům e-mailem
+ Přihlásit se k účtu
+ Vaše umístění se nezměnilo.
+ Umístění není dostupné.
+ Vyžadováno povolení k zobrazení seznamu blízkých míst
+ NAVIGOVAT
+ PŘEČÍST ČLÁNEK
+ Vítejte na Wikimedia Commons, %1$s! Jsme rádi, že jste zde.
+ %2$s vám napsal na vaši diskusní stránku
+ Děkujeme za vaši editaci
+ %1$s vás zmínil na %2$s.
+ Přepnout pohled
diff --git a/app/src/main/res/values-csb/strings.xml b/app/src/main/res/values-csb/strings.xml
index 47f21d85d..f89303b2b 100644
--- a/app/src/main/res/values-csb/strings.xml
+++ b/app/src/main/res/values-csb/strings.xml
@@ -21,8 +21,8 @@
Wladënk %1$s nie darzëł sãTkni, abë òbôczëc
- Wladënk %d lopka
- Wladënk %d lopków
+ Wladënk %1$d lopka
+ Wladënk %1$d lopkówMòjé slédno wladowóné lopcziŻdające
@@ -54,18 +54,18 @@
GPS je w twòjim ùrządzeniém wëłączony. Chcesz gò włączëc?Wëłączë GPSNie dô jesz wladënków
-
+ Przesłano @string/contributions_subtitle_zero
- Przesłano %d lopk
- Przesłano %d lopczi
+ Przesłano %1$d lopk
+ Przesłano %1$d lopczi
-
- Naczãto %d wladënk
- Naczãto %d wladënczi
+
+ Naczãto %1$d wladënk
+ Naczãto %1$d wladënczi
-
- %d wladënk
- %d wladënczi
+
+ %1$d wladënk
+ %1$d wladëncziFelënk pasowny kategòrëji do %1$sKategòrëje
diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml
index b107125f7..27480ee3d 100644
--- a/app/src/main/res/values-cy/strings.xml
+++ b/app/src/main/res/values-cy/strings.xml
@@ -22,7 +22,7 @@
Tapiwch i weld1 ffeil yn uwchlwytho
- %d ffeil yn uwchlwytho
+ %1$d ffeil yn uwchlwythoFy uwchlwythiadauYn y ciw
@@ -52,24 +52,24 @@
Dim Uwchlwythiadau eto1 uwchlwythiad
- %d uwchlwythiad
+ %1$d uwchlwythiadCychwyn uwchlwytho 1 ffeil
- Cychwyn uwchlwytho %d ffeil
+ Cychwyn uwchlwytho %1$d ffeil1 uwchlwythiad
- %d uwchlwythiad
+ %1$d uwchlwythiadDim categori\'n cyfateb i %1$s ar gael
- Cychwynwch deipio i ychwanegu categoriau.\nCyffyrddwch y neges hon (neu ddewis Nôl) i fynd i\'r cam nesaf.
+ Cychwynwch deipio i ychwanegu categoriau.\nCyffyrddwch y neges hon (neu ddewis Nôl) i fynd i\'r cam nesaf.CategorïauGosodiadauCofrestruAmdanomMeddalwedd Cynnwys Agored wedi\'i rhyddhau o dan <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Drwydded Apache v2</a> Mae logo a nod masnach Comin Wicimedia yn eiddo i Sefydliad Wicimedia ac fe\'u defnyddir gyda chaniatad y Sefydliad hwnnw. Nid ydym ni wedi cael sêl bendith y sefydliad nac yn perthyn iddyn nhw.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Mae\'r codau ffynhonnell ar wefanau</a> Comin a <a href=\"https://commons-app.github.io/\">gwefan </a> GitHub. Gellwch greu <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Ymholiad Github</a> os gwelwch byg, neu i awgrymu gwelliannau.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Mae\'r codau ffynhonnell ar wefanau</a> Comin a <a href=\"https://commons-app.github.io/\">gwefan </a> GitHub. Gellwch greu <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Ymholiad Github</a> os gwelwch byg, neu i awgrymu gwelliannau.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Polisi preifatrwydd</a>AmdanomDanfonwch Adborth (drwy Ebost)
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index c379283ab..5437d0008 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -21,8 +21,8 @@
Overførsel af %1$s mislykkedesKlik for at se
- %d fil overføres
- %d filer overføres
+ %1$d fil overføres
+ %1$d filer overføresMine seneste overførslerI kø
@@ -56,26 +56,26 @@
Ingen overførelser endnu\@string/contributions_subtitle_zero
- %d overførsel
- %d overførelser
+ %1$d overførsel
+ %1$d overførsler
- Starter %d overførsel
- Starter %d overførsler
+ Starter %1$d overførsel
+ Starter %1$d overførsler
- %d overførsel
- %d overførsler
+ %1$d overførsel
+ %1$d overførslerIngen kategorier matchende %1$s er fundet
- Tilføj kategorier for at gøre billederne mere synlig på Wikimedia Commons.
+ Tilføj kategorier for at gøre billederne mere synlig på Wikimedia Commons. Tast for at tilføje kategorier.KategorierIndstillingerOpret kontoOm
- Open Source-programmer udgivet under <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. %1$s og dets logo er varemærker for Wikimedia Foundation og bruges med tilladelse fra Wikimedia Foundation. Vi er hverken støttet af eller forbundet med Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Kilde</a> og <a href=\"https://commons-app.github.io/\">hjemmeside</a> på GitHub. Opret en ny <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-sag</a> for fejlrapporter og forslag.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Privacy policy</a>
+ Wikimedia Commons-programmet oprettet og udviklet af frivillige i Wikimediafællesskabet. Wikimedia Foundation er ikke involveret i oprettelse, udvikling eller vedligeholdelse af programmet.
+ Opret en ny <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-sag</a> for fejlrapporter og forslag.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Privatlivspolitik</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Bidragsydere</a>OmSend tilbagemelding (med e-mail)
@@ -121,7 +121,7 @@
Overfør venligst IKKE:- Selvportrætter eller billeder af dine venner\n- Billeder du har hentet fra internettet\n- Skærmbilleder af dine proprietære programmerEksempel på overførsel:
- - Titel: Sydneys Operahus\n- Beskrivelse: Operahuset i Sydney set fra anden siden af bugten\n- Kategorier: Operahuset i Sydney, Operahuset i Sydney fra vest, Operahuset i Sydney udefra
+ - Titel: Sydneys Operahus\n- Beskrivelse: Operahuset i Sydney set fra den anden siden af\n bugten\n- Kategorier: Operahuset i Sydney fra vest, Operahuset i Sydney\n udefraBidrag med dine billeder. Hjælp Wikipedias artikler med at komme til live!Billeder på Wikipedia kommer fra Wikimedia Commons.Dine billeder hjælper med til at uddanne folk rundt om i verden.
@@ -164,6 +164,9 @@
Tofaktorgodkendelse er ikke understøttet i øjeblikket.Ønsker du at logge ud?Commons-logo
+ Commons-hjemmeside
+ Commons Facebook-side
+ Commons Github-kildekodeBaggrundsbilledeMediebillede mislykkedesIntet billede fundet
@@ -188,6 +191,7 @@
TilbagemeldingLog afØvelse
+ PåmindelserSteder i nærheden kan ikke vises uden placeringsrettighederingen beskrivelse fundetCommons-filside
@@ -195,6 +199,20 @@
Fejl under mellemlagring af billederEn unik beskrivelse for filen, som vil fungere som et filnavn. Du kan bruge normalt sprog med mellemrum. Udelad filendelsen.Beskriv mediet så godt som muligt: Hvor blev det taget? Hvad viser det? Hvad er konteksten? Beskriv objekterne eller personerne. Giv information som ikke nemt kan gættes, for eksempel hvornår på dagen billedet blev taget, om det er et landskabsbillede. Om billedet viser noget usædvanligt, forklar hvad som gør det usædvanlig.
+ Giv tilladelseBrug eksternt lagerGem billeder taget med din enheds program på kameraet
+ Send logfil
+ Send logfil til udviklerne via e-post
+ Log ind på din konto
+ Sted er ikke ændret.
+ Sted ikke tilgængeligt.
+ Tilladelse kræves for at vise en liste over steder i nærheden
+ FÅ RUTEHJÆLP
+ LÆS ARTIKEL
+ Velkommen til Wikimedia Commons, %1$s! Vi er glad for, at du er her.
+ %1$s efterlod en besked på din diskussionsside
+ Tak fordi du lavede en redigering
+ %1$s nævnte dig på %2$s.
+ Skift visning
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 4ddc24ad7..58e0b272a 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -22,7 +22,7 @@
Tippen zum BetrachtenEine Datei wird hochgeladen
- %d Dateien werden hochgeladen
+ %1$d Dateien werden hochgeladenMeine kürzlich hochgeladenen DateienIn der Warteschlange
@@ -57,24 +57,24 @@
\@string/contributions_subtitle_zeroEine hochgeladene Datei
- %d hochgeladene Dateien
+ %1$d hochgeladene DateienStarte einen Upload
- Starte %d Uploads
+ Starte %1$d UploadsEine hochgeladene Datei
- %d hochgeladene Dateien
+ %1$d hochgeladene DateienDie Kategorie „%1$s“ wurde nicht gefunden
- Füge Kategorien hinzu, um deine Bilder auf Wikimedia Commons auffindbarer zu machen.\n\nBeginn zu schreiben, um Kategorien hinzuzufügen.\nTippe auf diese Nachricht oder drücke „Zurück“, um diesen Schritt zu überspringen.
+ Füge Kategorien hinzu, um deine Bilder auf Wikimedia Commons auffindbarer zu machen.\nBeginne mit der Eingabe, um Kategorien hinzuzufügen.KategorienEinstellungenRegistrierenÜberDie Wikimedia-Commons-App ist eine Open-Source-App, entwickelt und gewartet von Freiwilligen der Wikimedia-Gemeinschaft. Die Wikimedia Foundation ist nicht bei der Erstellung, Entwicklung oder Wartung der App beteiligt.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Quellcode</a> und <a href=\"https://commons-app.github.io/\">Website</a> auf GitHub. Einen neuen <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-Eintrag</a> für Fehlerberichte und Vorschläge erstellen.
+ Einen neuen <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-Eintrag</a> für Fehlerberichte und Vorschläge erstellen.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Datenschutzrichtlinie</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Danksagungen</a>Über
@@ -121,7 +121,7 @@
Bitte NICHT hochladen:- Selfies oder Bilder deiner Freunde\n- Bilder, die du aus dem Internet heruntergeladen hast\n- Screenshots von proprietären SoftwareanwendungenBeispiel-Upload:
- - Titel: Opernhaus von Sydney\n- Beschreibung: Opernhaus von Sydney, von der Bucht aus gesehen\n- Kategorien: Opernhaus von Sydney, Opernhaus von Sydney von Westen, Opernhaus von Sydney (Fernansicht)
+ - Titel: Opernhaus von Sydney\n- Beschreibung: Opernhaus von Sydney, von der Bucht aus gesehen\n- Kategorien: Opernhaus von Sydney von Westen, Opernhaus von Sydney (Fernansicht)Teile deine Bilder. Erwecke Wikipedia-Artikel zum Leben!Die Bilder auf Wikipedia kommen von Wikimedia Commons.Deine Bilder helfen dabei, Menschen auf der ganzen Welt zu bilden.
@@ -164,6 +164,9 @@
Die Zwei-Faktor-Authentifikation wird derzeit nicht unterstützt.Möchtest du dich wirklich abmelden?Commons-Logo
+ Commons-Website
+ Commons-Facebookseite
+ Commons-Quellcode auf GithubHintergrundbildMedienbild fehlgeschlagenKein Bild gefunden
@@ -188,6 +191,7 @@
RückmeldungenAbmeldenAnleitung
+ BenachrichtigungenOrte in der Nähe können ohne Berechtigung zur Standortbestimmung nicht ermittelt werdenKeine Beschreibung gefundenCommons-Dateiseite
@@ -198,4 +202,17 @@
Berechtigung gebenExternen Speicher verwendenMit der In-App-Kamera aufgenommene Bilder auf deinem Gerät speichern
+ Logdatei senden
+ Logdatei an die Entwickler per E-Mail senden
+ Bei deinem Benutzerkonto anmelden
+ Der Standort hat sich nicht geändert.
+ Der Standort ist nicht verfügbar.
+ Berechtigung zur Anzeige einer Liste mit Orten in der Nähe erforderlich
+ RICHTUNGEN ABRUFEN
+ ARTIKEL LESEN
+ Willkommen auf Wikimedia Commons, %1$s! Wir sind froh, dass du hier bist.
+ %1$s hinterließ eine Nachricht auf deiner Diskussionsseite
+ Vielen Dank für deine Bearbeitung
+ %1$s erwähnte dich auf %2$s.
+ Ansicht wechseln
diff --git a/app/src/main/res/values-diq/strings.xml b/app/src/main/res/values-diq/strings.xml
index fd19f8cad..8ab93b5ed 100644
--- a/app/src/main/res/values-diq/strings.xml
+++ b/app/src/main/res/values-diq/strings.xml
@@ -22,9 +22,9 @@
pıpawane xo bıvin1 dosye selagnayış
- %d dosye Selagnayışi
+ %1$d dosye Selagnayışi
- Barkerdışê mı
+ Barkerdışê mınê peyêniRatneyaNêbı%1$d%% temamya
@@ -52,19 +52,19 @@
Theba bar nêbı1 bar kerdış
- %d bar kerdışi
+ %1$d bar kerdışiSergendış 1 bar kerdış
- Sergendış %d bar kerdışi
+ Sergendış %1$d bar kerdışi 1 bar kerdış
- %d bar kerdışi
+ %1$d bar kerdışi Kategoriyan de %1$s deye theba çıniyo
- Kategori cı kerdışi rê nusnayış bıkeré. Ena gamer ravêrdışi rê mesaci bıpıloğnê yana peyd şırê.
- Kategoriy
+ Kategori cı kerdışi rê nusnayış bıkeré. Ena gamer ravêrdışi rê mesaci bıpıloğnê yana peyd şırê.
+ KategoriyiSaziQeyd beHeq te cı
@@ -100,7 +100,7 @@
Misal bar kerden:Şıma fam kerdê?E!
- Kategoriy
+ KategoriyiBar beno…Theba nêweçineyaAkerdenayış çıniyo
@@ -118,4 +118,5 @@
AkeKeyeBar ke
+ Veciyayış
diff --git a/app/src/main/res/values-dty/strings.xml b/app/src/main/res/values-dty/strings.xml
index 8f8d9c06b..09c0426ab 100644
--- a/app/src/main/res/values-dty/strings.xml
+++ b/app/src/main/res/values-dty/strings.xml
@@ -28,7 +28,6 @@
लगइन अद्दाइ असमर्थ - कृपया तमरो प्रयोगकर्ता नाउँ हेरऽलगइन अद्दाइ असमर्थ - कृपया तमरो पासवर्ड जाँचऽश्रेणीइन
- उपयोग प्रतिवेदनअनमिलानअनबारेमीबारेमी
@@ -54,4 +53,5 @@
मिडियाऽ शीर्षकबेलिविस्तारबिटा परीक्षक बनऽ
+ स्थान उपलब्ध आथिन।
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 2fe7f7b89..bdaafb27b 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -20,11 +20,11 @@
Αποπεράτωση επιφόρτωσης %1$sΑνέβασμα του %1$s απέτυχεΠατήστε για να δείτε
-
+ 1 αρχείο επιφορτώνεται
- %d αρχεία επιφορτώνονται
+ %1$d αρχεία επιφορτώνονται
- Οι επιφορτώσεις μου
+ Οι Πρόσφατες Φορτώσεις ΜουΣτην ουράΑπέτυχε%1$d%% ολοκληρώθηκε
@@ -42,6 +42,7 @@
Δεν είναι δυνατή η σύνδεση - παρακαλούμε ελέγξτε τον κωδικό σαςΠάρα πολλές ανεπιτυχείς προσπάθειες. Παρακαλώ δοκιμάστε ξανά σε λίγα λεπτά.Συγνώμη, αυτός ο χρήστης έχει αποκλειστεί από τα Commons
+ Πρέπει να δώσετε τον κωδικό πιστοποίησης με δύο παράγοντεςΗ είσοδος απέτυχεΑνέβασμαΟνομάστε το σύνολο
@@ -50,18 +51,21 @@
Αναζήτηση κατηγοριώνΑποθήκευσηΑνανέωση
-
+ Το GPS στην συσκευή είναι απενεργοποιημένο. Θέλετε να το ενεργοποιήσετε;
+ Ενεργοποιήσετε το GPS
+ Δεν υπάρχουν ακόμα φορτωμένα αρχεία
+ Δεν υπάρχουν επιφορτώσεις ακόμη
- 1 επιφόρτωση
- %d επιφορτώσεις
+ %1$d επιφόρτωση
+ %1$d επιφορτώσεις
-
- Έναρξη 1 επιφόρτωσης
- Έναρτξη %d επιφορτώσεων
+
+ Έναρξη %1$d επιφόρτωσης
+ Έναρξη %1$d επιφορτώσεων
-
- 1 επιφόρτωση
- %d επιφορτώσεις
+
+ %1$d επιφόρτωση
+ %1$d επιφορτώσειςΔεν βρέθηκαν κατηγορίες που ταιριάζουν %1$sΠροσθέστε κατηγορίες για να κάνετε τις εικόνες σας πιο ανιχνεύσιμες στα Wikimedia Commons.\n\nΑρχίστε να γράφετε για να προσθέσετε κατηγορίες.\nΠατήστε αυτό το μήνυμα (ή πατήστε επιστροφή) για να παραλείψετε αυτό το βήμα.
@@ -69,10 +73,10 @@
ΡυθμίσειςΕγγραφήΣχετικά
- Λογισμικό ανοικτού κώδικα και κυκλοφορεί υπό την <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Άδεια Apache v2</a>. Το Wikimedia Commons και το λογότυπο είναι εμπορικά σήματα του Ιδρύματος Wikimedia και χρησιμοποιούνται με άδεια από το Ίδρυμα Wikimedia. Δεν προτεινόμαστε ή συνδεόμαστε με το Ίδρυμα Wikimedia.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Πηγή</a> και <a href=\"https://commons-app.github.io/\">ιστοσελίδα</a> στο GitHub. Δημιουργήστε ένα νέο <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub θέμα</a> για αναφορές σφαλμάτων και προτάσεις.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Πολιτική προσωπικών δεδομένων</a>
- <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">CREDITS</a>
+ Λογισμικό ανοικτού κωδικού που κυκλοφορεί υπό την <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Άδεια Apache v2</a>. Το Wikimedia Commons και το λογότυπο είναι εμπορικά σήματα του Ιδρύματος Wikimedia και χρησιμοποιούνται με άδεια από το Ίδρυμα Wikimedia. Δεν συμμετέχουμε στην δημιουργία, ανάπτυξη ή συντήρηση του Ιδρύματος Wikimedia.
+ Δημιουργήστε ένα νέο <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub θέμα</a> για αναφορές σφαλμάτων και προτάσεις.
+ <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Πολιτική Απορρήτου και προσωπικών δεδομένων</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">CREDITS</a>ΣχετικάΑποστολή σχολίων (μέσω Email)Δεν υπάρχει εγκατεστημένη εφαρμογή ηλεκτρονικού ταχυδρομείου
@@ -82,6 +86,7 @@
ΞαναπροσπαθήστεΑκύρωσηΑυτή η εικόνα θα έχει άδεια στα πλαίσια του %1$s
+ Αποστέλλοντας αυτήν την εικόνα, δηλώνω πως αυτή η εργασία είναι δική μου, και δεν περιέχει υλικό άλλου συγγραφέα, και εκτός αυτού πρόσκειται στο , thathref=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Κοινές πολιτικές της Wikipedia</a>.ΛήψηΆδεια χρήσηςΧρήση προηγούμενου τίτλου/περιγραφής
@@ -124,15 +129,17 @@
Νομίζεις ότι έχεις;Ναι!Κατηγορίες
- Φόρτωση…
+ Φόρτωση…Καμία επιλεγμένηΚαμία περιγραφήΆγνωστη άδειαΑνανέωσηΑπαιτούμενη άδεια: Ανάγνωση εξωτερικής αποθήκευσης. Η εφαρμογή δεν μπορεί να λειτουργήσει χωρίς αυτή.
+ Απαιτούμενη άδεια: Με εξωτερική αποθήκευση.Το πρόγραμμα δεν μπορεί να λειτουργήσει με αυτήν.Προαιρετική άδεια: Ανάκτηση τρέχουσας θέσης σας για προτάσεις κατηγοριώνΕντάξειΚοντινοί Τόποι
+ Δεν βρέθηκαν τόποι εδώ κοντάΠροειδοποίησηΑυτό το αρχείο υπάρχει ήδη στα Commons. Είστε σίγουρος ότι θέλετε να συνεχίσετε;Ναι
@@ -141,8 +148,71 @@
Τίτλος πολυμέσουΠεριγραφήΗ περιγραφή του πολυμέσου μπαίνει εδώ. Αυτή μπορεί να είναι σχετικά μεγάλη, και θα χρειαστεί να αναδιπλωθεί σε πολλές γραμμές. Ελπίζουμε ωστόσο ότι θα φαίνεται όμορφα.
+ Ημερομηνία φόρτωσης
+ Άδεια
+ Συντεταγμένες
+ Δεν δόθηκε τίποταΓίνετε Δοκιμαστής BetaΣυμμετέχετε στο κανάλι beta μας στο Google Play και αποκτήστε πρώιμη πρόσβαση σε νέες λειτουργίες και διορθώσεις σφαλμάτωνχρήση wikidata(Προσοχή: η απενεργοποίηση αυτή μπορεί να προκαλέσει μεγάλη κατανάλωση κινητὠν δεδομένων)
+ Κωδικός 2FA
+ To Όριο του Πρόσφατα Φορτωμένου Αρχείου μου
+ Μέγιστο Όριο
+ Δεν μπορεί να προβάλλει περισσότερα από 500 αρχεία
+ Ορίσετε το Όριο της Πρόσφατης Επιφόρτωσης
+ Πιστοποίηση με δύο παράγοντες δεν υποστηρίζεται προς το παρόν.
+ Θέλετε πράγματι να αποσυνδεθείτε;
+ Κοινό Λογότυπο
+ Ιστοσελίδα των Κοινών
+ Σελίδα των Κοινών στο Facebook
+ Κώδικας πηγής των Κοινών στο GitHub
+ Εικόνα Υποβάθρου
+ Η Εικόνα των Μέσων Απέτυχε (δεν μπορεί να φορτωθεί)
+ Δεν Βρέθηκε καμία Εικόνα
+ Φορτώστε την Εικόνα
+ Mount Zao
+ Llamas
+ Γέφυρα Ουρανίου Τόξου
+ Τουλίπα
+ Όχι selfies
+ Ιδιόκτητη Εικόνα
+ Καλωσόρισες Βικιπαίδεια
+ Καλωσορίστε το Δικαίωμα Αντιγραφής
+ Κτίρια Όπερας Sidney
+ Ακυρώστε
+ Ανοίξετε
+ Κλείσετε
+ Αρχική Σελίδα
+ Φορτώστε
+ Εδώ Κοντά
+ Γύρω από
+ Ρυθμίσεις
+ Σχόλια
+ Αποσύνδεση
+ Σεμινάριο
+ Ενημερώσεις
+ Οι κοντινές τοποθεσίες δεν μπορούν να προβληθούν δίχως τις άδειες τοποθεσίας
+ δεν βρέθηκε περιγραφή
+ Σελίδα φακέλλου κοινής χρήσης
+ Τεμάχιο Wikidata
+ Υπήρξε σφάλμα κατά την σκίαση εικόνων
+ Ένας μοναδικός τίτλος περιγραφής του φακέλλου, που θα χρησιμεύσει ως όνομα φακέλλου. Μπορείτε να χρησιμοποιήσετε τις ήδη υπάρχουσες γλώσσες με διαστήματα. Μην συμπεριλάβετε την επέκταση φακέλλου
+ \nΠαρακαλώ περιγράψετε τα μέσα το δυνατό περισσότερο : Πού οδηγήθηκε αυτό; Τι δείχνει; Ποιο είναι το περιεχόμενο του; Παρακαλώ περιγράψετε τα αντικείμενα ή τα πρόσωπα. Αποκαλύψετε πληροφορίες που δεν μπορούν εύκολο να μαντέψει κανείς, για παράδειγμα την ώρα εντός της ημέρας αν πρόκειται για τοπίο. Αν τα μέσα δείξουν κάτι ασύνηθες, παρακαλώ εξηγήστε τι το καθιστά μη συνηθισμένα.
+ Χορηγήστε άδεια
+ Χρησιμοποιήσετε την εξωτερική αποθήκευση
+ Αποθηκεύσετε εικόνες που παίρνονται στην κάμερα εφαρμογής στην συσκευή σας
+ Αποστείλατε τον φάκελλο σύνδεσης
+ Στείλατε τον φάκελλο σύνδεσης στους δημιουργούς μέσω email
+ Συνδεθείτε στο λογαριασμό σας.
+ Ο εντοπισμός δεν έχει αλλάξει.
+ Ο τόπος δεν είναι διαθέσιμος.
+ Απαιτείται άδεια για την εμφάνιση λίστας κοντινών σημείων
+ Λάβετε κατευθύνσεις
+ Ανάγνωση άρθρου
+ Καλωσήρθατε στο Wikimedia Commons, %1$s! Είμαστε χαρούμενοι που είστε εδώ.
+ Ο %1$s άφησε ένα μήνυμα στην σελίδα συζήτησής σας
+ Ευχαριστούμε που κάνατε μια επεξεργασία
+ Ο %1$s σας ανέφερε στο %2$s.
+ Μεταβολή προβολής
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 5a927f8dc..0a2c423ae 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -12,8 +12,8 @@
Acceso fallido.No se encontró el archivo. Prueba con otro.Falló la autenticación.
- ¡Empenzando a subir!
- ¡Se subieron %1$s!
+ Ha comenzado la carga.
+ Se ha cargado %1$s.Pulsa para ver tu subidaEmpezando la subida de %1$sCargando %1$s
@@ -21,8 +21,8 @@
Falló la carga de %1$sToca para ver
- Subiendo %d archivo
- Subiendo %d archivos
+ Cargando %1$d archivo
+ Cargando %1$d archivosMis subidas recientesEn la cola
@@ -56,26 +56,26 @@
No hay subidas aún\@string/contributions_subtitle_zero
- %d subida
- %d subidas
+ %1$d carga
+ %1$d cargas
- Iniciando 1 subida
- Iniciando %d subidas
+ Iniciando %1$d carga
+ Iniciando %1$d cargas
- %d subida
- %d subidas
+ %1$d carga
+ %1$d cargasNo se encontraron categorías que coincidieran con %1$s
- Añade categorías para hacer que tus imágenes sean más fáciles de encontrar en Wikimedia Commons.\n\nComienza a escribir para añadir categorías.\nToca este mensaje (o pulsa Anterior) para omitir este paso.
+ Añade categorías para hacer que tus imágenes sean más fáciles de encontrar en Wikimedia Commons.\nComienza a escribir para añadir categorías.CategoríasAjustesRegístrateAcerca de
- Programa de código abierto publicado bajo la <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Licencia Apache ver. 2</a>. %1$s y su logotipo son marcas registradas de la Fundación Wikimedia y se utilizan con su permiso. No hemos sido acreditados por la Fundación Wikimedia ni estamos afiliados con ella.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Código fuente</a> y <a href=\"https://commons-app.github.io/\">sitio web</a> en GitHub. Crea <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencias</a> para informar de problemas y sugerencias.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Normativa de privacidad</a>
+ La aplicación de código abierto Wikimedia Commons fue creada por, y recibe mantenimiento de, cesionarios y voluntarios de la comunidad de Wikimedia. La Fundación Wikimedia no está involucrada en la creación, el desarrollo ni el mantenimiento de la aplicación.
+ Notifica de problemas y sugerencias en <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub</a>.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Normativa de privacidad</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Créditos</a>Acerca deEnviar comentarios (por correo)
@@ -121,7 +121,7 @@
NO cargues:- Autorretratos o fotos de tus amigos\n- Imágenes que hayas descargado de Internet\n- Capturas de pantalla de aplicaciones privativasEjemplo de carga:
- - Título: Casa de la Ópera de Sídney\n- Descripción: Casa de la Ópera de Sídney vista desde el otro lado de la bahía\n- Categorías: Casa de la Ópera de Sídney, Casa de la Ópera de Sídney desde el oeste, Vistas a distancia de la Casa de la Ópera de Sídney
+ - Título: Casa de la Ópera de Sídney\n- Descripción: Casa de la Ópera de Sídney vista desde el otro lado de la bahía\n- Categorías: Casa de la Ópera de Sídney desde el oeste, Vistas a distancia de la Casa de la Ópera de SídneyContribuye con tus imágenes.\n¡Ayuda a que los artículos de Wikipedia tengan vida!Las imágenes en Wikipedia proceden de\nWikimedia Commons.Tus imágenes ayudan a educar a la gente\nalrededor del mundo.
@@ -147,13 +147,13 @@
TítuloTítulo del multimediaDescripción
- Aquí va la descripción del multimedia. Potencialmente, puede ser bastante largo, y deberá agruparse en múltiples líneas. De todas formas, esperamos que se ve bien.
+ Aquí va la descripción del archivo multimedia. Esta puede ser muy extensa, en cuyo caso deberá ajustarse en varios renglones. No obstante, esperamos que se vea bien.Fecha de subidaLicenciaCoordenadasNo se proporcionaronPrueba la versión beta
- Opta por nuestro canal beta en Google Play y obtén acceso a funcionalidades nuevas y correcciones de errores
+ Apúntate a nuestro canal beta en Google Play y obtén acceso a funcionalidades nuevas y correcciones de erroresUtilizar Wikidata(Advertencia: desactivar esto puede ocasionar un gran consumo de datos del móvil)Código de autenticación de 2 pasos
@@ -164,6 +164,9 @@
Por el momento no se admite la autenticación de dos factores.¿Confirmas que quieres salir?Logo de Commons
+ Sitio web de Commons
+ Página de Facebook de Commons
+ Código fuente de Commons en GitHubImagen de fondoFalló la imagen de multimediaNo se encontró ninguna imagen
@@ -188,13 +191,27 @@
ComentariosSalirTutorial
+ NotificacionesLos sitios cercanos no pueden mostrarse sin los permisos de ubicaciónno se encontró ninguna descripciónPágina del archivo en CommonsElemento de Wikidata
- Error mientras se guardaban imágenes en la caché
+ Error al almacenar imágenes en la antememoriaUn título único descriptivo para el archivo, que servirá como un nombre de archivo. Puede usar un lenguaje claro con espacios. No incluya la extensión del archivo.
- Por favor, describa el elemento multimedia tanto como sea posible: ¿dónde fue tomado?, ¿qué muestra?, ¿cuál es el contexto? Por favor, describa los objetos o personas. Ofrezca la información que no puede ser inferida tan facilmente, por ejemplo el momento del día si es un paisaje. Si el medio muestra algo inusual, explique qué lo hace insual.
+ Por favor, describa el elemento multimedia tanto como sea posible: ¿dónde fue tomado?, ¿qué muestra?, ¿cuál es el contexto? Por favor, describa los objetos o personas. Ofrezca la información que no puede ser inferida tan fácilmente, por ejemplo el momento del día si es un paisaje. Si el medio muestra algo inusual, explique qué lo hace insual.
+ Otorgar permisoUtilizar almacenamiento externoGuardar en el dispositivo imágenes capturadas con la cámara de la aplicación
+ Enviar archivo de registro
+ Enviar archivo de registro a los desarrolladores por correo electrónico
+ Accede a tu cuenta
+ La ubicación no ha cambiado.
+ La ubicación no está disponible.
+ Se necesita permiso para mostrar una lista de lugares cercanos
+ CÓMO LLEGAR
+ LEER ARTÍCULO
+ ¡Te damos la bienvenida a Wikimedia Commons, %1$s! Qué bueno que estés aquí.
+ %1$s dejó un mensaje en tu página de discusión
+ Gracias por realizar una edición
+ %1$s te ha mencionado en %2$s.
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index 2503b80c3..a340d7d72 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -51,16 +51,16 @@
Oraindik igoerarik ezigoera 1
- %d igoera
+ %1$d igoeraEz da kategoriak aukritu %1$s izenarekin
- Gehitu kategoriak zure argazkiak Wikimedia Commonsen aurkitzen errazagoak izan daitezen.
+ Gehitu kategoriak zure argazkiak Wikimedia Commonsen aurkitzen errazagoak izan daitezen.KategoriakHobespenakEman izenaHoni buruzOpen Source softwarea <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache v2 Lizentziaren</a> pean egina. Wikimedia Commons eta bere logoa Wikimedia Fundazioaren marka erregistratuak dira eta Wikimedia Fundazioaren baimenarekin erabiltzen dira. Ez gaude Wikimedia Fundaziora afiliatuta.
- GitHub-eko <a href=\"https://github.com/commons-app/apps-android-commons\">Iturria</a> eta <a href=\"https://commons-app.github.io/\">webgunea</a>. <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-eko gai</a> berria sortu erroreen berri emateko.
+ GitHub-eko <a href=\"https://github.com/commons-app/apps-android-commons\">Iturria</a> eta <a href=\"https://commons-app.github.io/\">webgunea</a>. <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-eko gai</a> berria sortu erroreen berri emateko.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Pribatutasun politika</a>Honi buruzBidali zure iritzia (e-posta bidez)
@@ -124,6 +124,7 @@
Beta testatzaile bihurtuWikidata erabili(Erne:hau kentzeak mugikorrak datu asko kontsumitzea ekar dezake)
+ 2FA KodeaGehienezko muga500 baino gehiago ezin dira erakutsiCommonsen logoa
@@ -155,4 +156,8 @@
Artxibo orrialde komunaWikidata itemaArgazkiak hartzerakoan sortutako akatsa
+ Baimena eman
+ Zure kontuan saioa hasi
+ NORABIDEAK JASO
+ IRAKURRI ARTIKULUA
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 0dd8b1673..68d522865 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -21,8 +21,8 @@
%1$s بارگذاری نشدبرای دیدن انگشت بزنید
- %d پرونده در حال بارگذاری
- %d پرونده در حال بارگذاری
+ %1$d پرونده در حال بارگذاری
+ %1$d پرونده در حال بارگذاریبارگذاریهای اخیر مندر صف
@@ -54,28 +54,24 @@
مکانیاب در دستگاه شما خاموش است. آیا دوست دارید فعال شود؟فعال کردن مکانیابهنوز هیچ بارگذاری
-
- \@string/contributions_subtitle_zero
- بارگذاری شد
- %d بارگذاری شد
-
+ {{%1$d|zero=@string/contributions_subtitle_zero|one=بارگذاری شد|%1$d بارگذاری شد}}
- شروع %d بارگذاری پرونده
- شروع بارگذاری %d پرونده
+ شروع %1$d بارگذاری پرونده
+ شروع بارگذاری %1$d پرونده
- %d بارگذاری
- %d بارگذاری
+ %1$d بارگذاری
+ %1$d بارگذاریردهای منطبق با %1$s یافت نشد
- برای دسترسی آسانتر در ویکیانبار به تصویرهایتان رده بیافزائید.\n\nشروع به افزودن رده.\nاین پیام (یا دکمه بازگشت) را لمس کنید تا این مرحله پایان پذیرد.
+ برای دسترسی آسانتر در ویکیانبار به تصویرهایتان رده بیافزائید.\n\nشروع به افزودن رده.ردههاتنظیماتثبت نامدرباره
- نرمافزار متنباز آزاد تحت <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">مجوز آپاچی نسخهٔ ۲</a>\n\n%1$s و نشانش یک نشان تجاریست و با اجازهٔ بنیاد ویکیمدیا استفاده میشود. ما زیرمجموعه یا شعبهٔ بنیاد نیستیم.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Source</a> and <a href=\"https://commons-app.github.io/\">وبسایت</a> در گیتهاب. ایجاد یک <a href=\"https://github.com/commons-app/apps-android-commons/issues\">درخواست در گیتهاب</a> برای گزارش باگ و یا پیشنهاد یک خصوصیت جدید.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">سیاست حفظ حریم خصوصی</a>
+ اپلیکیشن ویکیانبار بنیاد ویکیمدیا یک نرمافزار آزاد است که توسط کاربران داوطلب و پاداشبگیر ایجاد و نگهداری میشود. بنیاد ویکیمدیا در ایجاد، نگهداری و توسعهٔ آن دخالتی ندارد.
+ ایجاد یک <a href=\"https://github.com/commons-app/apps-android-commons/issues\">درخواست در گیتهاب</a> برای گزارش باگ و یا پیشنهاد یک خصوصیت جدید.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">سیاست حفظ حریم خصوصی</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">مجوز</a>دربارهارسال بازخورد (از طریق ایمیل)
@@ -121,7 +117,7 @@
لطفاً بارگذاری نکنید:-سلفی خودتان یا تصویر دوستانتان\n-تصاویری که از اینترنت دانلود کردید\n-نماگرفت از دیگر اپلیکیشنهانمونه بارگذاری:
- -عنوان: خانهٔ اپرای سیدنی\n-توضیحات: خانهٔ اپرای سیدنی از آن طرف خلیج\n-ردهها: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ -عنوان: خانهٔ اپرای سیدنی\n-توضیحات: خانهٔ اپرای سیدنی از آن طرف خلیج\n-ردهها: Sydney Opera House from the west, Sydney Opera House remote viewsعکسهای خود را به اشتراک بگذارید. به ویکیپدیا کمک کنید تا مقالاتش زنده شوند!ویکیپدیا از تصویرهای ویکیانبار استفاده میکند.تصویرهای شما به مطالعهٔ مردم در سراسر دنیا کمک میکنند.
@@ -164,6 +160,9 @@
تأیید دومرحلهای الان پشتیبانی نمیشود.آیا واقعاً قصد خروج از سامانه را دارید؟نشان ویکیانبار
+ وبسایت ویکیانبار
+ صفحهٔ فیسبوک ویکیانبار
+ صفحهٔ کدهای گیتهاب ویکیانبارتصویر پسزمینهخطای تصویر رسانهتصویری یافت نشد
@@ -188,6 +187,7 @@
بازخوردخروجآموزش
+ آگاهسازیهامکانهای اطراف بدون اجازه دادن به مکانیاب مقدور نیستتوضیحی یافت نشدصفحهٔ دروند در ویکیانبار
@@ -195,6 +195,20 @@
خطا در زمان دریافت تصاویرعنوانی توصیفی و یکتا برای پرونده که به عنوان نام پرونده در نظر گرفته خواهد شد. ترجیحاً به زبان ساده باشد، میتوانید فاصله هم به کار ببرید. پسوند پرونده را ننویسید.لطفاً تصویر را تا حد توان شرح دهید. کجا گرفته شدهاست؟ شامل چه چیزی میشود؟ لطفاً اشیا یا افراد را شرح دهید. اطلاعاتی که به راحتی قابل مشاهده هستند را صرفهنظر کنید. اگر چیزی در تصویر غیر طبیعی به نظر میرسد آن را شرح دهید.
+ اجازه بدهاستفاده از حافظهٔ خارجیذخیرهٔ تصویرهای گرفته شده توسط دوربین درونکار اپلیکیشن بر روی دستگاه شما
+ ارسال فایل سیاهه
+ ارسال فایل سیاهه بهوسیلهٔ ایمیل برای توسعهدهندگان
+ ورود به حساب کاربریتان
+ مکان تغییر نکردهاست.
+ مکان موجود نیست.
+ برای نمایش مکانّای اطراف نیاز به اجازه است.
+ دریافت جهتها
+ خواندن مقاله
+ %1$s، به ویکیانبار خوش آمدید! خوشحالیم که اینجا هستید.
+ %1$s در صفحهٔ بحث شما پیامی گذاشتهاست
+ برای ویرایش ممنون
+ %1$s در %2$s به شما اشاره کردهاست.
+ دکمه نمایش
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index e6a70d7a7..01864d294 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -22,7 +22,7 @@
Napauta katsoaksesi1 tiedosto tallentuu
- %d tiedostoa tallentuu
+ %1$d tiedostoa tallentuuViimeaikaiset tallennukseniJonossa
@@ -54,28 +54,28 @@
GPS ei ole käytössä. Haluatko ottaa sen käyttöön?Ota GPS käyttöönEi tallennuksia vielä
-
+ \@string/contributions_subtitle_zero
- %d tallennus
- %d tallennusta
+ %1$d tallennus
+ %1$d tallennusta
-
- aloitetaan %d tallennus
- aloitetaan %d tallennusta
+
+ aloitetaan %1$d tallennus
+ aloitetaan %1$d tallennusta
-
- %d tallennus
- %d tallennusta
+
+ %1$d tallennus
+ %1$d tallennustaLuokkaa %1$s ei löytynyt
- Lisää luokkia tehdäksesi kuvistasi helpommin löydettäviä.\n\nAloita kirjoittaminen lisätäksesi luokkia.\nNapauta tätä viestiä (tai paina takaisin) ohittaaksesi tämän vaiheen.
+ Lisää luokkia tehdäksesi kuvistasi enemmän löydettäviä Wikimedia Commonssissa.\nAloita kirjoittaminen lisätäksesi luokkia.LuokatAsetuksetRekisteröidyTietojaTämä on vapaan lähdekoodin ohjelmisto, joka on julkaistu <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a> -lisenssin alaisena. Wikimedia Commons ja sen logo ovat Wikimedia Foundationin tavaramerkkejä ja niitä käytetään Wikimedia Foundationin luvalla. Emme ole hyväksyttyjä tai sidoksissa Wikimedia Foundationioniin.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Lähde</a> ja <a href=\"https://commons-app.github.io/\">nettisivusto</a> GitHubissa. Luo uusi <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-issue</a> bugiraporteille ja ehdotuksille.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Yksityisyydensuoja</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Lähde</a> ja <a href=\"https://commons-app.github.io/\">nettisivusto</a> GitHubissa. Luo uusi <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-issue</a> bugiraporteille ja ehdotuksille.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Yksityisyydensuoja</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Tekijät</a>TietojaLähetä palautetta (sähköpostitse)
@@ -121,7 +121,7 @@
ÄLÄ tallenna seuraavia:- Selfiet tai kuvat ystävistäsi\n- Netistä ladatut kuvat\n- Kuvakaappaukset kaupallisista sovelluksistaTallennusesimerkki:
- - Nimi: Sydneyn operatalo\n- Kuvaus: Sydneyn oopperatalo katsottuna lahden toisella puolella\n- Luokat: Sydneyn oopperatalo, Sydneyn oopperatalo lännestä, Sydneyn oopperatalo remote views
+ - Otsikko: Sydneyn oopperatalo\n- Kuvaus: Sydneyn oopperatalo katsottuna lahden toiselta puolelta\n- Luokat: Sydneyn oopperatalo, Sydneyn oopperatalo lännestä, Sydneyn oopperatalon etänäkymätHerätä Wikipedia-artikkelit eloon kuvillasi! Tuo kuvasi Wikipediaan.Wikipedian kuvat tulevat Wikimedia Commonsista.Kuvasi auttavat useita ihmisiä ympäri maailmaa artikkeleiden ymmärtämisessä.
@@ -153,8 +153,11 @@
Ryhdy beetatestaajaksiKäytä Wikidataa(Varoitus: poiskytkeminen voi aiheuttaa suuren mobiilidatankäytön)
+ 2FA koodi
+ Tuorein latausrajaniMaksimimääräEi voida näyttää enempää, kuin 500
+ Aseta tuore latausrajaKaksivaiheinen tunnistus ei ole vielä tuettu.Haluatko varmasti kirjautua ulos?Commons Logo
@@ -162,9 +165,11 @@
Kuvaa ei löytynytLataa kuvaZao-vuori
+ LaamatSateenkaarisiltaTulppaaniEi selfieitä
+ Patentoitu kuvaTervetuloa WikipediaanTervetuloa tekijänoikeusSydneyn oopperatalo
@@ -179,8 +184,18 @@
PalauteKirjaudu ulosOpas
+ IlmoituksetLähellä olevia paikkoja ei voida näyttää ilman sijaintilupaa
+ kuvausta ei löytynytCommons-tiedostosivuWikidata-kohdeTiedoston yksilöllinen ja kuvaava otsikko, jota käytetään tiedostonimenä. Voit käyttää tavallista kieltä välilyönnein. Älä sisällytä tiedoston päätettä.
+ Anna lupa
+ Käytä ulkoista tallennustilaa
+ Lähetä lokitiedosto
+ Lähetä logitiedosto kehittäjille sähköpostin kautta
+ Kirjaudu tilillesi
+ Sijainti ei ole muuttunut.
+ Sijainti ei käytettävissä.
+ LUE ARTIKKELI
diff --git a/app/src/main/res/values-fo/strings.xml b/app/src/main/res/values-fo/strings.xml
index ddc39ebf1..eebe9cf1e 100644
--- a/app/src/main/res/values-fo/strings.xml
+++ b/app/src/main/res/values-fo/strings.xml
@@ -43,7 +43,7 @@
Leita í bólkumGoymOngin bólkur sum passaði saman við %1$s varð funnin
- Legg bólkar til fyri at tínar myndir verða lættari at finna á Wikimedia Commons.\n\nByrja við at skriva fyri at leggja bólkar til.\nTrýst hetta boðið (ella vend aftur) fyri at droppa hetta stigið.
+ Legg bólkar til fyri at tínar myndir verða lættari at finna á Wikimedia Commons.\n\nByrja við at skriva fyri at leggja bólkar til.\nTrýst hetta boðið (ella vend aftur) fyri at droppa hetta stigið.BólkarInnstillingarUm
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 3897e321a..75e42c9d5 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -21,8 +21,8 @@
Le téléversement de %1$s a échouéAppuyer pour afficher
- %d fichier en cours de téléversement
- %d fichiers en cours de téléversement
+ %1$d fichier en cours de téléchargement
+ %1$d fichiers en cours de téléchargementMes téléversements récentsMis en file d\'attente
@@ -56,25 +56,25 @@
Encore aucun téléversement\@string/contributions_subtitle_zero
- %d téléversement
- %d téléversements
+ %1$d téléchargement
+ %1$d téléchargements
- %d téléversement démarré
- %d téléversements démarrés
+ %1$d téléchargement démarré
+ %1$d téléchargements démarrés
- %d téléversement
- %d téléversements
+ %1$d téléchargement
+ %1$d téléchargementsAucune catégorie correspondant à %1$s trouvée
- Ajoutez des catégories pour rendre vos images plus simple à trouver sur Wikimedia Commons. \n\nCommencer à ajouter des catégories. \nAppuyez sur ce message (ou retournez en arrière) pour sauter cette étape.
+ Ajoutez des catégories pour rendre vos images plus simples à trouver sur Wikimedia Commons. \nCommencer à ajouter des catégories.CatégoriesParamètresS’inscrireÀ propos
- L\'application Wikimedia Commons est une application open-source crée et maintenue par les bénéficiaires et volontaires de la communauté Wikimedia. La fondation Wikimedia n\'est pas associée à la création, le développement ou la maintenance de l\'application.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Sources</a> et <a href=\"https://commons-app.github.io/\">site web</a> sur GitHub. Créer un nouveau <a href=\"https://github.com/commons-app/apps-android-commons/issues\">signalement GitHub</a> pour signales des bogues ou des suggestions.
+ L’application Wikimedia Commons est une application open source créée et tenue à jour par les bénéficiaires et volontaires de la communauté Wikimedia. La fondation Wikimedia n’est pas associée à la création, le développement ou l’entretien de l’application.
+ Créer un nouveau <a href=\"https://github.com/commons-app/apps-android-commons/issues\">signalement GitHub</a> pour signaler des bogues ou des suggestions.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Politique de confidentialité</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Remerciements</a>À propos
@@ -121,7 +121,7 @@
Veuillez ne PAS téléverser :- des selfies ou des images de vos amis \n- des images téléchargées sur Internet\n- des copies d’écran d’applications propriétairesExemple de téléversement :
- - Titre : L’opéra de Sydney\n- Description : L’opéra de Sydney vu à travers la baie\n- Catégories : Opéra de Sydney, Opéra de Sydney depuis l’ouest, vues à distance de l’Opéra de Sydney
+ - Titre : Opéra de Sydney\n- Description : L’opéra de Sydney vu à travers la baie\n- Catégories : Opéra de Sydney depuis l’ouest, vues à distance de l’Opéra de SydneyContribuez avec vos images. Aidez les articles de Wikipédia à prendre vie !Les images sur Wikipédia viennent de Wikimedia Commons.Vos images aident à éduquer les gens dans le monde entier.
@@ -164,6 +164,9 @@
L’authentification à deux facteurs n’est pas prise en charge pour le moment.Voulez-vous vraiment vous déconnecter ?Logo de Commons
+ Site web de Communs
+ Page Facebook de Communs
+ Code source Github de CommunsImage de fondÉchec sur l’image du médiaAucune image trouvée
@@ -188,6 +191,7 @@
CommentaireDéconnexionTutoriel
+ NotificationsLes endroits proches ne peuvent pas être affichés si vous ne partagez pas votre position géographique.aucune description trouvéePage des fichiers de Commons
@@ -198,4 +202,17 @@
Accorder le droitUtiliser le stockage externeEnregistrer les images prises avec l’appareil photo de votre appareil
+ Envoyer le journal
+ Envoyer le journal aux développeurs par courriel
+ Connectez-vous à votre compte
+ L\'emplacement n\'a pas changé.
+ Emplacement non disponible.
+ Une permission est requise pour afficher une liste de lieux relatifs
+ OBTENIR DES DIRECTIVES
+ LIRE L’ARTICLE
+ Bienvenue sur Wikimedia Commons, %1$s! Nous sommes heureux que vous soyez venu.
+ %1$s a laissé un message sur votre page de discussion
+ Merci de faire une modification
+ %1$s vous a mentionné sur %2$s .
+ Basculer l’affichage
diff --git a/app/src/main/res/values-frp/error.xml b/app/src/main/res/values-frp/error.xml
new file mode 100644
index 000000000..f7a658562
--- /dev/null
+++ b/app/src/main/res/values-frp/error.xml
@@ -0,0 +1,4 @@
+
+
+ Grant-marci !
+
diff --git a/app/src/main/res/values-frr/strings.xml b/app/src/main/res/values-frr/strings.xml
index d5097cdc3..4d8ed0d4a 100644
--- a/app/src/main/res/values-frr/strings.xml
+++ b/app/src/main/res/values-frr/strings.xml
@@ -21,8 +21,8 @@
Huuchschüüren faan „%1$s“ skiaf gingenTipe, am det uuntulukin
- %d datei woort huuchschüürd
- %d datein wurd huuchschüürd
+ %1$d datei woort huuchschüürd
+ %1$d datein wurd huuchschüürdMin tuleetst huuchschüürd dateinSkal noch teew
@@ -54,27 +54,27 @@
GPS as üüb dan aperoot ei uun. Wel dü det nü aktiwiare?GPS aktiwiareBit nü nian uploads
-
+ \@string/contributions_subtitle_zeroIan huuchschüürd datei
- %d huuchschüürd datein
+ %1$d huuchschüürd datein
-
- Began %d upload
- Began %d uploads
+
+ Began %1$d upload
+ Began %1$d uploads
-
- %d huuchschüürd datei
- %d huuchschüürd datein
+
+ %1$d huuchschüürd datei
+ %1$d huuchschüürd dateinDet kategorii „%1$s“ küd ei fünjen wurd.
- Dü könst nei kategoriin iinracht, amdat din bilen beeder fünjen wurd. Eder skriiw en kategorii-nööm of tipe üüb det nooracht (of gung üüb det \'turag\'-fial), an läät di straal ütj.
+ Dü könst nei kategoriin iinracht, amdat din bilen beeder fünjen wurd. Eder skriiw en kategorii-nööm of tipe üüb det nooracht (of gung üüb det \'turag\'-fial), an läät di straal ütj.KategoriinIinstelangenIinskriiwAuerOpen Source software ütjden oner det lisens <a href=\" https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>.\nWikimedia Commons an det Wikimedia-Commons-Logo san markintiaken faan\'t Wikimedia Foundation an wurd mä ferloof faan\'t Wikimedia Foundation brükt. Wi hiar ei tu\'t Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Code</a> an <a href=\"https://commons-app.github.io/\">Wääbsteed</a> üüb GitHub</a>. En neien <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-iindrach</a> för feelern of föörslacher maage.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Code</a> an <a href=\"https://commons-app.github.io/\">Wääbsteed</a> üüb GitHub</a>. En neien <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-iindrach</a> för feelern of föörslacher maage.Brükerreegeln<a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Soonk</a>Auer
@@ -120,7 +120,7 @@
Oober schüür EI huuch:- Selfies of bilen faan din frinjer\n- Bilen, diar dü ütj at internet deellooset heest\n- Bilskirembilen faan ünfrei softwareBispal:
- - Tiitel: Sydney Opernhüs\n- Beskriiwang: Opernhüs fan Sydney, faan\'t bocht ütj sen\n- Kategoriin: Sydney Opernhüs, Sydney Opernhüs faan waasten, Sydney Opernhüs faan widj wech
+ - Tiitel: Sydney Opernhüs\n- Beskriiwang: Opernhüs fan Sydney, faan\'t bocht ütj sen\n- Kategoriin: Sydney Opernhüs, Sydney Opernhüs faan waasten, Sydney Opernhüs faan widj wechSkaft din bilen. Halep mä an maage artiikler üüb Wikipedia labener!A bilen üüb Wikipedia kem faan Wikimedia Commons.Mä din bilen halepst dü minsken üüb a hialer welt.
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 045d28140..90de742cc 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -21,8 +21,8 @@
Erro ao cargar \"%1$s\"Prema para mostralo
- Cargando %d ficheiro
- Cargando %d ficheiros
+ Cargando %1$d ficheiro
+ Cargando %1$d ficheirosAs miñas subas recentesNa cola
@@ -56,26 +56,26 @@
Aínda non hai subas\@string/contributions_subtitle_zero
- %d carga
- %d cargas
+ %1$d carga
+ %1$d cargas
- Iniciando %d carga
- Iniciando %d cargas
+ Iniciando %1$d carga
+ Iniciando %1$d cargas
- %d carga
- %d cargas
+ %1$d carga
+ %1$d cargasNon se atopou ningunha categoría que coincidise con \"%1$s\"
- Engada categorías para facer máis accesibles as súas imaxes na Wikimedia Commons.\n\nComece a escribir para engadir categorías.\nPrema nesta mensaxe (ou no botón \"Atrás\") para saltar este paso.
+ Engada categorías para facer máis accesibles as súas imaxes na Wikimedia Commons.\nComece a escribir para engadir categorías.CategoríasConfiguraciónsRexistrarseAcerca de
- Software de código aberto liberado baixo a <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">licenza Apache v2</a>. %1$s e o seu logo son marcas rexistradas da Fundación Wikimedia e úsanse coa súa autorización. Non fomos acreditados pola Fundación Wikimedia nin estamos afiliados con ela.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Código fonte</a> e <a href=\"https://commons-app.github.io/\">sitio web</a> en GitHub. Crear unha nova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia</a> para informar de problemas e suxestións.
- <a href=\"https://wikimediafoundation.org/wiki/Política_de_protección_de_datos_(gl)\">Política de protección de datos</a>
+ A aplicación Wikimedia Commons é unha aplicación de código aberto creada e mantida polos cesionarios e voluntarios da comunidade de Wikimedia. A Fundación Wikimedia non está involucrada na creación, desenvolvemento ou mantemento da aplicación.
+ Crear unha nova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia</a> para informar de problemas e suxestións.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Política de privacidade</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Créditos</a>Acerca deEnviar comentarios (por correo electrónico)
@@ -121,7 +121,7 @@
Por favor, NON subaː- Selfies ou imaxes dos seus amigos\n- Imaxes descargadas de Internet\n- Capturas de pantalla de aplicacións con dereitos de autorExemplo de subaː
- - Título: Ópera de Sydney\n- Descrición: A Ópera de Sydney vista dende a baía\n- Categorías: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Título: Ópera de Sydney\n- Descrición: A Ópera de Sydney vista dende a baía\n- Categorías: Sydney Opera House from the west, Sydney Opera House remote viewsAchegue as súas imaxes. Axude a que os artigos da Wikipedia cobren vida!As imaxes da Wikipedia veñen da Wikimedia Commons.As súas imaxes axudan a educar xente de todo o mundo.
@@ -164,6 +164,9 @@
Actualmente non se permite a autenticación de dous factores.Está seguro de querer saír?Logo de Commons
+ Sitio web de Commons
+ Páxina de Facebook en Commons
+ Código fonte de Commons en GithubImaxe de fondoFallou a imaxe de multimediaNon se atopou imaxe
@@ -188,6 +191,7 @@
ComentariosSaírTitorial
+ NotificaciónsOs sitios situados preto non poden visualizarse sen permisos de localizaciónnon se atopou descriciónPáxina do ficheiro en Commons
@@ -195,6 +199,19 @@
Erro mentras se gardaban as imaxes na cachéUn título único descritivo para o ficheiro, que servirá como un nome de ficheiro. Pode usar unha linguaxe clara con espazos. Non inclúa a extensión do ficheiroPor favor, describa o ficheiro todo o posibleː Onde se gravou? Cal é o contexto? Por favor, describa os obxectos ou persoas. Indique información que non pode ser adiviñada de forma doada, por exemplo, a hora do día se é unha paisaxe. Se o ficheiro amosa algo pouco habitual, por favor, explique que é o que o fai excepcional.
+ Outorgar permisoUsar o almacenamento externoGardar as imaxes capturadas coa cámara do seu dispositivo
+ Enviar ficheiro de rexistro
+ Enviar ficheiro de rexistro ós desenvolvedores por correo electrónico
+ Comezar sesión na súa conta
+ A localización non cambiou.
+ A localización non está dispoñible.
+ Precísase permiso para amosar unha lista de lugares preto de aquí
+ OBTER DIRECCIÓNS
+ LER ARTIGO
+ Benvido a Wikimedia Commons, %1$s! Alegrámonos de que estea aquí.
+ %1$s deixoulle unha mensaxe na súa páxina de conversa
+ Grazas por realizar unha edición
+ %1$s mencionouno en %2$s.
diff --git a/app/src/main/res/values-haw/strings.xml b/app/src/main/res/values-haw/strings.xml
index 7754696c8..4e66c5970 100644
--- a/app/src/main/res/values-haw/strings.xml
+++ b/app/src/main/res/values-haw/strings.xml
@@ -43,7 +43,7 @@
Huli i nā māheleMālamaʻAʻohe māhele e huli ʻia e like me %1$s
- Hoʻohui i nā māhele no ke kaunānā ʻana ma Kahilehulehu Wikimedia.
+ Hoʻohui i nā māhele no ke kaunānā ʻana ma Kahilehulehu Wikimedia.MāheleMakemakePilina
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index 08cf39d61..452bb6523 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -9,10 +9,10 @@
प्रवेश हो रहाप्रतीक्षा करें…प्रवेश में सफल हुआ!
- प्रवेश विफल हुआ
- फ़ाइल नहीं मिला, कृपया अन्य फ़ाइल से कोशिश करें।
+ प्रवेश विफल हुआ!
+ फ़ाइल नहीं मिली, कृपया अन्य फ़ाइल से प्रयास करें।प्रमाणीकरण विफल!
- अपलोड शुरू हुआ!
+ अपलोड आरंभ!%1$s अपलोड हुआ!अपना अपलोड देखने के लिए टैप करें%1$s का अपलोड शुरू हुआ
@@ -20,7 +20,7 @@
%1$s का अपलोड पूरा हुआ%1$s का अपलोड विफल हुआदेखने हेतु टैप करें
-
+ %d फ़ाइल अपलोड हो रहा%d फ़ाइलें अपलोड हो रहीं
@@ -55,57 +55,57 @@
जीपीएस सक्षम करेंअभी तक कोई अपलोड नहीं
- %d अपलोड
- %d अपलोड्स
+ %1$d अपलोड
+ %1$d अपलोड्स
- %d अपलोड शुरू
- %d अपलोड शुरू
+ %1$d अपलोड शुरू
+ %1$d अपलोड शुरू
-
- %d अपलोड
- %d अपलोड
+
+ %1$d अपलोड
+ %1$d अपलोड%1$s से कोई श्रेणी मेल नहीं खाती
- अपने छवियों को विकिमीडिया कॉमन्स में अधिक ढूँढने लायक बनाने हेतु श्रेणियाँ लगायें। \n\nश्रेणी जोड़ने हेतु लिखना शुरू करें।\nइसे न करने हेतु इस संदेश को टैप करें या पीछे का बटन दबायें।
+ विकिमीडिया कॉमन्स पर अपनी छवियों को अधिक खोजने योग्य बनाने के लिए श्रेणियां जोड़ें|\nश्रेणियां जोड़ने के लिए टाइप करना प्रारंभ करें|श्रेणियाँपसंदखाता खोलेंपरिचय
- मुक्त स्रोत सॉफ्टवेयर जो <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">अपाचे लाइसेन्स</a> के अंतर्गत जारी किया गया है। %1$s और इसका लोगो विकिमीडिया संस्था का व्यापारिक चिह्न है और इसके मर्जी से ही उपयोग किया जाना चाहिए। हम किसी भी प्रकार से विकिमीडिया संस्था से जुड़े नहीं हैं।
- <a href=\"https://github.com/commons-app/apps-android-commons\">स्रोत</a> और <a href=\"https://commons-app.github.io/\">वेबसाइट</a> गिटहब में है और त्रुटि व सुझाव हेतु <a href=\"https://github.com/commons-app/apps-android-commons/issues\">गिटहब समस्या</a> देखें।
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">गोपनियता नीति</a>
+ विकिमीडिया कॉमन्स एप्प एक मुक्त स्रोत एप्प है जो कि विकिमीडिया समुदाय के अनुदानप्राप्तकर्ताओं व स्वयंसेवकों द्वारा निर्मित एवं प्रबंधित है। विकिमीडिया फॉऊण्डेशन इस एप्प के निर्माण, विकास व प्रबंधन में किसी प्रकार से भी संलग्न नहीं है।
+ त्रुटि की सूचना और सुझावों के लिए <a href=\"https://github.com/commons-app/apps-android-commons/issues\"> GitHub समस्या </a> बनाएं
+ <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">गोपनीयता नीति</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">श्रेय</a>परिचयप्रतिक्रिया दें (ईमेल द्वारा)
- कोई ईमेल ग्राहक स्थापित नहीं
+ कोई ईमेल साधन स्थापित नहींहाल ही उपयोग में ली गयी श्रेणियाँपहले सिंक हेतु प्रतीक्षा में…आपने अब तक कोई फोटो अपलोड नहीं किया है।फिर प्रयास करेंरद्द करें
- इस छवि का लाइसेन्स %1$s के अंतर्गत है।
+ इस छवि का लाइसेन्स %1$s के अंतर्गत होगा।इस तस्वीर को सबमिट करके, मैं घोषणा करता हूं कि यह मेरा अपना काम है, इसमें कॉपीराइट सामग्री या सेल्फी नहीं है, और अन्यथा <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">विकीमीडिया कॉमन्स नीतियां का पालन करता हूँ </a>डाउनलोडलाइसेन्सपिछले शीर्षक/विवरण का उपयोग करेंवर्तमान स्थान स्वतः ज्ञात करें
- यदि छवि जियोटैगेड नहीं है तो श्रेणियों के सुझाव हेतु वर्तमान स्थान ज्ञात करें।
+ यदि छवि पर जियोटैग नहीं है तो श्रेणियों के सुझाव हेतु वर्तमान स्थान ज्ञात करें।रात्रि मोडडार्क थीम का प्रयोग करें
- विशेषता-साझेदारी 4.0
+ एट्रीब्यूशन-शेयरअलाइक 4.0एट्रिब्यूशन 4.0
- एट्रीबुसन-शेयरअलाइक 3.0
- एट्रीबुसन 3.0
- CC0
- CC BY-SA 3.0
- CC BY-SA 3.0 (ऑस्ट्रीया)
+ एट्रीब्यूशन-शेयरअलाइक 3.0
+ एट्रीब्यूशन 3.0
+ सीसी0
+ सीसी बाय-एसए 3.0
+ सीसी बाय-एसए 3.0 (ऑस्ट्रिया)CC BY-SA 3.0 (जर्मनी)CC BY-SA 3.0 (एस्टोनिया)CC BY-SA 3.0 (स्पेन)
- CC BY-SA 3.0 (क्रोटिया)
- CC BY-SA 3.0 (लुक्सेमबौर्ग)
- CC BY-SA 3.0 (नेदरलैंड)
+ CC BY-SA 3.0 (क्रोएशिया)
+ CC BY-SA 3.0 (लक्समबर्ग)
+ CC BY-SA 3.0 (नीदरलैंड)CC BY-SA 3.0 (नॉर्वे)CC BY-SA 3.0 (पोलैंड)CC BY-SA 3.0 (रोमानिया)
@@ -113,19 +113,19 @@
CC BY-SA 4.0CC BY 4.0CC Zero
- विकिपीडिया में उपयोग होने वाले कई चित्रों को होस्ट विकिमीडिया कॉमन्स करता है।
- आपके लिए चित्र पूरे विश्व के लोगों को शिक्षित करेंगे!
- कृपया उन तस्वीरों को डालें, जो आपने ली है या केवल आपके द्वारा बनाई गई है:
- - प्राकृतिक वस्तुओं (फूल, पशु, पहाड़ों)\n- उपयोगी वस्तुओं (साइकिल, रेलवे स्टेशन)\n- प्रसिद्ध लोगों (आपके महापौर, ओलंपिक एथलीटों जिनसे आप मिले हो)
+ विकिपीडिया में उपयोग होने वाले अधिकतर चित्र विकिमीडिया कॉमन्स पर रखे जाते है।
+ आपके लिए चित्र पूरे विश्व के लोगों को शिक्षित करने में सहायता करेंगे!
+ कृपया उन तस्वीरों को डालें, जो केवल आपके द्वारा ली गई या बनाई गई है:
+ - प्राकृतिक वस्तुएँ (फूल, पशु, पहाड़)\n- उपयोगी वस्तुएँ (साइकिल, रेलवे स्टेशन)\n- प्रसिद्ध लोग (आपके महापौर, ओलंपिक एथलीट जिनसे आप मिले हों)कृपया अपलोड न करें:- सेल्फी या अपने दोस्तों की तस्वीरें\n- इंटरनेट से डाउनलोड की गई तस्वीरें\n- किसी निजी एप का स्क्रीनशॉटअपलोड का उदाहरण:
- - शीर्षक: सिडनी ओपेरा हाउस\n- विवरण: सिडनी ओपेरा हाउस खाड़ी के पार से देखा गया\n- श्रेणियाँ: सिडनी ओपेरा हाउस, पश्चिम से सिडनी ओपेरा हाउस, सिडनी ओपेरा हाउस दूर से
- अपने छवियों का योगदान करें। विकिपीडिया के लेखों को जीवित करने में सहायता करें।
+ - शीर्षक: सिडनी ओपेरा हाउस\n- विवरण: सिडनी ओपेरा हाउस का खाड़ी के पार से दृश्य\n- श्रेणियाँ: सिडनी ओपेरा हाउस, पश्चिम से सिडनी ओपेरा हाउस, सिडनी ओपेरा हाउस दूर से
+ अपने लिए चित्रों का योगदान करें। विकिपीडिया के लेखों में जान फूँकने में सहायता करें।विकिपीडिया में छवि विकिमीडिया कॉमन्स से आती है।
- आपके चित्र पूरे विश्व के लोगों को शिक्षित करेंगे।
+ आपके चित्र पूरे विश्व के लोगों को शिक्षित करने में सहायता करते हैं।इंटरनेट से मिली कोई कॉपीराइट सामग्री के साथ साथ पोस्टर, पुस्तक के खड्डे आदि को भी अपलोड करने से बचें।
- जो आपने सोचा वो मिला?
+ क्या आपको लगता है कि आप समझ गए?हाँ!श्रेणियाँलोड हो रहा है…
@@ -152,9 +152,9 @@
निर्देशांककुछ नहीं प्रदान किया गयाबीटा परीक्षक बनें
- गूगल प्ले पर हमारे बीटा चैनल में ऑप्ट-इन करें और नई सुविधाओं और बग फिक्स के लिए शीघ्र प्राप्त करें
+ गूगल प्ले पर हमारे बीटा चैनल का चयन करें और नई सुविधाओं व त्रुटिसुधारों तक पहले पहुँचेविकिडेटा का प्रयोग करें
- (चेतावनी: इसे अक्षम करने से बड़ी मोबाइल डेटा की खपत हो सकती है)
+ (चेतावनी: इसे अक्षम करने से मोबाइल डेटा की खपत अधिक हो सकती है)2 एफए कोडमेरी हाल ही की अपलोड सीमाअधिकतम सीमा
@@ -163,6 +163,9 @@
दो कारक प्रमाणीकरण वर्तमान में समर्थित नहीं हैक्या आप वास्तव में निकास करना चाहते हैं?कॉमन्स का प्रतीक चिन्ह
+ कॉमन्स जालस्थान
+ कॉमन्स फेसबुक पृष्ठ
+ कॉमन्स गिटहब स्त्रोत कूटशब्दपृष्ठभूमि छविमीडिया छवि विफलकोई छवि नहीं मिली
@@ -187,6 +190,7 @@
आपके सुझावप्रस्थान करेंअनुशिक्षण
+ सूचनायेंआस-पास के स्थान बिना स्थान अनुमतियों के प्रदर्शित नहीं किए जा सकते हैंकोई विवरण नहीं मिलाकॉमन्स फाइल पृष्ठ
@@ -194,6 +198,19 @@
चित्र कैशिंग करते समय त्रुटिफ़ाइल के लिए एक अद्वितीय वर्णनात्मक शीर्षक, जो एक फ़ाइल नाम के रूप में काम करेगा। आप रिक्त स्थान के साथ सादे भाषा का उपयोग कर सकते हैं। फ़ाइल विस्तार शामिल न करेंकृपया मीडिया जितना संभव हो उतना बताएं: यह कहां लिया गया? यह क्या दिखाता है? संदर्भ क्या है? कृपया वस्तुओं या व्यक्तियों का वर्णन करें। ऐसी जानकारी का खुलासा करें जिसे आसानी से अनुमानित नहीं किया जा सकता, उदाहरण के लिए दिन का समय यदि यह परिदृश्य है। अगर मीडिया कुछ असामान्य दिखाता है, तो कृपया बताएं कि इसे क्या असामान्य बनाता है।
+ अनुमति देंबाहरी स्टॉरज का पृयोग करे।आप अपने डिवाइस के इन-ऐप कैमरा से ली गई तस्वीरों को सहेजें।
+ लॉग फाइल भेजें
+ डेवेलपर्स को लॉग फाइल ई-मेल से भेजें
+ अपने खाते में प्रवेश करें
+ स्थान परिवर्तन नहीं हुआ।
+ स्थान उपलब्ध नहीं।
+ आसपास के स्थान दिखाने के लिए अनुमति चाहिए
+ दिशा - निर्देश प्राप्त करें
+ लेख पढ़ें
+ विकिमीडिया कॉमन्स पर आपका स्वागत है, %1$s! हमें हर्ष है कि आप यहाँ हैं।
+ %1$s ने आपके वार्ता पृष्ठ पर सन्देश छोड़ा है
+ सम्पादन करने के लिए धन्यवाद
+ %1$s ने %2$s में आपका उल्लेख किया है
diff --git a/app/src/main/res/values-hrx/strings.xml b/app/src/main/res/values-hrx/strings.xml
index eeed8a5b3..3c07ed6f5 100644
--- a/app/src/main/res/values-hrx/strings.xml
+++ b/app/src/main/res/values-hrx/strings.xml
@@ -43,7 +43,7 @@
Kategorie dorrichsucheSpeichreDie Kategorie „%1$s“ woard net gefunn
- Füch Kategoriee hinzu, um deine Bilder auf Wikimedia Commons ufffindbarer se mache.\n\nBeginn zu schreibe, um Kategoriee hinzuzufüchen.\nTippe uff die Nachricht orrer drücke \"Zurück\", um den Schritt zu üwerspringe.
+ Füch Kategoriee hinzu, um deine Bilder auf Wikimedia Commons ufffindbarer se mache.\n\nBeginn zu schreibe, um Kategoriee hinzuzufüchen.\nTippe uff die Nachricht orrer drücke \"Zurück\", um den Schritt zu üwerspringe.KategorieeInstellungeÜwer
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index b99a78d67..134563a09 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -11,9 +11,9 @@
Sikeres bejelentkezésA bejelentkezés nem sikerült.A fájl nem található. Próbálkozz másik fájllal.
- Sikertelen azonosítás.
- Feltöltés indul.
- %1$s feltöltve!
+ Sikertelen hitelesítés.
+ Feltöltés elindult.
+ %1$s feltöltve.Feltöltés megtekintéseFeltöltés indul: %1$s%1$s feltöltése
@@ -21,8 +21,8 @@
%1$s feltöltése sikertelenÉrintsd meg a megtekintéshez
- %d fájl feltöltése folyamatban
- %d fájl feltöltése folyamatban
+ %1$d fájl feltöltése folyamatban
+ %1$d fájl feltöltése folyamatbanLegutóbbi feltöltéseimVárólistán
@@ -56,26 +56,26 @@
Még nincsenek feltöltések\@string/contributions_subtitle_zero
- %d feltöltés
- %d feltöltés
+ %1$d feltöltés
+ %1$d feltöltés
- %d feltöltés kezdése
- %d feltöltés kezdése
+ %1$d feltöltés kezdése
+ %1$d feltöltés kezdése
- %d feltöltés
- %d feltöltés
+ %1$d feltöltés
+ %1$d feltöltésNincs a(z) „%1$s” keresési kifejezésnek megfelelő kategória
- Adj kategóriákat a képekhez, hogy könnyebben meg lehessen találni őket a Commonson.\n\nKezdd el beírni a kategória nevét, hogy hozzáadd.\nKattints erre az üzenetre a lépés kihagyásához
+ Adj kategóriákat a képekhez, hogy könnyebben meg lehessen találni őket a Commonson.\nKezdd el beírni a kategória nevét, hogy hozzáadd.\nBökj erre az üzenetre (vagy a vissza gombra) a lépés kihagyásáhozKategóriákBeállításokRegisztrációNévjegy
- Nyílt forráskódú szoftver <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a> alatt kiadva. A Wikimédia Commons és a logója a Wikimédia Alapítvány védjegyei, és a Wikimédia Alapítvány engedélyével vannak használva. Az alkalmazás fejlesztői nincsenek semmilyen kapcsolatban Wikimédia Alapítvánnyal.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Forráskód</a> és <a href=\"https://commons-app.github.io/\">weboldal</a> a GitHubon. Nyiss egy új <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-problémát</a> hibabejelentéssel vagy fejlesztési javaslattal.
- <a href=\"https://wikimediafoundation.org/wiki/Adatvédelmi_irányelv\">Adatvédelmi irányelvek</a>
+ A Wikimedia Commons applikáció egy nyílt forráskódú szoftver, amit a Wikimedia-közösség önkéntesei készítettek és tartanak karban. A Wikimédia Alapítvány nem vesz részt az applikáció megalkotásában, fejlesztésében és üzemeltetésében.
+ Nyiss egy új <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-problémát</a> hibabejelentéssel vagy fejlesztési javaslattal.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Adatvédelmi irányelvek</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Köszönetnyilvánítás</a>NévjegyVisszajelzés küldése (e-mailben)
@@ -86,6 +86,7 @@
ÚjraMégseEz a kép %1$s licenc alatt kerül feltöltésre
+ A kép feltöltésével kijelentem, hogy ez a saját munkám és nem tartalmaz jogvédett anyagot, nem szelfi és megfelel a<a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Wikimedia Commons irányelveinek</a>.LetöltésLicencElőző cím/leírás használata
@@ -104,7 +105,7 @@
CC BY-SA 3.0 (Észtország)CC BY-SA 3.0 (Spanyolország)CC BY-SA 3.0 (Horvátország)
- CC BY-SA 3.0 (Luxembourg)
+ CC BY-SA 3.0 (Luxemburg)CC BY-SA 3.0 (Hollandia)CC BY-SA 3.0 (Norvégia)CC BY-SA 3.0 (Lengyelország)
@@ -120,7 +121,7 @@
Kérjük, NE tölts fel:- Szelfiket vagy képeket a barátaidról\n- Internetröl letöltött képeket\n- Kereskedelmi alkalmazások képernyőképeitPélda feltöltés:
- - Cím: Sydney-i Operaház\n- Leírás: A Sydney-i Operaház az öböl túlpartjáról\n- Kategóriák: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Cím: Sydney-i Operaház\n- Leírás: A Sydney-i Operaház az öböl túlpartjáról\n- Kategóriák: Sydney Opera House from the west, Sydney Opera House remote viewsTedd közzé a képeidet! Segíts életre kelteni a Wikipédia-szócikkeket!A Wikipédián található képek a Wikimédia Commonsből származnak.A képeid segítenek a világ minden táján élő emberek oktatásában.
@@ -134,6 +135,7 @@
Ismeretlen licencFrissítésSzükséges engedély: Külső tárhely olvasása. Az alkalmazás nem működik enélkül.
+ Szükséges engedély: Külső tárhely írása. Az alkalmazás nem működik enélkül.Lehetséges engedély: Jelenlegi hely megszerzése, a kategóriajavaslatok lehetőségéért.OKKözeli helyek
@@ -147,6 +149,9 @@
LeírásA média leírása kerül ide. Ez akár egészen hosszú is lehet, és több sorba fog kerülni. Azért reméljük, jól néz majd ki.Feltöltési dátum
+ Licenc
+ Koordináták
+ Nincs megadvaBéta tesztelővé válásJelentkezz a béta csatornánkra a Google Playen, hogy hamarabb megkapd a hibajavításokat és az új funkciókatWikidata használata
@@ -162,6 +167,13 @@
HáttérképNem található képKép feltöltése
+ Zaō-hegy
+ Lámák
+ Rainbow Bridge
+ Tulipán
+ Csak semmi szelfi
+ Szabadalmaztatott kép
+ Üdvözlünk a WikipédiánSydney OperaházMégseMegnyitás
@@ -174,7 +186,19 @@
VisszajelzésKijelentkezésBevezető
+ Közeli helyek nem megjeleníthetőek a helyadatokhoz való hozzáférés engedélyezése nélkülnincs leírás
- Commons szócikk
+ Commons leírólapWikidata-elem
+ Hiba a képek gyorsítótárazásakor
+ Egy egyedi, leíró cím a fájlnak, ami fájlnévként fog szolgálni. Egyszerű nyelvezetet használhatsz szóközökkel. Ne tedd bele a kiterjesztést.
+ Kérlek a lehető legteljesebb módon írd le a fájlt: hol készült, mit ábrázol, mi a kontextus? Kérlek add meg az objektumokat vagy személyeket a képen, valamint a nehezen kitalálható információkat (például a kép készítésének dátumát, ha az egy tájkép). Amennyiben a média valami szokatlant ábrázol, kérlek fejtsd ki, hogy mi teszi szokatlanná.
+ Engedély adása
+ Külső tárhely használata
+ Az alkalmazáson belüli kamerával készült képek mentése az eszközre
+ Naplófájlok küldése
+ Naplófájlok küldése e-mailben a fejlesztőknek
+ Bejelentkezés a fiókodba
+ A hely nem változott.
+ A hely nem érhető el.
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
index 6cb355fee..a739449c4 100644
--- a/app/src/main/res/values-id/strings.xml
+++ b/app/src/main/res/values-id/strings.xml
@@ -20,7 +20,7 @@
Tekan untuk melihatMengunggah 1 berkas
- Mengunggah %d berkas
+ Mengunggah %1$d berkasUnggahan sayaAntrean
@@ -49,15 +49,15 @@
Tidak ada unggahan1 unggahan
- %d unggahan
+ %1$d unggahanMemulai 1 unggahan
- Memulai %d unggahan
+ Memulai %1$d unggahan1 unggahan
- %d unggahan
+ %1$d unggahanTidak ada kategori yang cocok dengan %1$sTambahkan kategori agar foto Anda lebih mudah ditemukan di Wikimedia Commons.
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 486ba2852..3d766f26d 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -22,7 +22,7 @@
Tekan untuk melihatmengunggah 1 berkas
- mengunggah %d berkas
+ mengunggah %1$d berkasUnggahan sayaAntrean
@@ -52,18 +52,18 @@
Tidak ada unggahan1 unggahan
- %d unggahan
+ %1$d unggahanMemulai 1 unggahan
- Memulai %d unggahan
+ Memulai %1$d unggahan1 unggahan
- %d unggahan
+ %1$d unggahanTidak ada kategori yang cocok dengan %1$s
- Tambahkan kategori agar foto Anda lebih mudah ditemukan di Wikimedia Commons.\n\nMulai mengetik untuk menambahkan kategori.\nTekan pesan ini (atau tekan kembali) untuk melewatkan langkah ini.
+ Tambahkan kategori agar foto Anda lebih mudah ditemukan di Wikimedia Commons.\n\nMulai mengetik untuk menambahkan kategori.\nTekan pesan ini (atau tekan kembali) untuk melewatkan langkah ini.KategoriPengaturanMendaftar
diff --git a/app/src/main/res/values-is/error.xml b/app/src/main/res/values-is/error.xml
new file mode 100644
index 000000000..d4150af3c
--- /dev/null
+++ b/app/src/main/res/values-is/error.xml
@@ -0,0 +1,7 @@
+
+
+ Commons-forritið hrundi
+ Úbbs. Eitthvað fór úrskeiðis!
+ Lýstu því fyrir okkur, eins nákvæmlega og þú getur, hvað þú varst að gera þegar forritið hrundi. Sendu það svo til okkar í tölvupósti, það getur hjálpað okkur við að laga þetta.
+ Þakka þér fyrir!
+
diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml
new file mode 100644
index 000000000..36c7ee2b4
--- /dev/null
+++ b/app/src/main/res/values-is/strings.xml
@@ -0,0 +1,217 @@
+
+
+ Commons
+ Stillingar
+ Notandanafn
+ Lykilorð
+ Skrá inn
+ Nýskrá
+ Innskráning í gangi
+ Hinkraðu aðeins…
+ Innskráning tókst!
+ Innskráning mistókst!
+ Skráin fannst ekki. Prófaðu aðra skrá.
+ Auðkenning tókst ekki!
+ Innsending í gangi!
+ %1$s var sent inn!
+ Bankaðu til að skoða sem þú ert að senda inn
+ Hef innsendingu á %1$s
+ Verið er að senda %1$s inn
+ Lýk innsendingu á %1$s
+ Innsending á %1$s mistókst
+ Bankaðu til að skoða
+
+ %1$d skrá að sendast inn
+ %1$d skrár að sendast inn
+
+ Nýlega innsendar skrár
+ Í biðröð
+ Mistókst
+ %1$d%% lokið
+ Sendi gögn
+ Úr myndasafni
+ Taktu mynd
+ Í nágrenninu
+ Innsendu skrárnar mínar
+ Deila
+ Skoða í vafra
+ Titill
+ Lýsing
+ Innskráning mistókst - bilun í neti
+ Innskráning mistókst. Athugaðu notandanafnið þitt
+ Innskráning mistókst. Athugaðu lykilorðið þitt
+ Of margar misteknar tilraunir. Reyndu aftur eftir nokkrar mínútur.
+ Því miður, þessi notandi hefur verið bannaður á Commons
+ Þú verður að setja inn tveggja-þrepa auðkenningarkóðann þinn.
+ Innskráning mistókst
+ Senda inn skrár
+ Settu nafn á þetta sett
+ Breytingar
+ Senda inn
+ Leita í flokkum
+ Vista
+ Endurlesa
+ GPS er óvirkt í tækinu þínu. Viltu virkja það?
+ Virkja GPS
+ Engar innsendingar ennþá
+
+ \@string/contributions_subtitle_zero
+ %1$d innsending
+ %1$d innsendingar
+
+
+ Byrja %1$d innsendingu
+ Byrja %1$d innsendingar
+
+
+ %1$d innsending
+ %1$d innsendingar
+
+ Engir flokkar sem samsvara %1$s fundust
+ Bættu við flokkum til að gera myndirnar þínar aðgengilegri á Wikimedia Commons\nByrjaðu að skrifa til að bæta við flokkum.
+ Flokkar
+ Stillingar
+ Nýskrá
+ Um
+ Wikimedia Commons forritið er opinn og frjáls hugbúnaður sem gerður er og viðhaldið af stuðningsaðilum og sjálfboðaliðum Wikimedia samfélagsins. Wikimedia Foundation sjálfseignarstofnunin kemur ekki að gerð, forritun eða viðhaldi forritsins. \
+ Útbúðu nýjar <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub tilkynningar (issue)</a> til að koma villum og uppástungum á framfæri.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Meðferð persónuupplýsinga</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Framlög</a>
+ Um
+ Senda umsögn (með tölvupósti)
+ Ekkert tölvupóstforrit er uppsett
+ Nýlega notaðir flokkar
+ Bíð eftir fyrstu samstillingu...
+ Þú ert ekki ennþá búin(n) að senda inn neinar myndir.
+ Reyna aftur
+ Hætta við
+ Þessi mynd verður birt með %1$s notkunarleyfi
+ Með því að senda inn þessa mynd, lýsi ég því hér með yfir að þetta er mitt eigið verk, það inniheldur ekkert höfundarréttarvarið efni eða sjálfsmyndir, og að það samræmist á allan hátt við <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">stefnu Wikimedia Commons varðandi notkun efnis</a>.
+ Sækja
+ Notkunarleyfi
+ Nota fyrri titil/lýsingu
+ Ná sjálfvirkt í núverandi staðsetningu
+ Lesa núverandi staðsetningu til að geta stungið upp á flokkum ef myndin er ekki með hnattstaðsetningarhnitum
+ Næturhamur
+ Nota dökkt þema
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0
+ CC0
+ CC BY-SA 3.0
+ CC BY-SA 3.0 (Austurríki)
+ CC BY-SA 3.0 (Þýskaland)
+ CC BY-SA 3.0 (Eistland)
+ CC BY-SA 3.0 (Spánn)
+ CC BY-SA 3.0 (Króatía)
+ CC BY-SA 3.0 (Lúxemborg)
+ CC BY-SA 3.0 (Holland)
+ CC BY-SA 3.0 (Noregur)
+ CC BY-SA 3.0 (Pólland)
+ CC BY-SA 3.0 (Rúmenía)
+ CC BY 3.0
+ CC BY-SA 4.0
+ CC BY 4.0
+ CC Zero
+ Wikimedia Commons hýsir mest af því myndefni sem notað er á Wikipedia.
+ Myndirnar þínar hjálpa fólki um allan heim að læra!
+ Sendu inn myndir sem þú hefur tekið eða sem eru algerlega eftir þig sjálfa(n):
+ - Náttúruleg fyrirbæri (blóm, dýr, fjöll)\n- Notadrjúgir hlutir (reiðhjól, lestarstöðvar)\n- Frægt fólk (sveitarstjórinn þinn, ólympíufarar sem þú hefur hitt)
+ EKKI senda inn:
+ - Sjálfsmyndir eða myndir af vinum þínum\n- Myndir sem þú náðir í á Internetinu\n- Skjámyndir af séreignahugbúnaði
+ Dæmi um innsent efni:
+ - Titill: Óperuhúsið í Sydney\n- Lýsing: Óperuhúsið í Sydney séð að handan yfir flóann\n- Flokkar: Óperuhúsið í Sydney séð úr vestri, Óperuhúsið í Sydney séð úr fjarlægð
+ Bjóddu fram myndirnar þínar. Blástu lífi í greinar á Wikipedia!
+ Myndir á Wikipedia koma frá Wikimedia Commons..
+ Myndirnar þínar hjálpa fólki um allan heim að læra.
+ Forðastu höfundaréttarvarið efni sem finnst á netinu eins og myndir af veggspjöldum, bókarkápum, o.s.frv.
+ Heldurðu að þú hafir náð þessu?
+ Já!
+ Flokkar
+ Hleð…
+ Ekkert valið
+ Engin lýsing
+ Óþekkt notkunarleyfi
+ Endurlesa
+ Nauðsynlegar heimildir: Lesa ytri gagnageymslu. Forritið virkar ekki án þess.
+ Nauðsynlegar heimildir: Skrifa í ytri gagnageymslu. Forritið virkar ekki án þess.
+ Nauðsynlegar heimildir: Lesa núverandi staðsetningu til að geta stungið upp á flokkum
+ Í lagi
+ Staðir í nágrenninu
+ Engir staðir fundust í nágrenninu
+ Aðvörun
+ Þessi skrá er þegar fyrirliggjandi á Commons. Ertu viss um að þú viljir halda áfram?
+ Já
+ Nei
+ Titill
+ Titill þessa gagnamiðils
+ Lýsing
+ Lýsing á gagnamiðlinum kemur hér. Þetta má vera nokkuð langt og mun þurfa að skríða yfir nokkrar línur. Við vonum að þetta líti samt þokkalega út.
+ Sent inn þann
+ Notkunarleyfi
+ Hnit
+ Ekkert uppgefið
+ Taktu þátt í beta-forprófunum
+ Veldu að taka þátt í beta-forprófunarrásinni á Google Play og fáðu snemmbúinn aðgang að nýjum eiginleikum og villuleiðréttingum
+ Nota Wikidata
+ (Aðvörun: ef þetta er óvirkt getur það leitt til mikillar notkunar á gagnamagni)
+ 2FA-kóði
+ Nýleg sendingartakmörk mín
+ Hámark
+ Get ekki birt fleiri en 500
+ Setja nýleg sendingartakmörk mín
+ Tveggja-þrepa auðkenning er ekki studd í augnablikinu.
+ Viltu í alvörunni skrá þig út?
+ Táknmerki Commons
+ Commons vefsvæðið
+ Commons Facebook-síðan
+ Grunnkóði Commons á Github
+ Bakgrunnsmynd
+ Mynd gagnamiðils brást
+ Engin mynd fannst
+ Senda inn mynd
+ Zao-fjall
+ Lamadýr
+ Regnbogabrú
+ Túlípani
+ Engar sjálfur
+ Séreignarmynd
+ Velkomin á Wikipedia
+ Höfundarréttur
+ Óperuhúsið í Sydney
+ Hætta við
+ Opna
+ Loka
+ Heim
+ Senda
+ Í nágrenninu
+ Um
+ Stillingar
+ Umsagnir
+ Útskráning
+ Kennsla
+ Tilkynningar
+ Ekki er hægt að birta nálæga staði an heimildar til að ná í hnattstaðsetningu
+ engin lýsing fannst
+ Síða Commons-skrár
+ Wikidata-atriði
+ Villa kom upp í skyndiminni mynda
+ Einstakur og lýsandi titill, sem mun verða skráarheiti. Þú mátt nota einfaldan texta með bilum. Ekki hafa með neina skráarendingu
+ Lýstu gögnunum eins vel og auðið er: Hvar er myndin tekin? Hvað sýnir hún? Hvert er samhengið? Lýstu fólki og fyrirbærum. Gefðu upp þær upplýsingar sem ekki er auðvelt að giska á, til dæmis á hvaða tíma dags myndin er tekin ef hún sýnir landslag. Ef gögnin sýna eitthvað óvenjulegt, útskýrðu þá hvað það er sem sé sérstakt.
+ Gefa heimild
+ Nota ytri gagnageymslu
+ Vistaðu myndir sem teknar hafa verið með innbyggðu myndavélinni í tækinu þínu
+ Senda atvikaskrá
+ Senda atvikaskrá til forritaranna með tölvupósti
+ Skrá inn á aðganginn þinn
+ Staðsetning hefur ekki breyst.
+ Staðsetning ekki tiltæk.
+ Heimild þarf til að birta lista yfir staði í nágrenninu
+ FÁ LEIÐSÖGN
+ LESA GREIN
+ Velkomin á Wikimedia Commons, %1$s! Við erum ánægð með að þú skulir vera hérna.
+ %1$s skildi eftir skilaboð á spjallsíðunni þinni
+ Takk fyrir að hafa gert breytingar
+ %1$s minntist á þig á %2$s.
+
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 0eb6b8b41..df93909d4 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -21,8 +21,8 @@
Caricamento di %1$s non riuscitoPremi per visualizzare
- %d file in caricamento
- %d file in caricamento
+ %1$d file in caricamento
+ %1$d file in caricamentoI miei ultimi caricamentiIn coda
@@ -56,25 +56,25 @@
Non è stato ancora caricato niente\@string/contributions_subtitle_zero
- 1 caricamento
- %d caricamenti
+ %1$d caricamento
+ %1$d caricamenti
- Iniziato %d caricamento
- Iniziati %d caricamenti
+ Iniziato %1$d caricamento
+ Iniziati %1$d caricamenti
- %d caricamento
- %d caricamenti
+ %1$d caricamento
+ %1$d caricamentiNon è stata trovata alcuna categoria che contiene %1$s
- Aggiungi categorie per rendere le tue immagini più facilmente individuabili su Wikimedia Commons.\n\nInizia a digitare per aggiungere categorie.\nTocca questo messaggio (o premi Indietro) per saltare l\'aggiunta di categorie.
+ Aggiungi categorie per rendere le tue immagini più facilmente individuabili su Wikimedia Commons.\nInizia a digitare per aggiungere categorie.CategorieImpostazioniRegistratiInformazioniL\'app di Wikimedia Commons è un\'applicazione open source creata e mantenuta da beneficiari e volontari della comunità Wikimedia. La Fondazione Wikimedia non è coinvolta nella creazione, sviluppo o manutenzione dell\'applicazione.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Codice sorgente</a> e <a href=\"https://commons-app.github.io/\">sito web</a> su GitHub. Crea una nuova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">segnalazione GitHub</a> per riportare errori e suggerimenti.
+ Crea una nuova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">segnalazione GitHub</a> per riportare errori e suggerimenti.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Politica sulla privacy</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Crediti</a>Informazioni
@@ -114,6 +114,7 @@
CC ZeroWikimedia Commons ospita la maggior parte delle immagini che vengono utilizzate in Wikipedia.Le tue immagini aiutano l\'istruzione di persone in tutto il mondo!
+ Per favore NON caricare:Esempi di caricamento:Contribuisci con le tue immagini. Aiuta a rendere vive le voci di Wikipedia!Le immagini su Wikipedia provengono da Wikimedia Commons.
@@ -146,11 +147,18 @@
Non è possibile mostrarne più di 500L\'autenticazione a due fattori non è attualmente supportata.Vuoi veramente uscire?
+ Logo di Commons
+ Sito web di Commons
+ Pagina Facebook di Commons
+ Codice sorgente Github di CommonsNessuna immagine trovataCarica immagine
+ Monte ZaoArcobalenoTulipanoNo autoscatti (selfie)
+ Benvenuto Wikipedia
+ Benvenuto CopyrightTeatro dell\'opera di SydneyAnnullaApri
@@ -163,7 +171,18 @@
CommentiEsciTutorial
+ Notifichenessuna descrizione trovataPagina di Commons del fileElemento Wikidata
+ Dai autorizzazione
+ Accedi alla tua utenza
+ La posizione non è cambiata.
+ Posizione non disponibile.
+ OTTIENI DIREZIONI
+ LEGGI VOCE
+ Benvenuto in Wikimedia Commons, %1$s! Siamo contenti che tu sia qui.
+ %1$s ti ha lasciato un messaggio nella tua pagina di discussione
+ Grazie per aver fatto una modifica
+ %1$s ti ha menzionato su %2$s.
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index 5342b9f3f..cc5026d83 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -22,7 +22,7 @@
נא ללחוץ כדי להציגקובץ אחד מועלה
- %d קבצים מועלים
+ %1$d קבצים מועליםההעלאות האחרונות שליבתור
@@ -55,26 +55,27 @@
הפעלת GPSלא הועלה עדיין שום דבר
+ \@string/contributions_subtitle_zeroהעלאה אחת
- %d העלאות
+ %1$d העלאותהתחלת העלאה
- התחלת %d העלאות
+ התחלת 1$d% העלאותהעלאה אחת
- %d העלאות
+ %1$d העלאותלא נמצאו קטגוריות בשם %1$s
- נא להוסיף קטגוריות כדי שיהיה קל יותר למצוא את התמונות שלך בוויקישיתוף.\n\nכדי להוסיף קטגוריה יש להתחיל לכתוב.\nיש ללחוץ על ההודעה הזאת או על כפתור \"חזרה\" כדי לדלג על הצעד הזה.
+ יש להוסיף קטגוריות כדי שיהיה קל יותר למצוא את התמונות שלך בוויקישיתוף.\nכדי להוסיף קטגוריה יש להתחיל לכתוב.קטגוריותהגדרותרישוםאודות
- תכנת קוד פתוח המתפרסמת לפי תנאי <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. השם %1$s והסמל שמשויך אליו הם סימני מסחר של קרן ויקימדיה ומשמשים באישור קרן ויקימדיה. איננו נתמכים על־ידי קרן ויקימדיה או קשורים אליה בשותפות.
- <a href=\"https://github.com/commons-app/apps-android-commons\">קוד מקור</a> ו<a href=\"https://commons-app.github.io/\">אתר</a> בגיטהאב. נא ליצור <a href=\"https://github.com/commons-app/apps-android-commons/issues\">דיווח בגיטהאב</a> בשביל באגים והצעות.
-
+ יישום ויקישיתוף (Wikimedia Commons app) הוא יישום קוד פתוח שמפותח ומתוחזק על־ידי מקבלי מלגות ומתנדבים של קהילת ויקימדיה. קרן ויקימדיה אינה מעורבת ביצירה, פיתוח, או תחזוקה של היישום.
+ נא ליצור <a href=\"https://github.com/commons-app/apps-android-commons/issues\">דיווח בגיטהאב</a> בשביל באגים והצעות.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">מדיניות פרטיות</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">יוצרים</a>אודותשליחת משוב (בדוא\"ל)
@@ -120,7 +121,7 @@
נא לא להעלות:- תמונות עצמיות (\"סלפי\") או תמונות של חברים שלכם\n- תמונות שהורדתם מהאינטרנט\n- צילומי מסך של יישומים קניינייםהעלאה לדוגמה:
- - כותרת: בית האופרה של סידני\n- תיאור: בית האופרה של סידני מהצד השני של המפרץ\n- קטגוריות: Sydney Opera House (בית האופרה של סידני), Sydney Opera House from the west (בית האופרה של סידני מהמערב), Sydney Opera House remote views (מראה מרחוק על בית האופרה של סידני)
+ - כותרת: בית האופרה של סידני\n- תיאור: בית האופרה של סידני מהצד השני של המפרץ\n- קטגוריות: Sydney Opera House from the west (בית האופרה של סידני מהמערב), Sydney Opera House remote views (מראה מרחוק על בית האופרה של סידני)תרמו את התמונות שלכם. עזרו לערכים בוויקיפדיה להתעורר לחיים!התמונות בוויקיפדיה מגיעות מ־Wikimedia Commons.התמונות שלכם עוזרות להעניק חינוך לאנשים מסביב לעולם.
@@ -163,7 +164,11 @@
אימות דו־שלבי אינו נתמך כעת.האם באמת לצאת מהחשבון?סמל ויקישיתוף
+ אתר ויקישיתוף
+ עמוד הפייסבוק של ויקישיתוף
+ קוד המקור של ויקישיתוף ב־GitHubתמונת רקע
+ תמונת המדיה נכשלהלא נמצאה תמונההעלאת תמונההר זאו
@@ -172,6 +177,7 @@
צבעוניבלי תמונות סלפיתמונה קניינית
+ ברוך בואך ויקיפדיהבית האופרה של סידניביטולפתיחה
@@ -184,6 +190,7 @@
משוביציאהמדריך
+ הודעותאי־אפשר להציג מקומות בסביבה ללא הרשאות מיקוםלא נמצא תיאורדף קובץ בוויקישיתוף
@@ -191,6 +198,20 @@
שגיאה במשירת תמונות במטמוןכותרת מתארת ייחודית לקובץ, שתשמש שם קובץ. אפשר להשתמש בשפה פשוטה עם רווחים. אין לכלול סיומת קובץנא לתאר את המדיה כמה שיותר: איפה היא נוצרה? מה היא מראה? מה ההקשר? נא לתאר את העצמים או את האנשים. נא לחשוף מידע שאי־אפשר לנחש בקלות, למשל, הזמן ביום אם זאת תמונת נוף. אם המדיה מציגה משהו בלתי־רגיל, נא להסביר מה מיוחד בה.
+ לתת הרשאהלהשתמש באחסון חיצונישמירת תמונות שצולמו באמצעות מצלמה בתוך היישום במכשיר שלך
+ שליחת קובץ יומן
+ שליחת קובץ יומן למפתחים בדואר אלקטרוני
+ כניסה לחשבון שלך
+ המיקום לא השתנה.
+ המיקום אינו זמין.
+ נדרשת הרשאה כדי להציג רשימה של מקומות בסביבה
+ קבלת כיוונים
+ קריאה הערך
+ ברוך בואך לוויקישיתוף של ויקימדיה, %1$s! שמחים לראות אותך כאן אצלנו.
+ קיבלת הודעה מאת %1$s בעמוד השיחה שלך
+ תודה לך על העריכה
+ אוזכרת על ידי %2$s ב{{GRAMMAR:תחילית|%1$s}}.
+ החלפת מצב תצוגה
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 08e243703..0806e8f54 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -21,7 +21,7 @@
%1$s のアップロードに失敗しました閲覧するにはタップしてください
- %d 件のファイルをアップロード中
+ %1$d 件のファイルをアップロード中自分の最近のアップロードファイル順番待ち中
@@ -46,18 +46,21 @@
このセットに名前をつけてください変更アップロード
- 検索するカテゴリ
+ カテゴリを検索保存更新
+ お使いのデバイスではGPSが無効になっています。有効にしますか?
+ GPSを有効にする
+ まだ何もアップロードされていません。\@string/contributions_subtitle_zero
- %d 件のアップロード
+ %1$d 件のアップロード
- %d 件のアップロードを開始中
+ %1$d 件のアップロードを開始中
- %d 件のアップロード
+ %1$d 件のアップロード%1$s に一致するカテゴリが見つかりませんあなたの画像をウィキメディア・コモンズで見つけやすくするためにカテゴリを追加してください。\n\nカテゴリ名の入力を開始してください。\nこの手順をスキップするにはこのメッセージをタップしてください(または戻るボタン)。
@@ -65,9 +68,9 @@
設定利用者登録このアプリについて
- <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache ライセンス v2</a> のもとで公開されているオープン ソース ソフトウェアです。Wikimedia Commons ならびにそのロゴはウィキメディア財団の商標であり、ウィキメディア財団の許可により使用しています。このサイトはウィキメディア財団の公認3でも提携先でもありません。
- ソースは <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a> にあります。バグとアイディアは <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Github</a> へ。
- <a href=\"https://wikimediafoundation.org/wiki/プライバシー・ポリシー\">プライバシー・ポリシー</a>
+ ウィキメディア・コモンズ・アプリはウィキメディア・コミュニティの助成金受給者とボランティアによって製作・メンテナンスされているオープンソースソフトウェアです。ウィキメディア財団はこのアプリの製作・開発・メンテナンスに関与していません。
+ ソースは <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a> にあります。バグとアイディアは <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Github</a> へ。
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/プライバシー・ポリシー\">プライバシー・ポリシー</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">クレジット</a>このアプリについてフィードバックをメールで送信
@@ -112,7 +115,7 @@
アップロードが《禁止》のもの:- あなたの友人の自撮り写真や画像\n- インターネットからダウンロードした画像\n- 著作権のあるアプリのスクリーンショットアップロードの例:
- - 題名: シドニー・オペラハウス\n- 説明: 湾の向こうから見たシドニー・オペラハウス\n- カテゴリ: シドニー・オペラハウス、シドニー・オペラハウスの西側、遠くから見たシドニー・オペラハウス
+ - 題名: シドニー・オペラハウス\n- 説明: 湾の向こうから見たシドニー・オペラハウス\n- カテゴリ: 西側から見たシドニー・オペラハウス、遠くから見たシドニー・オペラハウス画像を投稿してください。ウィキペディアの記事に彩りを!ウィキペディアの画像はウィキメディア・コモンズに保管されています。あなたの画像は世界中の人々が学習する助けになります
@@ -126,9 +129,11 @@
不明なライセンス更新必要な権限:外部ストレージを読み込みます。これがなければアプリは機能しません。
+ 必要な権限:外部ストレージを作成します。これがなければアプリは機能しません。オプションの権限:カテゴリ候補の現在の位置を取得する承認周りの場所
+ 付近の場所が見つかりません警告このファイルが既にコモンズにあります。本当にアップロードしますか?はい
@@ -146,7 +151,11 @@
ウィキデータを使用してください(警告:これを無効にすると、モバイルデータを大量に消費する可能性があります)最近のアップロードファイルに表示する最大件数
+ 最大限
+ 最近のアップロードファイルに表示する最大件数
+ 2段階認証は現在サポートされていません。ログアウトしてもよろしいですか?
+ コモンズの商標背景画像画像がありません画像をアップロード
@@ -155,17 +164,31 @@
レインボーブリッジチューリップウィキペディアへようこそ
+ シドニーオペラハウスキャンセル開く閉じるホームアップロード
+ 付近このアプリについて設定フィードバックログアウトチュートリアル
+ 通知
+ 場所の権限がないと、近くの場所を表示できません説明がありませんウィキデータ項目
+ 画像をキャッシュする際のエラー
+ ファイル固有の説明的な表題。ファイル名として使われます。平易な言葉を使い、空白を入れることができます。拡張子は含めないでください。
+ 可能な限りメディアを説明してください:どこで撮られましたか?それは何を示していますか?文脈とは何ですか?物や人を説明してください。容易に推測できない情報、例えば風景の場合の時刻を明らかにする。メディアに珍しいことがある場合は、何が珍しいのかを説明してください。
+ 権限を取得外部ストレージを使用
+ アプリ内のカメラで撮影した写真を端末に保存する
+ ログファイルを送信する
+ メールで開発者にログファイルを送信する
+ 自分のアカウントにログイン
+ 場所は変更されていません。
+ 位置が無効です。
diff --git a/app/src/main/res/values-ji/strings.xml b/app/src/main/res/values-ji/strings.xml
index a523d2754..c955a3e73 100644
--- a/app/src/main/res/values-ji/strings.xml
+++ b/app/src/main/res/values-ji/strings.xml
@@ -21,8 +21,8 @@
ארויפֿלאדן %1$s דורכגעפֿאלןדרוקט צו באקוקן
- %d טעקע לאדט אן
- %d טעקעס לאדן אן
+ %1$d טעקע לאדט אן
+ %1$d טעקעס לאדן אןמײַנע לעצטיקע ארויפֿלאדןאין ריי
@@ -48,11 +48,11 @@
\@string/contributions_subtitle_zero איין ארויפֿלאד
- %d ארויפֿלאדן
+ %1$d ארויפֿלאדן
- אנהייבן %d ארויפֿלאד
- אנהייבן %d ארויפֿלאדן
+ אנהייבן %1$d ארויפֿלאד
+ אנהייבן %1$d ארויפֿלאדןקאַטעגאריעסאיינשטעלונגען
diff --git a/app/src/main/res/values-jv/strings.xml b/app/src/main/res/values-jv/strings.xml
index a9e91c77a..95c315e2a 100644
--- a/app/src/main/res/values-jv/strings.xml
+++ b/app/src/main/res/values-jv/strings.xml
@@ -20,7 +20,7 @@
Rampung ngunggah %1$sWurung ngunggah %1$sTunyuk saperlu ndeleng
-
+ %d barkas lagi diunggahUnggahanku Sing Anyar
@@ -53,25 +53,25 @@
GPS dipatèni ing pirantiné panjenengan. Panjenengan arep ngurubaké?Urubaké GPSDurung ana unggahan
-
+ \@string/contributions_subtitle_zero
- %d unggahan
- %d unggahan
+ %1$d unggahan
+ %1$d unggahan
-
- Miwiti %d unggahan
- Miwiti %d unggahan
+
+ Miwiti %1$d unggahan
+ Miwiti %1$d unggahan
-
- %d unggahan
- %d unggahan
+
+ %1$d unggahan
+ %1$d unggahanOra ana kategori sing cocog karo %1$sKategoriSetèlanNdhaftarNgenani
- <a href=\"https://github.com/commons-app/apps-android-commons\">Sumber</a> lan <a href=\"https://commons-app.github.io/\">situs jaringan</a> ing GitHub. Gawé anyar <a href=\"https://github.com/commons-app/apps-android-commons/issues\">bab GitHub</a> ngenani lapuran ama lan saran.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Sumber</a> lan <a href=\"https://commons-app.github.io/\">situs jaringan</a> ing GitHub. Gawé anyar <a href=\"https://github.com/commons-app/apps-android-commons/issues\">bab GitHub</a> ngenani lapuran ama lan saran.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Pranatan priangga</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Krèdhit</a>Ngenani
diff --git a/app/src/main/res/values-ka/strings.xml b/app/src/main/res/values-ka/strings.xml
index 1cd0f7e2b..83e834d9a 100644
--- a/app/src/main/res/values-ka/strings.xml
+++ b/app/src/main/res/values-ka/strings.xml
@@ -20,9 +20,9 @@
ატვირთვის დასრულება %1$%1$ ატვირთვა ვერ მოხერხდადააჭირეთ სანახავად
-
- %d ფაილი იტვირთება
- %d ფაილი იტვირთება
+
+ %1$d ფაილი იტვირთება
+ %1$d ფაილი იტვირთებაჩემი ბოლო ატვირთვებირიგი
@@ -54,25 +54,25 @@
თქვენ მოწყობილობაზე GPS გამორთულია. ნამდვილად გსურთ ჩართვა?GPS-ის ჩართვაატვირთვები არ არის
-
+ \@string/contributions_subtitle_zero
- %d ატვირთვა
- %d ატვირთვა
+ %1$d ატვირთვა
+ %1$d ატვირთვა
-
- იწყება %d ატვირთვა
- იწყება %d ატვირთვა
+
+ იწყება %1$d ატვირთვა
+ იწყება %1$d ატვირთვა
-
- %d ატვირთვა
- %d ატვირთვა
+
+ %1$d ატვირთვა
+ %1$d ატვირთვაკატეგორიაკონფიგურაციარეგისტრაციაპროგრამის შესახებღია კოდის მქონე აპლიკაცია, ვრცელდება ლიცენზიით <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. ვიკისაწყობი და მისი ლოგო ფონდი ვიკიმედიის სავაჭრო ნიშნებია და ფონდი ვიკიმედიის ნებართვით გამოიყენება. ჩვენ არ ვართ დაკავშირებული ფონდ ვიკიმედიასთან.
- <a href=\"https://github.com/commons-app/apps-android-commons\">წყარო</a> და <a href=\"https://commons-app.github.io/\">საიტი</a> GitHub-ზე. შეცდომის შესატყობინებლად ან წინადადებისათვის შექმენით ახალი <a href=\"https://github.com/commons-app/apps-android-commons/issues\">მოთხოვნა GitHub-ზე</a>.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">წყარო</a> და <a href=\"https://commons-app.github.io/\">საიტი</a> GitHub-ზე. შეცდომის შესატყობინებლად ან წინადადებისათვის შექმენით ახალი <a href=\"https://github.com/commons-app/apps-android-commons/issues\">მოთხოვნა GitHub-ზე</a>.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">კონფიდენციალურობის პოლიტიკა</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">მადლობები</a>პროგრამის შესახებ
@@ -113,7 +113,7 @@
გთხოვთ არ ატვირთოთ:- სელფი ან მეგობრების სურათები\n- ინტერნეტიდან ჩამოტვირთული ფოტოები\n- არათავისუფალი აპლიკაციების სკრინშოტებიატვირთვის ნიმუში:
- - სათაური: სიდნეის ოპერის თეატრი\n- აღწერა: სიდნეის ოპერის თეატრის ხედი უბიდან\n- კატეგორიები: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - სათაური: სიდნეის ოპერის თეატრი\n- აღწერა: სიდნეის ოპერის თეატრის ხედი უბიდან\n- კატეგორიები: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsატვირთეთ თქვენი ფოტოები. დაეხმარეთ ვიკიპედიის სტატიებს გაცოცხლებაში!ვიკიპედიის ფოტოები ვიკისაწყობში ინახება.თქვენი სურათები ხალხის განათლებას ეხმარება მთელ მსოფლიოში.
diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml
index 9cb921c7b..651981705 100644
--- a/app/src/main/res/values-kab/strings.xml
+++ b/app/src/main/res/values-kab/strings.xml
@@ -21,8 +21,8 @@
Asali n %1$s ur yeddi araSenned akken ad sekneḍ
- %d n ufaylu yezga yettali
- %d n ifuyla zgan ttalin
+ %1$d n ufaylu yezga yettali
+ %1$d n ifuyla zgan ttalinIfuyla yulin melmi kanTtraǧun
@@ -56,26 +56,26 @@
Usla ayen yulin\@string/contributions_subtitle_zero
- %d n usali
- %d n usali
+ %1$d n usali
+ %1$d n usali
- Beddu n %d n usali
- Beddu n %d n usali
+ Beddu n %1$d n usali
+ Beddu n %1$d n usali
- %d n usali
- %d n usali
+ %1$d n usali
+ %1$d n usaliUlac taggayin imenṭaḍen akked %1$s i yettwafen
- Rnu taggayin akken ad terreḍ tugniwin-ik fessus-it i tifin di Wikimedia Commons.\n\nBdu timerna n taggayin.\nSenneḍ ɣef yizen-agi (neɣ uɣal ar deffir) akken ad tnegzeḍ asebter-agi.
+ Rnu taggayin akken ad terreḍ tugniwin-ik sehlent i tifin di Wikimedia Commons.\n\nBdu timerna n taggayin.TaggayinIɣewwaṛenJerredƔef
- Iseɣzanen n uɣbalu yeldin ffɣen-d ddaw n <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">turagt Apache v2</a>. %1$s akked ulugu-is d ticraḍ yersen n tesbeddit Wikimedia i yettwaseqdacen s tsiregt n tesbeddit Wikimedia. Ur nettwasireg ara sɣuṛ tasbeddit Wikimedia udiɣ ur nttekka ra ɣur-s.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Iɣbula</a> akked <a href=\"https://commons-app.github.io/\">usmel web</a> di GitHub. Rnu <a href=\"https://github.com/commons-app/apps-android-commons/issues\">tummla GitHub</a> tamaynut akken ad temmleḍ ibugen neɣ ad d-mudded isumar.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Tasertit n tbaḍnit</a>
+ Asnas Wikipedia Commons d asnas n uɣbalu yeldin i d-yennulfan udiɣ yettwarfed sɣur iwiziwen d wid i t-iḍemnen n tmezdagnut Wikimedia. Tasbeddit Wikimedia ur tetteka ara deg usnulfu-is, tanfelit-is, neɣ aseggem n usnas.
+ Rnu <a href=\"https://github.com/commons-app/apps-android-commons/issues\">tummla tamaynut GitHub</a> akken ad temmleḍ ibugen neɣ ad d-mudded isumar.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Tasertit n tbaḍnit</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Ismaden</a>ƔefAzen tikti (s yimayl)
@@ -121,7 +121,7 @@
UR salay ara:- isilfiyen neɣ tugniwin n yimdakkal-ik \n- tugniwin i d-sidreḍ si Internet\n- inɣal n ugdil n yisnasen yesɛan imawlanAmedya n usali:
- - Azwel: Tazqqa Opera n Sydney\n- Aglam : Tazeqqa Opera n Sydney seg ilel\n- Taggayin: Tazeqqa Opera n Sydney, Tazeqqa Opera n Sydney seg umalu, timeẓriyin s lebɛid n Tazeqqa Opera n Sydney.
+ - Azwel: Tazqqa Opera n Sydney\n- Aglam : Tazeqqa Opera n Sydney seg ilel\n- Taggayin: Tazeqqa Opera n Sydney, Tazeqqa Opera n Sydney seg umalu, timeẓriyin s lebɛid n Tazeqqa Opera n SydneyTtekki s tugniwin-ik. Snerni imagraden n Wikipedia!Tugniwin ɣef Wikipedia ttasent-d si Wikimedia Commons.Tugniwin-ik ad slemdent imdanen deg umaḍal.
@@ -164,6 +164,9 @@
Asesteb s snat n tarrayin ur yettusefrak ara akka tura.Tabɣiḍ ad teffɣeḍ?Alugu Commons
+ Asmel wen n Commons
+ Asmel Facebook n Commons
+ Tangalt aɣbalu Github n CommonsTugna n ugilalTugna n midya ur teddi araUlac tugna
@@ -188,6 +191,7 @@
TiktiTuffɣaAtuṭuryel
+ IlɣaImḍiqen iqerben zemren ur ttwaskanen ara ma yella ur tebḍiḍ ara adig-ik arakal.ulac aglam yettwafenAsebter n ifuyla yettwasnen
@@ -195,6 +199,19 @@
Tuccḍa di tririt n tugniwin ar tuffirtAzwul n useglem asuf i ufaylu, ara ttwasqedcen d isem n ufaylu. Tzemreḍ ad tesqedceḍ tutlayt fessusen s isekkilen ilmawen. Ur sedday ara asiɣzef n ufayluMa ulac aɣilf, seglem amidya s wayen akk i tzemreḍ: Anida yettwasekles? Acu i d-yemmal? D acu-t usatal-is? Seglem tiɣawsiwin neɣ imdanen. Mudd-d talɣut ur yezmiren ad tettwaf s wudem fessusen, amedya akud n wass ma yella d agama. Ma yella admidya yaskan-d ayen ur nuɣ ara tanumi, ini-d d aci i tyettarran d ayen ifazen.
+ Mudd tasiregtSeqdec asekles azɣaraySekles tiwlafin yettwaṭṭfen s tkamirat yellan deg ibenk
+ Azen afaylu n uɣmis
+ Azen afaylu n uɣmis i yinermisen s yimayl
+ Qqen ar umiḍan-ik
+ Adeg ur ibeddel ara.
+ Ulac adeg
+ Ilaq usireg i uskan tabdart n wadigen iqerben
+ AWI IWELLIHEN
+ ƔER AMAGRAD
+ Anṣuf ar Wikimedia Commons, %1$s! Nefreḥ yes-k dagi.
+ %1$syeǧǧa-d izen deg usenter n uskasi
+ Tanemmirt ɣef ubeddel
+ %1$s yemmel-ak-id deg %2$s .
diff --git a/app/src/main/res/values-km/strings.xml b/app/src/main/res/values-km/strings.xml
index 6c4f01d82..b2ac01a04 100644
--- a/app/src/main/res/values-km/strings.xml
+++ b/app/src/main/res/values-km/strings.xml
@@ -43,7 +43,7 @@
ស្វែងរកចំណាត់ថ្នាក់ក្រុមរក្សាទុករកមិនឃើញចំណាត់ថ្នាក់ក្រុមដែលត្រូវនឹង %1$s ទេ
- បន្ថែមចំណាត់ថ្នាក់ក្រុមអោយរូបភាពរបស់អ្នកដើម្បីអោយងាយស្រួលស្វែងរក្នុង Wikimedia Commons។\nចាប់ផ្ដើមវាយបញ្ចូលឈ្មោះចំណាត់ថ្នាក់ក្រុម។\nចុចលើសារនេះ (ឬចុចប៊ូតុងត្រលប់ក្រោយ)ដើម្បីរំលងជំហ៊ាននេះ។
+ បន្ថែមចំណាត់ថ្នាក់ក្រុមអោយរូបភាពរបស់អ្នកដើម្បីអោយងាយស្រួលស្វែងរក្នុង Wikimedia Commons។\nចាប់ផ្ដើមវាយបញ្ចូលឈ្មោះចំណាត់ថ្នាក់ក្រុម។\nចុចលើសារនេះ (ឬចុចប៊ូតុងត្រលប់ក្រោយ)ដើម្បីរំលងជំហ៊ាននេះ។ចំណាត់ថ្នាក់ក្រុមការកំណត់អំពី
diff --git a/app/src/main/res/values-kn/error.xml b/app/src/main/res/values-kn/error.xml
new file mode 100644
index 000000000..16418bb1c
--- /dev/null
+++ b/app/src/main/res/values-kn/error.xml
@@ -0,0 +1,4 @@
+
+
+ ಧನ್ಯವಾದಗಳು!
+
diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml
index c0494afd8..370026cd4 100644
--- a/app/src/main/res/values-kn/strings.xml
+++ b/app/src/main/res/values-kn/strings.xml
@@ -21,8 +21,8 @@
%1$s ಅಪ್ಲೋಡ್ ಮಾಡುವಿಕೆ ವಿಫಲವಾಗಿದೆನೋಡಲು ಮೆಲ್ಲಗೆ ತಟ್ಟಿ
- %d ಕಡತ ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆ
- %d ಕಡತಗಳು ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆ
+ %1$d ಕಡತ ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆ
+ %1$d ಕಡತಗಳು ಅಪ್ಲೋಡ್ ಅಗುತ್ತಿದೆನನ್ನ ಇತ್ತಿಚಿನ ಅಪ್ಲೋಡ್ಗಳುಸರತಿಸಾಲಿನಲ್ಲಿದೆ
@@ -55,7 +55,6 @@
ಜಿಪಿಎಸ್ ಸಕ್ರಿಯಗೊಳಿಸುಇನ್ನೂ ಯಾವುದೆ ಅಪ್ಲೋಡ್ಗಳಿಲ್ಲವರ್ಗಗಳು
- ಬಳಕೆಯ ವರದಿಗಳುವ್ಯವಸ್ಥೆಗಳುಕುರಿತುಕುರಿತು
diff --git a/app/src/main/res/values-ko-rKP/strings.xml b/app/src/main/res/values-ko-rKP/strings.xml
new file mode 100644
index 000000000..a8dd2b83e
--- /dev/null
+++ b/app/src/main/res/values-ko-rKP/strings.xml
@@ -0,0 +1,132 @@
+
+
+ 공용
+ 설정
+ 사용자 이름
+ 통행암호
+ 가입
+ 가입하기
+ 가입 중
+ 기다려주세요…
+ 가입 성공!
+ 가입 실패!
+ 서류를 찾을수 없습니다. 다른 서류를 사용해주십시오.
+ 인증 실패!
+ 올리적재를 시작했습니다!
+ %1$s 서류를 올리적재하였습니다!
+ 당신의 올리적재를 보려면 두드리세요
+ %1$s 서류 올리적재를 시작하는중
+ %1$s 서류를 올리적재하는중
+ %1$s 서류 올리적재를 완료하는중
+ %1$s 서류 올리적재 실패
+ 보려면 두드리세요
+
+ %1$d개의 서류를 올리적재하는중
+ %1$d개의 서류를 올리적재하는중
+
+ 내 최근 올리적재
+ 대기중
+ 실패
+ %1$d%% 완료
+ 올리적재중
+ 화랑에서 선택
+ 사진 찍기
+ 근처
+ 내 올리적재
+ 공유
+ 열람기로 보기
+ 제목
+ 설명
+ 가입할수 없습니다 - 망 오유입니다
+ 가입할수 없습니다 - 사용자이름을 확인하세요
+ 가입할수 없습니다 - 통행암호를 확인하세요
+ 실패한 시도가 너무 많습니다. 몇분후에 다시 시도하세요.
+ 죄송합니다, 이 사용자는 공용에서 차단되였습니다
+ 두인자검증부호를 제공해야 합니다.
+ 가입 실패
+ 올리적재
+ 이 모임에 이름짓기
+ 바뀜
+ 올리적재
+ 분류 검색
+ 보관
+ 재생
+ 장치에서 GPS가 금지되여있습니다. 허가하시겠습니까?
+ GPS 허가
+ 아직 올리적재가 없습니다
+
+ \@string/contributions_subtitle_zero
+ %1$d개 올리적재
+
+
+ %1$d장의 올리적재를 시작합니다
+ %1$d장의 올리적재를 시작합니다
+
+
+ %1$d개 올리적재
+
+ %1$s와(과) 일치하는 분류를 찾을수 없습니다
+ 분류
+ 설정
+ 등록하기
+ 정보
+ 위키매체공용 프로그람은 공개원천 응용프로그람이며 위키매체공동체내의 자원봉사자에 의해 유지됩니다. 위키매체재단은 응용프로그람의 생성, 개발, 유지보수에 관여하지 않습니다.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">개인정보정책</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">제작진</a>
+ 정보
+ (전자우편으로) 반결합 발신하기
+ 설치된 전자우편 의뢰기가 없습니다
+ 최근에 사용한 분류
+ 첫 동기를 기다리는중…
+ 아직 사진을 올리적재하지 않았습니다.
+ 다시 시도
+ 취소
+ 이 그림은 %1$s에 따라 사용이 허가됩니다
+ 이 그림에 대해서, 이것이 본인이 창작한 저작물이며, 저작권에 위배되는것이 포함되지 않았으며, <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">위키매체공용정책</a>에 위배되지 않는다는것에 동의합니다.
+ 내리적재
+ 허가권
+ 이전의 제목/설명을 사용하기
+ 자동으로 현재 위치 얻기
+ 화상에 지리정보꼬리표가 달려 있지 않다면, 현재 위치를 검색하여 분류를 제안해주십시오
+ 야간방식
+ 어두운 주제 쓰기
+ 저작자표시-동일조건변경허락 4.0
+ 저작자표시 4.0
+ 저작자표시-동일조건변경허락 3.0
+ 저작자표시 3.0
+ CC0
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (오스트리아)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (도이췰란드)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (에스또니야)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (에스빠냐)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (흐르바쯔까)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (룩셈부르그)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (네데를란드)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (노르웨이)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (뽈스까)
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 3.0 (로므니아)
+ 크리에이티브코먼즈 저작자표시 3.0
+ 크리에이티브코먼즈 저작자표시-동일조건변경허락 4.0
+ 크리에이티브코먼즈 저작자표시 4.0
+ CC 령
+ 위키매체공용은 위키백과에 쓰이는 대부분의 화상을 주관합니다.
+ 당신의 화상은 전전세계 사람들을 교육하는데 도움이 됩니다!
+ 탈퇴
+ 강좌
+ 알림
+ 위치 허가가 없으면 주변 장소를 표시할수 없습니다
+ 설명이 없습니다
+ 공용 서류 문서
+ 위키자료 항목
+ 그림 캐쉬처리 오유
+ 이 서류를 설명할수 있는 제목으로, 서류 이름으로 사용됩니다. 띄여쓰기를 포함한 일반적인 단어를 사용할수 있습니다. 서류 확장자는 포함하지 마세요
+ 권한 주기
+ 외부기억기 사용하기
+ 일지서류 발신하기
+ 전자우편으로 개발자에게 일지서류 발신하기
+ %1$s님, 위키매체공용에 오신것을 환영합니다! 반갑습니다.
+ %1$s님이 당신의 사용자 토론 문서에 글을 남겼습니다
+ 편집해 주셔서 감사합니다
+ %1$s님이 %2$s에서 당신을 언급했습니다.
+
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 09e174cd9..a38bf2fc8 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -21,8 +21,8 @@
%1$s 파일 올리기 실패보려면 탭하세요
- %d개의 파일을 올리는 중
- %d개의 파일을 올리는 중
+ %1$d개의 파일을 올리는 중
+ %1$d개의 파일을 올리는 중내 최근 업로드대기 중
@@ -56,24 +56,23 @@
아직 올린 항목이 없습니다\@string/contributions_subtitle_zero
- 1개 업로드
- %d개 업로드
+ %1$d개 업로드
- %d장의 업로드를 시작합니다
- %d장의 업로드를 시작합니다
+ %1$d장의 업로드를 시작합니다
+ %1$d장의 업로드를 시작합니다
- %d개 업로드
+ %1$d개 업로드%1$s와(과) 일치하는 분류를 찾을 수 없습니다
- 위키미디어 공용에 그림을 더 찾을 수 있도록 분류를 추가합니다.\n\n분류를 추가하려면 입력을 시작하세요.\n이 단계를 건너뛰려면 이 메시지를 탭하세요(또는 뒤로를 누르세요).
+ 위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.분류설정가입하기정보
- 오픈 소스 소프트웨어는 <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">아파치 라이선스 v2</a>에 따라 공개됩니다. %1$s 및 관련 로고는 위키미디어 재단의 상표이며 위키미디어 재단의 허가를 통해 사용될 수 있습니다. 저희는 위키미디어 재단에 의해 보증되거나 제휴되어 있지 않습니다.
- 소스 코드는 <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>에 있으며, 웹사이트는 <a href=\"https://commons-app.github.io/\">GitHub</a>에 있습니다. 버그나 기타 제안은 <a href=\" https://github.com/commons-app/apps-android-commons/issues\">GitHub</a>에 보고해주세요.
+ 위키미디어 공용 앱은 오픈 소스 애플리케이션이며 위키미디어 공동체 내의 자원봉사자에 의해 유지됩니다. 위키미디어 재단은 애플리케이션의 생성, 개발, 유지보수에 관여하지 않습니다.
+ 소스 코드는 <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>에 있으며, 웹사이트는 <a href=\"https://commons-app.github.io/\">GitHub</a>에 있습니다. 버그나 기타 제안은 <a href=\" https://github.com/commons-app/apps-android-commons/issues\">GitHub</a>에 보고해주세요.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">개인정보 정책</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">제작진</a>정보
@@ -120,7 +119,7 @@
업로드하지 마십시오:- 자기 자신 또는 친구의 사진\n- 인터넷에서 다운로드한 사진\n- 사유 앱의 스크린샷업로드 예시:
- - 제목: 시드니 오페라 하우스\n- 설명: 항만에서 바라본 시드니 오페라 하우스\n- 분류: 시드니 오페라 하우스, 서쪽에서 본 시드니 오페라 하우스, 시드니 오페라 하우스 원경
+ - 제목: 시드니 오페라 하우스\n- 설명: 항만 건너편에서 바라본 시드니 오페라 하우스\n- 분류: 시드니 오페라 하우스, 서쪽에서 본 시드니 오페라 하우스, 시드니 오페라 하우스 원경당신의 그림을 기여하세요. 위키백과 문서의 생명이 오는 데 도와주세요!위키백과의 그림은 위키미디어 공용에서 옵니다.당신의 그림은 전 세계 사람들을 교육하는 데 도움이 됩니다.
@@ -163,6 +162,7 @@
2요소 인증은 현재 지원하지 않습니다.정말 로그아웃하시겠습니까?공용 로고
+ 공용 웹사이트배경 그림미디어 그림 실패그림이 없습니다
@@ -187,12 +187,24 @@
피드백로그아웃강좌
+ 알림위치 권한이 없으면 주변 장소를 표시할 수 없습니다설명이 없습니다공용 파일 문서위키데이터 항목그림 캐시 처리 오류
+ 이 파일을 설명할 수 있는 제목으로, 파일 이름으로 사용됩니다. 띄어쓰기를 포함한 일반적인 단어를 사용할 수 있습니다. 파일 확장자는 포함하지 마세요권한 부여외부 저장소 사용하기장치의 인앱 카메라로 찍은 사진 저장하기
+ 로그 파일 보내기
+ 이메일로 개발자에게 로그 파일 보내기
+ 자신의 계정으로 로그인
+ 위치가 변경되지 않았습니다.
+ 위치를 사용할 수 없습니다.
+ 주변 장소의 목록을 표시하기 위한 권한이 필요합니다.
+ %1$s님, 위키미디어 공용에 오신 것을 환영합니다! 반갑습니다.
+ %1$s님이 당신의 사용자 토론 문서에 글을 남겼습니다
+ 편집해 주셔서 감사합니다
+ %1$s님이 %2$s에서 당신을 언급했습니다.
diff --git a/app/src/main/res/values-ku/error.xml b/app/src/main/res/values-ku/error.xml
new file mode 100644
index 000000000..29de239eb
--- /dev/null
+++ b/app/src/main/res/values-ku/error.xml
@@ -0,0 +1,4 @@
+
+
+ Spas!
+
diff --git a/app/src/main/res/values-ku/strings.xml b/app/src/main/res/values-ku/strings.xml
new file mode 100644
index 000000000..6b6c840ec
--- /dev/null
+++ b/app/src/main/res/values-ku/strings.xml
@@ -0,0 +1,58 @@
+
+
+ Commons
+ Eyar
+ Navê bikarhêner
+ Şîfre
+ Têkeve
+ Hesabek nû çêke
+ Têdikeve
+ Ji kerema xwere bisekine ...
+ Têketin serkeftî bû.
+ Têketin biser neket!
+ Dosye nehat dîtin. Ji kerema xwe re dosyeyek din biceribîne.
+ Barkirin dest pê kir!
+ %1$s hat barkirin!
+ Bo barkirina xwe bibînî bitikîne.
+ %1$s tê barkirin
+ Barkirina %1$s bi ser neket
+ Bitikîne û bibîne
+ Barkirinên min ên dawî
+ Bi ser neket
+ Tê barkirin
+ Ji wênedankê
+ Wêneyekî bigre
+ Barkirinên min
+ Parve bike
+ Sernav
+ Danasîn
+ Bar bike
+ Bar bike
+ Li kategoriyan bigere
+ Tomar bike
+ Nû bike
+ GPS ne vekiri ye. Bila vebibe?
+ GPSê veke
+ Hê barkirin nîne
+ Kategorî
+ Eyar
+ Hesabek nû çêke
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Siyaseta Veşarîtiyê</a>
+ Dîsa hewl bide
+ Betal bike
+ Daxe
+ CC BY-SA 3.0
+ CC BY-SA 3.0
+ CC BY-SA 3.0
+ CC BY 4.0
+ CC Zero
+ Erê!
+ Kategorî
+ Tê barkirin…
+ Nû bike
+ Baş e
+ Hişyarî
+ Erê
+ Na
+ Destûr bide
+
diff --git a/app/src/main/res/values-kum/error.xml b/app/src/main/res/values-kum/error.xml
new file mode 100644
index 000000000..706bdae51
--- /dev/null
+++ b/app/src/main/res/values-kum/error.xml
@@ -0,0 +1,6 @@
+
+
+ Вагь. Бир зат чы терс гетди!
+ Этгенигизни язып бизге электрон почгъа йибери. О масъаланы чечме кёмек этежек!
+ Савбол!
+
diff --git a/app/src/main/res/values-ky/strings.xml b/app/src/main/res/values-ky/strings.xml
index 0f54d8df7..517570895 100644
--- a/app/src/main/res/values-ky/strings.xml
+++ b/app/src/main/res/values-ky/strings.xml
@@ -20,7 +20,7 @@
Көрүү үчүн басыңыз1 файл жүктөлүүдө
- %d файл жүктөлүүдө
+ %1$d файл жүктөлүүдөМенин жүктөөлөрүмКезек
@@ -49,18 +49,18 @@
Азырынча жүктөөлөр жок1 жүктөө
- %d жүктөө
+ %1$d жүктөө1 жүктөө башталды
- %d жүктөө башталды
+ %1$d жүктөө башталды1 жүктөө
- %d жүктөө
+ %1$d жүктөө%1$s түрмөктөрү табылган жок
- Уикиказынада Сиздин сүрөттөрдү жеңил табуу үчүн түрмөктөрдү кошуңуз.\n\nТүрмөктөрдү жазууну баштаңыз.\nБул кадамды аттап өтүү үчүн, бул билдирүүнү (же кийинкини) басыңыз.
+ Уикиказынада Сиздин сүрөттөрдү жеңил табуу үчүн түрмөктөрдү кошуңуз.\n\nТүрмөктөрдү жазууну баштаңыз.\nБул кадамды аттап өтүү үчүн, бул билдирүүнү (же кийинкини) басыңыз.ТүрмөктөрЫрастоолорТиркеме жөнүндө
diff --git a/app/src/main/res/values-la/strings.xml b/app/src/main/res/values-la/strings.xml
index 55f209391..5915adde8 100644
--- a/app/src/main/res/values-la/strings.xml
+++ b/app/src/main/res/values-la/strings.xml
@@ -16,7 +16,7 @@
Conficiens immissio %1$sInmissio %1$s non perfectaTangere ad videndum
- Conlationes meae
+ Conlationes meaeIn serieNon perfectum%1$d%% perfectum
@@ -37,12 +37,8 @@
Categorias scrutariServareNullae categoriae %1$s adaequant
- Categorias perspicuitatis Vicimediae causa addere.
-
-Hanc notionem tangere ad id praetermittere.
+ Categorias perspicuitatis Vicimediae causa addere.\n\nHanc notionem tangere ad id praetermittere.Categoriae
- Relationes utendi
- Relationes utendi ad Vicimediam mittereModiDeDe
@@ -61,6 +57,6 @@ Hanc notionem tangere ad id praetermittere.Intellexistine?PergereCategoriae
- Depromens…
+ Depromens…Nulla selecta
diff --git a/app/src/main/res/values-lag/error.xml b/app/src/main/res/values-lag/error.xml
new file mode 100644
index 000000000..a486a36be
--- /dev/null
+++ b/app/src/main/res/values-lag/error.xml
@@ -0,0 +1,4 @@
+
+
+ Kuusa!
+
diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml
index db691c23d..70e72e959 100644
--- a/app/src/main/res/values-lb/strings.xml
+++ b/app/src/main/res/values-lb/strings.xml
@@ -22,7 +22,7 @@
Dréckt fir nach eng Kéier ze probéierenee Fichier eroplueden
- %d Fichieren eroplueden
+ %1$d Fichieren eropluedenMeng rezent eropgeluede FichierenAn der Queue
@@ -55,26 +55,26 @@
Nach keng eropgeluede FichierenN@string/contributions_subtitle_zero
- 1 Fichier eropgelueden
- %d Fichieren eropgelueden
+ Ee Fichier eropgelueden
+ %1$d Fichieren eropgeluedenUgefaang 1 Fichier eropzelueden
- Ugefaang %d Fichieren eropzelueden
+ Ugefaang %1$d Fichieren eropzelueden
- 1 Fichier eropgelueden
- %d Fichieren eropgelueden
+ Ee Fichier eropgelueden
+ %1$d Fichieren eropgeluedenD\'Kategorie %1$s gouf net fonnt
- Fänkt u mat Tippen fir Kategorien derbäizesetzen.\nKlickt op dëse Message (oder dréck op Zréck) fir dëse Schrëtt z\'iwwersprangen.
+ Setzt Kategorien dobäi fir datt Är Biller méi einfach op Wikimedia Commons ze fanne sinn.\n\nFänkt u mat Tippe fir Kategorien dobäizesetzen.KategorienAstellungenMellt Iech unIwwer
- \'Open-Source-Software\' verëffentlecht ënner der <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache Lizenz v2</a>. %1$s a säi Logo si Markenzeeche vun der Wikimedia Foundation a gi mat der Autorisatioun vun der Wikimedia Foundation benotzt. Mir sinn net confirméiert vun oder liéiert mat der Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Quell</a> an <a href=\"https://commons-app.github.io/\">Internetsite</a> vu GitHub.\nLeet w.e.g. <a href=\"https://github.com/commons-app/apps-android-commons/issues\"> e GitHub Problem</a> fir Problemer ze mellen a Proposen ze maachen.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Dateschutzerklärung</a>
+ D\'App Wikimedia Commons ass eng \'Open-Source-App\' déi vu Fräiwëllege vun der Wikimedia Foundation entwéckelt gouf an och vun hinnen ënnerhal gëtt. D\'Wikimedia Foundation ass net an d\'Entwécklung oder den Ënnerhalt vun der App implizéiert.
+ Leet w.e.g. <a href=\"https://github.com/commons-app/apps-android-commons/issues\"> e GitHub Problem</a> fir Problemer ze mellen a Proposen ze maachen.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Dateschutzerklärung</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Merci</a>IwwerFeedback schécken (per E-Mail)
@@ -181,6 +181,7 @@
FeedbackAusloggenUleedung
+ NotifikatiounenPlazen nobäi kënnen net gewise ginn ouni Rechter fir d\'Lokalisatiounkeng Beschreiwung fonntCommons-Fichierssäit
@@ -188,4 +189,13 @@
Autorisatioun ginnExterne Späicher benotzenBiller späicheren déi mat der in-app Kamera vun Ärem Apparat gemaach goufen
+ Log-Fichier schécken
+ Log-Fichier per E-Mail un d\'Entwéckler schécken
+ An Äre Benotzerkont aloggen
+ De Plaz huet net geännert.
+ Plaz ass net disponibel.
+ ARTIKEL LIESEN
+ Wëllkomm op Wikimedia Commons, %1$s! Mir si frou datt Dir hei sidd.
+ Merci datt Dir eng Ännerung gemaach hutt
+ %1$s huet Iech op %2$s ernimmt.
diff --git a/app/src/main/res/values-lez/error.xml b/app/src/main/res/values-lez/error.xml
new file mode 100644
index 000000000..b8d73c3b0
--- /dev/null
+++ b/app/src/main/res/values-lez/error.xml
@@ -0,0 +1,5 @@
+
+
+ Чlурукl хьана
+ Ёъ, вуч-ятlани масакl фена
+
diff --git a/app/src/main/res/values-lrc/error.xml b/app/src/main/res/values-lrc/error.xml
new file mode 100644
index 000000000..e7c9b89ec
--- /dev/null
+++ b/app/src/main/res/values-lrc/error.xml
@@ -0,0 +1,4 @@
+
+
+ دتو منمونیم!
+
diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml
index a82510ff3..43b7fa862 100644
--- a/app/src/main/res/values-lt/strings.xml
+++ b/app/src/main/res/values-lt/strings.xml
@@ -20,11 +20,11 @@
Baigiamas įkelti %1$sĮkelti %1$s nepavykoBakstelėkite norėdami Rodyti
-
- 1 keliamas failas
- %d keliamų failų
+
+ %1$d keliamas failas
+ %1$d keliami failai
- Mano įkėlimai
+ Naujausi mano įkėlimaiEilėjeNepavyko%1$d%% baigta
@@ -50,31 +50,34 @@
Ieškoti kategorijasIšsaugotiAtnaujinti
-
- Dar nėra įkėlimų
+ GPS išjungta jūsų įrenginyje. Ar norite įjungti?
+ Išjungti GPS
+ Nėra įkėlimų kol kas
+
+ \@string/contributions_subtitle_zero1 įkėlimas
- %d įkėlimai
+ %1$d įkėlimai
-
- Pradedamas 1 įkėlimas
- Pradedami %d įkėlimai
+
+ Pradedamas %1$d įkėlimas
+ Pradedami %1$d įkėlimai
-
- 1 įkėlimas
- %d įkėlimai
+
+ %1$d įkėlimas
+ %1$d įkėlimaiNerasta kategorijų, atitinkančiu %1$s
- Pridėkite kategorijas, kad jūsų paveikslėliai būtų lengviau randami Vikimedija Commons.\n\nPradėkite rašyti, kad pridėtumėte kategorijas.\nPaspauskite šią žinutę (ar paspauskite atgal), kad praleistumėte šį žingsnį.
+ Pridėkite kategorijas, kad jūsų paveikslėliai būtų lengviau randami Vikimedija Commons.\n -\n -Pradėkite rašyti, kad pridėtumėte kategorijas.KategorijosNustatymaiUžsiregistruotiApie
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Privatumo politika</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Privatumo politika</a>ApieSiųsti Atsiliepimą (El. paštu)Nėra įdiegtos el. pašto tvarkyklėsNeseniai naudotos kategorijos
- Laukiama pirmo sinchronizavimo
+ Laukiama pirmo sinchronizavimo…Dar neįkėlėte jokių nuotraukų.Bandykite dar kartąAtšaukti
@@ -101,7 +104,7 @@
Ar viską supratote?Taip!Kategorijos
- Įkeliama…
+ Kraunasi...Niekas nepasirinktaNėra aprašymoNežinoma licencija
@@ -116,4 +119,24 @@
NePavadinimasAprašymas
+ Įkėlimo data
+ Licencija
+ Koordinatės
+ Ar tikrai norite atsijungti?
+ Įkelti paveikslėlį
+ Sveiki atvykę į Vikipediją
+ Atšaukti
+ Atidaryti
+ Uždaryti
+ Įkelti
+ Netoliese
+ Apie
+ Nustatymai
+ Atsiliepimai
+ Atsijungti
+ Suteikti leidimą
+ Prisijunkite prie savo paskyros
+ %1$s paliko žinutę jūsų aptarimo puslapyje
+ Dėkoja jums už atliktą pakeitimą
+ %1$s jus paminėjo %2$s.
diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml
index 9d55b0af6..1926747c5 100644
--- a/app/src/main/res/values-lv/strings.xml
+++ b/app/src/main/res/values-lv/strings.xml
@@ -42,8 +42,8 @@
IestatījumiReģistrētiesPar
- Izejas kods pieejams <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>. Kļūdas ziņot <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a>.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Privātuma politika</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Izejas kods</a> un <a href=\"https://commons-app.github.io/\">tīmekļa vietne</a> GitHub. Izveido jaunu <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub pieteikumu</a> kļūdas ziņojumam vai ieteikumam.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Privātuma politika</a>ParNosūtīt atsauksmes (pa e-pastu)Nesen lietotās kategorijas
@@ -94,5 +94,9 @@
IzietApmācībaapraksts nav atrasts
+ AtļautIzmantot ārējo krātuvi
+ LASĪT RAKSTU
+ Paldies par labojumu
+ Pārslēgt skatu
diff --git a/app/src/main/res/values-mg/error.xml b/app/src/main/res/values-mg/error.xml
new file mode 100644
index 000000000..3da2ac057
--- /dev/null
+++ b/app/src/main/res/values-mg/error.xml
@@ -0,0 +1,4 @@
+
+
+ Misaotra indrindra!
+
diff --git a/app/src/main/res/values-mg/strings.xml b/app/src/main/res/values-mg/strings.xml
index 0201801ee..f494577cd 100644
--- a/app/src/main/res/values-mg/strings.xml
+++ b/app/src/main/res/values-mg/strings.xml
@@ -45,7 +45,7 @@
CC AotraEny!Sokajy
- Am-pakàna…
+ Am-pakàna…Tsy misy safidyTsy misy visavisaLisansa tsy fantatra
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index a46933914..8a9811cd0 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -17,12 +17,12 @@
Допрете за да го погледате подигањетоПочнувам со подигањето на „%1$s“Подигање на „%1$s“
- Заврпувам со подигање на „%1$s“
+ Завршувам со подигање на „%1$s“Подигањето на „%1$s“ не успеаДопрете за да погледате
- Се подига %d податотека
- Се подигаат %d податотеки
+ Се подига %1$d податотека
+ Се подигаат %1$d податотекиМои скорешни подигањаЧека ред
@@ -56,26 +56,26 @@
Сè уште нема подигања\@string/contributions_subtitle_zero
- %d подигање
- %d подигања
+ %1$d подигање
+ %1$d подигања
- Започнувам %d подигање
- Започнувам %d подигања
+ Започнувам %1$d подигање
+ Започнувам %1$d подигања
- %d подигање
- %d подигања
+ %1$d подигање
+ %1$d подигањаНема категории што одговараат на %1$s
- Ставете им категории на сликите, за да можат корисниците полесно да ги најдат на Ризницата.\n\nЗа да ставите категорија, почнете со пишување на нејзиното име.\nДопрете ја поракава (или стиснете на назад) за да го прескокнете овој чекор.
+ Ставете им категории на сликите, за да можат корисниците полесно да ги најдат на Ризницата.\n\nЗа да ставите категорија, почнете со пишување на нејзиното име.КатегорииНагодувањаРегистрацијаЗа извршникот
- Програм со отворен код, издаден под лиценцата <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Апачи вер. 2</a>. %1$s и нејзиното лого се заштитни знаци на Фондацијата Викимедија и се користат со нејзина дозвола. Ние не сме поддржани и поврзани со Фондацијата Викимедија.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Извор</a> и <a href=\"https://commons-app.github.io/\">мреж. место</a> на GitHub</a>. Создајте нов <a href=\"https://github.com/commons-app/apps-android-commons/issues\">случај на GitHub</a> за пријавување грешки и давање предлози.
- <a href=\"https://wikimediafoundation.org/wiki/Заштита на личните податоци\">Заштита_на_личните_податоци</a>
+ Прилогот на Ризницата има отворен код. Негови творци и одржувачи се примателите на наменските средства од Викимедиината заедница како и членовите на заедницата. Фондацијата Викимедија нема учество во нејзиното создавање, разработка и одржување.
+ Создајте нов <a href=\"https://github.com/commons-app/apps-android-commons/issues\">проблем на GitHub</a> за пријавување на грешки и давање предлози.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Заштита на личните податоци</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Заслуги</a>За извршникотИспратете Ваше мислење (по е-пошта)
@@ -121,7 +121,7 @@
НЕ подигајте:u2022 Слики од вас или вашите пријатели \nu2022 Слики што сте ги презеле од семрежјето \nu2022 Екрански снимки на неслободни прилози (апликации)Пример за подигање:
- u2022 Наслов: Сиднејска опера \nu2022 Опис: Поглед на Сиднејската опера преку заливот\nu2022 Категорија: Сиднејска опера, Сиднејската опера од запад, Сиднејската опера од далеку
+ - Наслов: Сиднејска опера \n- Опис: Поглед на Сиднејската опера преку заливот\n- Категорија: Сиднејска опера, Сиднејската опера од запад, Сиднејската опера од далекуСподелете ги Вашите слики. Да ги оживееме статиите на Википедија!Сликите на Википедија доаѓаат од Ризницата.Со Вашите слики помагате во образованието на луѓето ширум светот.
@@ -164,6 +164,9 @@
Двочинителската заверка засега не е поддржана.Дали навистина сакате да се одјавите?Лого на Ризницата
+ Ризница
+ Ризницата на Facebook
+ Изворен код на Ризницата на GithubЗаднинска сликаСликата не успеаНе најдов слики
@@ -188,6 +191,7 @@
МислењаОдјаваУпатства
+ ИзвестувањаМестата во близина не можат да се прикажат без дозволи за местоположба.не најдов описиПодатотечна страница
@@ -198,4 +202,17 @@
Дај дозволаКористи надворешен складЗачувување на направените слики во прилогот со камерата на вашиот уред
+ Испрати дневничка податотека
+ Испрати дневничка податотека на разработувачите по е-пошта
+ Најавете се со вашата сметка
+ Местоположбата не е сменета.
+ Местоположбата е недостапна.
+ Се бара дозвола за приказ на список на околни места
+ ДАЈ НАСОКИ
+ ПРОЧИТАЈ СТАТИЈА
+ Добре дојдовте на Ризницата, %1$s! Драго ни е што сте тука.
+ %1$s ви остави порака на разговорната страница
+ Ви благодариме за уредувањето
+ %1$s ве спомна на %2$s.
+ Смени поглед
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index 07d87d2cb..fe59034e2 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -20,7 +20,7 @@
കാണാനായി ടാപ് ചെയ്യുക1 പ്രമാണം അപ്ലോഡ് ചെയ്യുന്നു
- %d പ്രമാണങ്ങൾ അപ്ലോഡ് ചെയ്യുന്നു
+ %1$d പ്രമാണങ്ങൾ അപ്ലോഡ് ചെയ്യുന്നുഎന്റെ അപ്ലോഡുകൾനിരയായി വെച്ചു
@@ -49,18 +49,18 @@
ഒരു അപ്ലോഡും ചെയ്തില്ലഒരു അപ്ലോഡ്
- %d അപ്ലോഡുകൾ
+ %1$d അപ്ലോഡുകൾഒരു അപ്ലോഡ് തുടങ്ങുന്നു
- %d അപ്ലോഡുകൾ തുടങ്ങുന്നു
+ %1$d അപ്ലോഡുകൾ തുടങ്ങുന്നുഒരു അപ്ലോഡ്
- %d അപ്ലോഡുകൾ
+ %1$d അപ്ലോഡുകൾ%1$s എന്നതുമായി പൊരുത്തമുള്ള ഒരു വർഗ്ഗവും കണ്ടെത്താനായില്ല
- താങ്കളുടെ ചിത്രങ്ങൾ വിക്കിമീഡിയ കോമൺസിൽ കൂടുതൽ എളുപ്പത്തിൽ കണ്ടെത്തപ്പെടാനായി വർഗ്ഗങ്ങൾ ചേർക്കുക.\n\nവർഗ്ഗങ്ങൾ ചേർക്കാനായി ടൈപ്പ് ചെയ്ത് തുടങ്ങുക.\nഈ ഘട്ടം ഒഴിവാക്കാൻ ടാപ് ചെയ്യുക (അല്ലെങ്കിൽ പിന്നോട്ട് പോവുക).
+ താങ്കളുടെ ചിത്രങ്ങൾ വിക്കിമീഡിയ കോമൺസിൽ കൂടുതൽ എളുപ്പത്തിൽ കണ്ടെത്തപ്പെടാനായി വർഗ്ഗങ്ങൾ ചേർക്കുക.\n\nവർഗ്ഗങ്ങൾ ചേർക്കാനായി ടൈപ്പ് ചെയ്ത് തുടങ്ങുക.\nഈ ഘട്ടം ഒഴിവാക്കാൻ ടാപ് ചെയ്യുക (അല്ലെങ്കിൽ പിന്നോട്ട് പോവുക).വർഗ്ഗങ്ങൾസജ്ജീകരണങ്ങൾവിവരണം
diff --git a/app/src/main/res/values-mn/error.xml b/app/src/main/res/values-mn/error.xml
new file mode 100644
index 000000000..15872699e
--- /dev/null
+++ b/app/src/main/res/values-mn/error.xml
@@ -0,0 +1,4 @@
+
+
+ Баярлалаа!
+
diff --git a/app/src/main/res/values-mn/strings.xml b/app/src/main/res/values-mn/strings.xml
index e07e87ffe..a7ac497d5 100644
--- a/app/src/main/res/values-mn/strings.xml
+++ b/app/src/main/res/values-mn/strings.xml
@@ -18,7 +18,7 @@
%1$s upload хийж дуусаж байна%1$s upload хийхэд алдааЭнд дарж харна
- Миний хийсэн upload
+ Миний хийсэн uploadЖагсаалтад орууллааАлдаа гарлаа%1$d%% хийгдсэн
@@ -42,7 +42,6 @@
Нэр төрлөөр хайхХадгалахНэр төрөл
- Хэрэглээний мэдээлэл:ТохиргооТухайТухай
diff --git a/app/src/main/res/values-mr/strings.xml b/app/src/main/res/values-mr/strings.xml
index 03d8c28f2..f7f65ef00 100644
--- a/app/src/main/res/values-mr/strings.xml
+++ b/app/src/main/res/values-mr/strings.xml
@@ -22,7 +22,7 @@
बघण्यास अलगद टपली मारा१ संचिका अपभारीत आहे
- %d संचिका अपभारीत आहे
+ %1$d संचिका अपभारीत आहेमाझी अपभारणेप्रतिक्षावलीत ठेवले
@@ -53,18 +53,18 @@
अद्याप अपभारणे नाहीतएक अपभारण
- %d अपभारणे
+ %1$d अपभारणे१ अपभारण सुरू करीत आहे upload
- %d अपभारणे सुरू करीत आहे
+ %1$d अपभारणे सुरू करीत आहे१ अपभारण
- %d अपभारणे
+ %1$d अपभारणे%1$s शी जुळणारे कोणतेच वर्ग सापडले नाहीत
- विकिमिडिया कॉमन्सवर आपल्या संचिकांना शोधणे सोपी जाण्यासाठी त्यांना वर्ग जोडा.\n\nवर्ग जोडण्यास टंकन सुरू करा.\nही पायरी चुकविण्यास या संदेशावर अलगद टिचका (किंवा बॅक कळ टिचका)
+ विकिमिडिया कॉमन्सवर आपल्या संचिकांना शोधणे सोपी जाण्यासाठी त्यांना वर्ग जोडा.\n\nवर्ग जोडण्यास टंकन सुरू करा.\nही पायरी चुकविण्यास या संदेशावर अलगद टिचका (किंवा बॅक कळ टिचका)वर्गमांडण्यानोंदणी करा
@@ -105,7 +105,7 @@
कृपया अपभारण करू \'\'\'नका\'\'\':-आपल्या किंवा मित्रांच्या सेल्फी\n-आपण आंतरजाल (इंटरनेट) वरुन अधिभारण केलेली चित्रे\n-प्रोप्रायटरी अॲप्सचे स्क्रिनशॉटउदाहरणादाखल अपभारण:
- - शीर्षक: Sydney Opera House\n- वर्णन: Sydney Opera House as viewed from across the bay\n- वर्ग: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - शीर्षक: Sydney Opera House\n- वर्णन: Sydney Opera House as viewed from across the bay\n- वर्ग: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsआपली चित्रे सहभागा. विकिपीडियावरील लेखात जीवंतपणा आणा!विकिपीडियावरची चित्रे विकिमिडिया कॉमन्सवरुन येतात.जगातल्या अनेक लोकांना आपल्या चित्राद्वारे शिकता येते.
diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml
index 5055b94b9..7c19027e3 100644
--- a/app/src/main/res/values-ms/strings.xml
+++ b/app/src/main/res/values-ms/strings.xml
@@ -43,18 +43,18 @@
Muat naikCari dalam kategoriSimpan
-
+ Belum ada muatnaik
- %d muatnaik
- %d muatnaik
+ %1$d muatnaik
+ %1$d muatnaikMemulakan %d kerja muat naik
-
- %d muatnaik
- %d muatnaik
+
+ %1$d muatnaik
+ %1$d muatnaikTiada kategori yang sepadan dengan %1$s dijumpai
- Tambahkan kategori supaya gambar-gambar anda lebih senang ditemui di Wikimedia Commons.\n\nMula menaip untuk menambahkan kategori.\nKetik mesej ini (atau tekan butang undur) untuk melangkau langkah ini.
+ Tambahkan kategori supaya gambar-gambar anda lebih senang ditemui di Wikimedia Commons.\n\nMula menaip untuk menambahkan kategori.\nKetik mesej ini (atau tekan butang undur) untuk melangkau langkah ini.KategoriTetapanPerihal
diff --git a/app/src/main/res/values-mt/strings.xml b/app/src/main/res/values-mt/strings.xml
index dde8ddcbd..7f5750ca0 100644
--- a/app/src/main/res/values-mt/strings.xml
+++ b/app/src/main/res/values-mt/strings.xml
@@ -5,7 +5,7 @@
PasswordIdħolDieħel
- Jekk jogħġbok stenna…
+ Jekk jogħġbok stenna…Dħalt b\'suċċess!Id-dħul fil-kont ma rnexxiex!L-awtentikar falla!
@@ -17,7 +17,7 @@
It-tlugħ ta\' %1$s spiċċaIt-tlugħ ta\' %1$s ma rnexxiexAgħfas biex tara
- It-tlugħ tiegħi
+ It-tlugħ tiegħiFil-kjuMa rnexxiex%1$d%% komplut
@@ -38,18 +38,13 @@
Fittex fil-kategorijiSalvaL-ebda kategorija ma nstabet li għandha %1$s
- Żid kategoriji sabiex l-istampi tiegħek ikunu skoperti iktar fuq Wikimedia Commons.
-
-Ibda ikteb sabiex iżżid kategoriji.
-Agħfas fuq dan il-messaġġ (jew mur lura) sabiex taqbeż dan il-pass.
+ Żid kategoriji sabiex l-istampi tiegħek ikunu skoperti iktar fuq Wikimedia Commons.\n\nIbda ikteb sabiex iżżid kategoriji.\nAgħfas fuq dan il-messaġġ (jew mur lura) sabiex taqbeż dan il-pass.Kategoriji
- Rapporti tal-użu
- Ibgħat ir-rapporti tal-użu lill-Wikimedia sabiex tgħinna ntejbdu l-appIssettjarDwarSoftwer ta\' sors miftuħ rilaxxat taħt il-<a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Liċenzja Apache v2</a>Sors fuq <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>. Żbalji fuq <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a>.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Politika tal-privatezza</a>
+ <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Politika tal-privatezza</a>DwarKategoriji wżati riċentamentGħadek ma tellgħajt l-ebda ritratt.
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index f5dd2164a..25eb9ee4e 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -20,9 +20,9 @@
Avslutter opplasting av %1$sOpplastingen av %1$s feiletTrykk for å vise
-
- %d fil lastes opp
- %d filer lastes opp
+
+ %1$d fil lastes opp
+ %1$d filer lastes oppMine nylige opplastingerI ventekø
@@ -54,28 +54,28 @@
GPS er slått av på denne enheten. Ønsker du å slå den på?Slå på GPSIngen opplastinger ennå
-
+ \@string/contributions_subtitle_zero
- %d opplasting
- %d opplastinger
+ %1$d opplasting
+ %1$d opplastinger
- Starter %d opplasting
- Starter %d opplastinger
+ Starter %1$d opplasting
+ Starter %1$d opplastinger
-
- %d opplasting
- %d opplastinger
+
+ %1$d opplasting
+ %1$d opplastingerIngen kategorier som stemte overens med %1$s funnet
- Legg til kategorier for å gjøre bildene dine lettere å finne på Wikimedia Commons.\n\nBegynn å skrive navnet på kategoriene.\nTrykk på denne meldingen (eller trykk tilbake) for å hoppe over dette steget.
+ Legg til kategorier for å gjøre bildene dine lettere å finne på Wikimedia Commons.\n\nBegynn å skrive navnet på kategoriene.KategorierInnstillingerRegistrer degOm
- Programvare med åpen kildekode sluppet under <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. %1$s og dets logo er varemerker fra Wikimedia Foundation og brukes med tillatelse fra dem. Vi er verken støttet av eller koblet til Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Kildekode</a> og <a href=\"https://commons-app.github.io/\">nettside</a> på GitHub. Opprett en ny <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-sak</a> for feilrapporter og forslag.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Personvernpolicy</a>
+ Wikimedia Commons-appen er åpen kildekode og er skapt og vedlikeholdt av stipendiater og frivillige fra Wikimedia-fellesskapet. Wikimedia Foundation er ikke involvert i utviklingen eller vedlikeholdet av appen.
+ Opprett en ny <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-sak</a> for feilrapporter og forslag.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Personvernpolicy</a><a href=\"https://github.com/commons-app/apps-android-comons/blob/master/CREDITS\">Bidragsytere</a>OmSend tilbakemelding (via Epost)
@@ -121,7 +121,7 @@
Vennligst IKKE last opp:- Selfies eller bilder av vennene dine\n- Bilder som du har lastet ned fra internet\n- Skjermbilder tatt fra proprietære apperOpplastingseksempel:
- - Tittel: Sydneys Operahus\n- Beskrivelse: Operahuset i Sydney sett fra andre siden av bukten\n- Kategorier: Operahuset i Sydney, Operahuset i Sydney ifra vest, Operahuset i Sydney utenfra
+ - Tittel: Sydneys operahus\n- Beskrivelse: Operahuset i Sydney sett fra andre siden av bukten\n- Kategorier: Operahuset i Sydney ifra vest, Operahuset i Sydney utenfraBidra med dine bilder. Hjelp til med å blåse liv i Wikipedias artikler!Bilder på Wikipedia kommer fra Wikimedia Commons.Bildene dine kan være til hjelp for mennesker over hele verden som søker kunnskap og dannelse.
@@ -164,6 +164,9 @@
Tofaktorautentisering støttes ikke ennå.Ønsker du virkelig å logge ut?Commons-logo
+ Commons-nettsted
+ Commons\' Facebook-side
+ Commons\' kildekode på GitHubBakgrunnsbildeMediebilde feiletIntet bilde funnet
@@ -188,6 +191,7 @@
TilbakemeldingerLogg utVeiviser
+ VarslerSteder i nærheten kan ikke vises uten tillatelse for stedsbestemmelseingen beskrivelse funnetCommons-filside
@@ -198,4 +202,16 @@
Gi tillatelseBruk ekstern lagringLagre bilder som er tatt med kameraet i appen på enheten din
+ Send loggfil
+ Send loggfil til utviklerne via epost
+ Logg inn med kontoen din
+ Stedet har ikke blitt endret.
+ Sted ikke tilgjengelig.
+ Tillatelse kreves for å vise listen over steder i nærheten
+ FÅ VEIBESKRIVELSE
+ LES ARTIKKEL
+ Velkommen til Wikimedia Commons, %1$s! Vi er glade for at du er her.
+ %1$s la igjen en beskjed på diskusjonssiden din
+ Takk for at du har gjort en redigering
+ %1$s nevnte deg på %2$s.
diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml
index 86397339d..10be9f8f8 100644
--- a/app/src/main/res/values-ne/strings.xml
+++ b/app/src/main/res/values-ne/strings.xml
@@ -1,7 +1,7 @@
- विकिमीडिया कमन्स
- सेटिङ्गहरू
+ कमन्स
+ सेटिङहरूप्रयोगकर्ता नामपासवर्डप्रवेश
@@ -9,8 +9,9 @@
कृपया प्रतिक्षा गर्नुहोस् …प्रवेश सफल!प्रवेश सफल हुन सकेन!
+ फाइल भेटिएन। कृपया अर्को फाइल प्रयास गर्नुहोस्।प्रमाणिकरण असफल भयो!
- अपलोड सुरु भयो!
+ अपलोड शुरू भयो!%1$s अपलोड गरियो !तपाईंको अपलोड हेर्नको लागि ट्याप गर्नुहोस् %1$s अपलोड सुरू गर्दैै
@@ -18,13 +19,14 @@
%1$s अपलोड सकाउँदै %1$s अपलोड असफल भयोहेर्नको लागि ट्याप गर्नुहोस्
- मेरा अपलोडहरू
+ मेरा हालैका अपलोडहरूलाममा राखियोअसफल भयो%1$d%% पूरा भयोअपलोड हुँदैग्यालरीबाटफोटो खिच्नुहोस्
+ वरिपरिकामेरा अपलोडहरूबाड्नेब्राउजरमा हेर्ने
@@ -43,7 +45,7 @@
खोज श्रेणीहरूसंग्रह गर्नेमिल्दो वर्ग %1$s फेला परेन
- विकिमीडिया कमन्समा आफ्नो तस्वीरहरू थप भेट्टाउने बनाउन वर्नहरू थप्नुहोस।\n\nवर्गहरू थप्नको लागि टाइप गर्न सुरु गर्नुहोस्।\nयो चरण छोड्न यो सन्देशमा ट्याप गर्नुहोस् (वा पछाडि जाने थिच्नुहोस्)।
+ विकिमिडिया कमन्समा आफ्नो तस्वीरहरू थप भेट्टाउने बनाउन श्रेणीहरू थप्नुहोस।\n\nश्रेणीहरू थप्नको लागि टाइप गर्न सुरु गर्नुहोस्।\nयो चरण छोड्न यो सन्देशमा ट्याप गर्नुहोस् (वा पछाडि जानेमा थिच्नुहोस्)।श्रेणीहरूसेटिङ्गहरूबारेमा
@@ -76,8 +78,8 @@
CC BY-SA ३.० (रोमानिया)CC BY-SA ३.०CC शुन्य
- आफ्नो तस्वीरहरूको योगदान दिनुहोस्। विकिपीडिया लेखलाई जीवनमा आउन मद्दत गर्नुहोस्!
- विकिपीडियाका तस्वीरहरू विकिमीडिया कमन्सबाट आउँछन्।
+ आफ्नो तस्वीरहरूको योगदान दिनुहोस्। विकिपिडियाका लेखलाई जीवनन्तता पाउन मद्दत गर्नुहोस्!
+ विकिपिडियाका तस्विरहरू विकिमिडिया कमन्सबाट आउँछन्।तपाईंको तस्वीरहरूले संसारभरिका मानिसहरूलाई शिक्षित बनाउन मद्दत गर्छन्।तपाईंले इन्टरनेटमा भेट्टाउनुभएका कपिराइट भएका सामग्रीहरू साथै पोस्टरहरू, पुस्तक आवरणहरू, आदिका तस्वीरहरू रोक्नुहोस्।के तपाईंले कुरा बुझ्नु भए जस्तो लाग्छ ?
@@ -88,4 +90,5 @@
वर्णन छैनअज्ञान अनुमतिपत्रताजागर्ने
+ टगल दृश्य
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 5483056e8..69c35cada 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -21,8 +21,8 @@
Het uploaden van %1$s is misluktWijs aan om te bekijken
- %d bestand aan het uploaden
- %d bestanden aan het uploaden
+ %1$d bestand aan het uploaden
+ %1$d bestanden aan het uploadenMijn uploadsIn wachtrij
@@ -54,24 +54,24 @@
Nog geen uploads1 upload
- %d uploads
+ %1$d uploadsBezig met 1 upload
- Bezig met %d uploads
+ Bezig met %1$d uploads1 upload
- %d uploads
+ %1$d uploadsEr zijn geen categorieën met \"%1$s\" gevonden
- Voeg categorieën toe om uw afbeeldingen makkelijker te vinden te maken op Wikimedia Commons.\n\nBegin met het toevoegen van categorieën.\n\nKlik op dit bericht, of ga terug, om deze stap over te slaan.
+ Voeg categorieën toe om uw afbeeldingen makkelijker te vinden te maken op Wikimedia Commons.\n\nBegin met het toevoegen van categorieën.\n\nKlik op dit bericht, of ga terug, om deze stap over te slaan.CategorieënInstellingenRegistrerenOverOpensourcesoftware vrijgegeven onder de <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. Wikimedia Commons en haar logo zijn handelsmerken van de Wikimedia Foundation en worden gebruikt met toestemming van de Wikimedia Foundation. We worden niet bekrachtigd door en zijn niet verbonden met de Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Broncode</a> en <a href=\"https://commons-app.github.io/\">website</a> op GitHub. Maak een nieuwe <a href=\"https://github.com/commons-app/apps-android-commons/issues\">melding op GitHub</a> voor bugs en suggesties.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Broncode</a> en <a href=\"https://commons-app.github.io/\">website</a> op GitHub. Maak een nieuwe <a href=\"https://github.com/commons-app/apps-android-commons/issues\">melding op GitHub</a> voor bugs en suggesties.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Privacybeleid</a>OverTerugkoppeling geven (via e-mail)
@@ -116,7 +116,7 @@
Gelieve NIET te uploaden:- Selfies of foto\'s van uw vrienden\n- Foto\'s die u hebt gedownload van het Internet\n- Screenshots van eigen appsVoorbeeld van een upload:
- - Titel: Sydney Opera House\n- Beschrijving: het Sydney Opera House gezien vanaf de overkant van de baai\n- Categorieën: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Titel: Sydney Opera House\n- Beschrijving: het Sydney Opera House gezien vanaf de overkant van de baai\n- Categorieën: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsDraag uw afbeelding bij. Help pagina\'s in Wikipedia tot leven te laten komen!Afbeeldingen op Wikipedia komen van Wikimedia Commons.Uw afbeeldingen helpen mensen van over de hele wereld te leren.
diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml
index e50e29ae3..548be1198 100644
--- a/app/src/main/res/values-oc/strings.xml
+++ b/app/src/main/res/values-oc/strings.xml
@@ -20,9 +20,9 @@
Fin del telecargament %1$sTelecargament fracassat a %1$sQuichar per afichar
-
- %d fichièr en cors de cargament
- %d fichièrs en cors de cargament
+
+ %1$d fichièr en cors de cargament
+ %1$d fichièrs en cors de cargamentMos darrièrs cargamentsMes en fila d\'espèra
@@ -50,25 +50,25 @@
Recercar de categoriasEnregistrarRefrescar
-
+ \@string/contributions_subtitle_zero
- %d cargament
- %d cargaments
+ %1$d cargament
+ %1$d cargaments
-
- %d cargament aviat
- %d cargaments aviats
+
+ %1$d cargament aviat
+ %1$d cargaments aviats
-
- %d cargament
- %d cargaments
+
+ %1$d cargament
+ %1$d cargamentsCap de categoria que correspond a %1$s pas trobadaCategoriasParamètresS’inscriureA prepaus
- <a href=\"https://github.com/commons-app/apps-android-commons\">Fonts</a> e <a href=\"https://commons-app.github.io/\">site web</a> sus GitHub. Crear un novèl <a href=\"https://github.com/commons-app/apps-android-commons/issues\">senhalament GitHub</a> per senhalar de bugs o de suggestions.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Fonts</a> e <a href=\"https://commons-app.github.io/\">site web</a> sus GitHub. Crear un novèl <a href=\"https://github.com/commons-app/apps-android-commons/issues\">senhalament GitHub</a> per senhalar de bugs o de suggestions.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Politica de confidencialitat</a>A prepausMandar vòstres comentaris (per corrièl)
diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml
index 858dabfe8..924d2a5fe 100644
--- a/app/src/main/res/values-or/strings.xml
+++ b/app/src/main/res/values-or/strings.xml
@@ -43,7 +43,7 @@
ଶ୍ରେଣୀଗୁଡ଼ିକ ଖୋଜନ୍ତୁସାଇତିବେ [Save]%1$s ସହ ମେଳ ହେଉଥିବା କୌଣସି ଶ୍ରେଣୀ ମିଳିଲା ନାହିଁ
- ଶ୍ରେଣୀମାନ ଯୋଡ଼ିବା ପାଇଁ ଟାଇପ କରନ୍ତୁ ।\nଏହି ସୋପାନଟି ବାଦ ଦେବା ପାଇଁ ମେସେଜଟି ଉପରେ ଟ୍ୟାପ କରନ୍ତୁ (କିମ୍ବା ପଛକୁ ଫେରନ୍ତୁ) ।
+ ଶ୍ରେଣୀମାନ ଯୋଡ଼ିବା ପାଇଁ ଟାଇପ କରନ୍ତୁ ।\nଏହି ସୋପାନଟି ବାଦ ଦେବା ପାଇଁ ମେସେଜଟି ଉପରେ ଟ୍ୟାପ କରନ୍ତୁ (କିମ୍ବା ପଛକୁ ଫେରନ୍ତୁ) ।ଶ୍ରେଣୀସମୂହସଂରଚନାବାବଦରେ
diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml
index a67dcbf5b..a0d00bc5a 100644
--- a/app/src/main/res/values-pa/strings.xml
+++ b/app/src/main/res/values-pa/strings.xml
@@ -21,7 +21,7 @@
ਵੇਖਣ ਲਈ ਥਪੇੜੋ੧ ਫ਼ਾਈਲ ਚੜ੍ਹਾਈ ਜਾ ਰਹੀ ਹੈ
- %d ਫ਼ਾਈਲਾਂ ਚੜ੍ਹਾਈਆਂ ਜਾ ਰਹੀਆਂ ਹਨ
+ %1$d ਫ਼ਾਈਲਾਂ ਚੜ੍ਹਾਈਆਂ ਜਾ ਰਹੀਆਂ ਹਨਮੇਰੇ ਅੱਪਲੋਡਕਤਾਰ ਵਿਚ
@@ -54,19 +54,19 @@
ਫ਼ਿਲਹਾਲ ਕੋਈ ਅੱਪਲੋਡ ਨਹੀਂ\@string/contributions_subtitle_zero
- %d upload
- %d ਅੱਪਲੋਡ
+ %1$d upload
+ %1$d ਅੱਪਲੋਡ
- %d ਅੱਪਲੋਡ ਸ਼ੁਰੂ ਹੋ ਰਹੀ ਹੈ
- %d ਸ਼ੁਰੂ ਹੋ ਰਹੇ ਹਨ
+ %1$d ਅੱਪਲੋਡ ਸ਼ੁਰੂ ਹੋ ਰਹੀ ਹੈ
+ %1$d ਸ਼ੁਰੂ ਹੋ ਰਹੇ ਹਨ&d ਅੱਪਲੋਡ
- %d ਅੱਪਲੋਡਾਂ
+ %1$d ਅੱਪਲੋਡਾਂ%1$s ਨਾਲ਼ ਮੇਲ ਖਾਂਦੀ ਕੋਈ ਸ਼੍ਰੇਣੀ ਨਹੀਂ ਲੱਭੀ
- ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਵਿਚ ਜ਼ਿਆਦਾ ਲੱਭਣਯੋਗ ਬਣਾਉਣ ਲਈ ਸ਼੍ਰੇਣੀਆਂ ਜੋੜੋ।\n\nਸ਼੍ਰੇਣੀਆਂ ਜੋੜਨ ਲਈ ਟਾਈਪ ਕਰਨ ਅਰੰਭ ਕਰੋ।\nਇਸ ਕਾਰਜ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰਨ ਲਈ ਇਹ ਸੁਨੇਹਾ ਥਪੇੜੋ (ਜਾਂ ਵਾਪਸੀ ਬਟਨ ਦਬਾਓ)।
+ ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਵਿਚ ਜ਼ਿਆਦਾ ਲੱਭਣਯੋਗ ਬਣਾਉਣ ਲਈ ਸ਼੍ਰੇਣੀਆਂ ਜੋੜੋ।\n\nਸ਼੍ਰੇਣੀਆਂ ਜੋੜਨ ਲਈ ਟਾਈਪ ਕਰਨ ਅਰੰਭ ਕਰੋ।\nਇਸ ਕਾਰਜ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰਨ ਲਈ ਇਹ ਸੁਨੇਹਾ ਥਪੇੜੋ (ਜਾਂ ਵਾਪਸੀ ਬਟਨ ਦਬਾਓ)।ਸ਼੍ਰੇਣੀਆਂਸੈਟਿੰਗਸਾਈਨ ਅੱਪ
@@ -118,7 +118,7 @@
ਕਿਰਪਾ ਕਰਕੇ ਅਪਲੋਡ ਨਾ ਕਰੋ:- ਸੈਲਫ਼ੀਆਂ ਜਾਂ ਤੁਹਾਡੇ ਦੋਸਤਾਂ ਦੀਆਂ ਤਸਵੀਰਾਂ\n- ਜੋ ਤਸਵੀਰਾਂ ਤੁਸੀਂ ਇੰਟਰਨੈੱਟ ਤੋਂ ਡਾਊਨਲੋਡ ਕੀਤੀਆਂ ਹਨ\n- ਨਿੱਜੀ ਐਪਲੀਕੇਸ਼ਨਾਂ ਦੇ ਸਕਰੀਨਸ਼ੌਟਉਦਾਹਰਣ ਵਜੋਂ ਇਹ ਅਪਲੋਡ:
- - ਸਿਰਲੇਖ: Sydney Opera House\n- ਵੇਰਵਾ: ਸਿਡਨੀ ਓਪੇਰਾ ਹਾਊਸ ਦੀ ਤਸਵੀਰ\n- ਸ਼੍ਰੇਣੀਆਂ: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - ਸਿਰਲੇਖ: Sydney Opera House\n- ਵੇਰਵਾ: ਸਿਡਨੀ ਓਪੇਰਾ ਹਾਊਸ ਦੀ ਤਸਵੀਰ\n- ਸ਼੍ਰੇਣੀਆਂ: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਯੋਗਦਾਨ ਪਾਓ। ਵਿਕੀਪੀਡੀਆ ਲੇਖਾਂ ਨੂੰ ਸੁਰਜੀਤ ਕਰ ਦਿਓ!ਵਿਕੀਪੀਡੀਆ ਉਤਲੀਆਂ ਤਸਵੀਰਾਂ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਤੋਂ ਆਉਂਦੀਆਂ ਹਨਤੁਹਾਡੀਆਂ ਤਸਵੀਰਾਂ ਦੁਨੀਆਂ ਭਰ ਦੇ ਲੋਕਾਂ ਨੂੰ ਪੜ੍ਹਨ ਵਿਚ ਮਦਦ ਕਰਦੀਆਂ ਹਨ।
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index f2c8f4eeb..20fc55610 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -21,8 +21,8 @@
Wysyłanie %1$s nieudaneDotknij, aby zobaczyć
- Przesyłanie %d pliku
- Przesyłanie %d plików
+ Przesyłanie %1$d pliku
+ Przesyłanie %1$d plikówNiedawno przesłaneW kolejce
@@ -56,25 +56,25 @@
Na razie brak przesłanych plików!Przesłano @string/contributions_subtitle_zero
- Przesłano %d plik
- Przesłano %d pliki
+ Przesłano %1$d plik
+ Przesłano %1$d pliki
- Rozpoczęto %d przesyłanie
- Rozpoczęto %d przesyłania
+ Rozpoczęto %1$d przesyłanie
+ Rozpoczęto %1$d przesyłania
- %d przesłanie
- %d przesłania
+ %1$d przesłanie
+ %1$d przesłaniaNie znaleziono kategorii pasujących do %1$s
- Dodaj kategorie, aby ułatwić odnalezienie plików w Wikimedia Commons.\n\nZacznij pisać, aby dodać kategorię.\nKliknij ten komunikat lub naciśnij „cofnij”, aby pominąć ten krok.
+ Dodaj kategorie, aby ułatwić odnalezienie plików w Wikimedia Commons.\nZacznij pisać, aby dodać kategorie.KategorieUstawieniaZarejestruj sięO aplikacjiOprogramowanie Open Source, wydane na licencji <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. Wikimedia Commons i jego logo są znakami towarowymi Wikimedia Foundation i są wykorzystywane za zgodą Wikimedia Foundation. Nie jesteśmy powiązani z Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Kod źródłowy</a> oraz <a href=\"https://commons-app.github.io/\">strona internetowa</a> na GitHub. Aby zgłosić błąd lub sugestię, utwórz nowe <a href=\"https://github.com/commons-app/apps-android-commons/issues\">zgłoszenie na GitHub</a>.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Kod źródłowy</a> oraz <a href=\"https://commons-app.github.io/\">strona internetowa</a> na GitHub. Aby zgłosić błąd lub sugestię, utwórz nowe <a href=\"https://github.com/commons-app/apps-android-commons/issues\">zgłoszenie na GitHub</a>.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Polityka prywatności</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Twórcy</a>O aplikacji
@@ -120,7 +120,7 @@
Prosimy NIE wysyłać:- Zdjęć przedstawiających siebie lub znajomych\n- Zdjęć pobranych z Internetu\n- Zrzutów ekranów płatnych aplikacjiPrzykład przesłania pliku:
- - Nazwa: Zamek Królewski w Warszawie\n- Opis: Zamek Królewski w Warszawie, widok od strony kolumny Zygmunta\n- Kategorie: Western facade (Royal Castle, Warsaw), Southern facade (Royal Castle, Warsaw), Castle Square in Warsaw
+ - Nazwa: Zamek Królewski w Warszawie\n- Opis: Zamek Królewski w Warszawie, widok od strony kolumny Zygmunta\n- Kategorie: Western facade (Royal Castle, Warsaw), Southern facade (Royal Castle, Warsaw), Castle Square in WarsawDodaj swoje ilustracje. Pomóż ożyć artykułom w Wikipedii!Ilustracje w Wikipedii pochodzą z Wikimedia Commons.Twoje ilustracje pomagają w edukacji ludzi na całym świecie.
@@ -182,4 +182,7 @@
nie znaleziono opisuElement WikidanychPodaj krótką, opisową i unikalną nazwę, która będzie służyła jako nazwa pliku. Możesz używać prostego języka i spacji. Nie dodawaj rozszerzenia pliku.
+ Zaloguj się na swoje konto
+ Witamy w Wikimedia Commons, %1$s! Cieszymy się, że tu jesteś.
+ %1$s wspomniał o Tobie w %2$s.
diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml
index 55c2e5e21..42347b7ee 100644
--- a/app/src/main/res/values-pms/strings.xml
+++ b/app/src/main/res/values-pms/strings.xml
@@ -21,8 +21,8 @@
Cariament falì a %1$sSgnaché për ësmon-e
- %d archivi an camin ch\'as caria
- %d archivi an camin ch\'as cario
+ %1$d archivi an camin ch\'as caria
+ %1$d archivi an camin ch\'as carioIj mè cariament recentAn coa
@@ -56,25 +56,25 @@
Ancor gnun cariament\@string/contributions_subtitle_zero
- %d cariament
- %d cariament
+ %1$d cariament
+ %1$d cariament
- %d cariament ancaminà
- %d cariament ancaminà
+ %1$d cariament ancaminà
+ %1$d cariament ancaminà
- %d cariament
- %d cariament
+ %1$d cariament
+ %1$d cariamentGnun-e categorìe rëspondente a %1$s trovà
- Ch\'a gionta dle categorìe, për ch\'a sia pi belfé trové soe plance su Wikimedia Commons.\n\nCh\'a ancamin-a a scrive për gionté dle categorìe.\nCh\'a sgnaca an s\'ën mëssage (o ch\'a sgnaca ël boton andaré) për sauté \'s pass.
+ Ch\'a gionta dle categorìe, për ch\'a sia pi belfé trové soe plance su Wikimedia Commons.\nCh\'a ancamin-a a scrive për gionté dle categorìe.CategorìeParàmeterMarchesseA propòsitL\'aplicassion Wikimedia Commons a l\'é n\'aplicassion a sorgiss duverta creà e mantnùa da \'d përson-e pagà e da \'d volontari ëd la comunità Wikimedia. La Fondassion Wikimedia a l\'é nen amplicà ant la creassion, ël dësvlup, o la manutension dl\'aplicassion.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Sorgiss</a> e <a href=\"https://commons-app.github.io/\">sit an sl\'aragnà</a> su GitHub. Creé na neuva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">signalassion GitHub</a> për signalé dij givo e dij sugeriment.
+ Creé na neuva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">signalassion GitHub</a> për signalé dij givo e dij sugeriment.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Régole ëd confidensialità</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Ringrassiament</a>A propòsit
@@ -121,7 +121,7 @@
Për piasì, ch\'a caria NEN:- dj\'àuto-scat o dle fòto dij sò amis\n- dle fòto ch\'a l\'ha dëscarià d\'an sl\'Aragnà\n- Plance d\'aplicassion sota drit d\'autorEsempi ëd cariament:
- - Tìtol: Ël teatro dl\'òpera ëd Sidney\n- Descrission: Ël teatro dl\'òpera ëd Sidney s-ciairà dëdlà dl\'ansen\n- Categorìe: Teatro dl\'òpera ëd Sidney, Teatro dl\'òpera ëd Sidney da ponent, viste a distansa dël Teatro dl\'òpera ëd Sydney
+ - Tìtol: Ël teatro dl\'òpera ëd Sidney\n- Descrission: Ël teatro dl\'òpera ëd Sidney s-ciairà dëdlà dl\'ansen\n- Categorìe: Teatro dl\'òpera ëd Sidney da ponent, viste a distansa dël Teatro dl\'òpera ëd SydneyCh\'a contribuissa con soe plance. Ch\'a giuta a j\'artìcoj ëd Wikipedia a pijé vita!Le plance su Wikipedia a ven-o da Wikimedia Commons.Soe plance a giuto a eduché la gent daspërtut ant ël mond.
@@ -164,6 +164,9 @@
L\'autentificassion a doi fator për ël moment a l\'é nen sostnùa.Veul-lo për da bon seurte dal sistema?Marca ëd Comun
+ Sit an sl\'aragnà ëd Comun
+ Pàgina su Facebook ëd Comun
+ Còdes sorgiss Github ëd ComunPlancia dë sfondFaliment ëd la plancia dël mojenGnun-a plancia trovà
@@ -188,6 +191,7 @@
SugerimentSeurte dal sistemaCors d\'antrodussion
+ NotìficheIj pòst ant j\'anviron a peulo nen esse smonù sensa ij përmess ëd localisassiongnun-a descrission trovàPàgina dj\'archivi ëd Comun
@@ -198,4 +202,17 @@
Dé ël përmessDovré n\'anmagasinament esternArgistré le plance pijà con la màchina fòto ëd sò angign
+ Mandé l\'archivi d\'argistr
+ Mandé l\'archivi d\'argistr ai dësvlupator për pòsta eletrònica
+ Ch\'as colega a sò cont
+ Ël leu a l\'é nen cangià.
+ Leu nen disponìbil.
+ A-i é da manca dël përmess pr\'ësmon-e na lista dij pòst davzin
+ OTEN-E D\'ANSTRUSSION
+ LESE L\'ARTÌCOL
+ Bin-ëvnù an Wikimedia Commons, %1$s! I soma content ëd vëdd-lo sì.
+ %1$s a l\'ha lassà un mëssagi su soa pàgina ëd ciaciarade
+ Mersì d\'avèj fàit na modìfica
+ %1$s a l\'ha massionalo su %2$s .
+ Cangé la visualisassion
diff --git a/app/src/main/res/values-ps/error.xml b/app/src/main/res/values-ps/error.xml
new file mode 100644
index 000000000..6fc0f00bb
--- /dev/null
+++ b/app/src/main/res/values-ps/error.xml
@@ -0,0 +1,4 @@
+
+
+ مننه!
+
diff --git a/app/src/main/res/values-ps/strings.xml b/app/src/main/res/values-ps/strings.xml
index 406e8717f..439943662 100644
--- a/app/src/main/res/values-ps/strings.xml
+++ b/app/src/main/res/values-ps/strings.xml
@@ -34,13 +34,13 @@
بياتازه کول1 پورته کول
- %d پورته کول
+ %1$d پورته کولوېشنيزېامستنېنومليکنهپه اړه
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">د پټنتيا تگلاره</a>
+ <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">د پټنتيا تگلاره</a>په اړهوروستۍ کارېدلې وېشنيزېتاسې تر اوسه کوم انځور نه دی پورته کړی.
@@ -68,6 +68,8 @@
هونهسرليک
+ ويکيپېډياښه راغلئ
+ ناگارلپرانيستلتړلکور
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 840e7a4f1..5792df782 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -22,7 +22,7 @@
Toque para vercarregando arquivo
- carregando %d arquivos
+ carregando %1$d arquivosMeus envios recentesNa fila
@@ -56,16 +56,16 @@
Ainda não carregado\@string/contributions_subtitle_zero
- %d envio
- %d envios
+ %1$d envio
+ %1$d envios
- Iniciando 1 carregamento
- Iniciando %d carregamentos
+ Iniciando %1$d carregamento
+ Iniciando %1$d carregamentos
- 1 carregamento
- %d carregamentos
+ %1$d carregamento
+ %1$d carregamentosNenhuma categoria correspondente %1$s encontradaAdicione categorias para fazer suas imagens mais vistas no Wikimedia Commons. \n\nComece a digitar para adicionar categorias.\nAperte nesta mensagem (ou aperte para voltar) para pular este passo
@@ -73,9 +73,9 @@
ConfiguraçõesCriar contaSobre
- Software livre distribuído sob a <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. %1$s e seu logotipo são marcas registradas da Wikimedia Foundation e são usadas com a permissão da Wikimedia Foundation. Não somos endossados nem afiliados à Wikimedia Foundation.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Fonte</a> e <a href=\"https://commons-app.github.io/\">site</a> em GitHub. Crie um novo <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub issue</a> para relatórios de bugs e sugestões.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Política de privacidade</a>
+ O Wikimedia Commons é um aplicativo de código aberto criado e mantido por beneficiários e voluntários da comunidade Wikimedia. A Wikimedia Foundation não está envolvida na criação, desenvolvimento ou manutenção do aplicativo.
+ Criar uma nova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">publicação no GitHub</a> para informar erros e sugestões.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Política de privacidade</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Créditos</a>SobreEnviar comentários (por e-mail)
@@ -164,6 +164,9 @@
Dois fatores de autenticação não são suportados.Quer realmente sair ?Logotipo do Commons
+ Site do Commons
+ Página do Commons no Facebook
+ Código fonte do Commons no GithubImagem de fundoFalha na imagem de mídiaNenhuma imagem encontrada
@@ -188,6 +191,7 @@
ComentáriosSairTutorial
+ NotificaçõesOs locais próximos não podem ser exibidos sem permissões de localizaçãoNenhuma descrição encontradaPágina de arquivo do Commons
@@ -198,4 +202,17 @@
Dar permissãoUsar o armazenamento externoSalvar as fotos tiradas com a câmera no aplicativo no seu dispositivo
+ Enviar arquivo de registro
+ Enviar arquivo de log para desenvolvedores por e-mail
+ Faça login na sua conta
+ O local não mudou.
+ Localização não disponível.
+ Permissão necessária para exibir uma lista de locais próximos
+ OBTER DIREÇÕES
+ LER ARTIGO
+ Bem-vindo ao Wikimedia Commons, %1$s! É um prazer tê-lo aqui.
+ %1$s deixou uma mensagem na sua página de discussão
+ Obrigado por ter realizado uma edição
+ %1$s fez menção a você em %2$s.
+ Visualização de alternância
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index d5e906937..bfd0027fd 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -10,6 +10,7 @@
Aguarde, por favor…Inicio de sessão bem sucedidoO início de sessão falhou!
+ Ficheiro não encontrado. Por favor, tente outro ficheiro.Falha de autenticação!Iniciado o carregamento!%1$s enviado!
@@ -19,11 +20,11 @@
A terminar o carregamento de %1$sO carregamento de %1$s falhouToque para ver
-
- A carregar um ficheiro
- A carregar %d ficheiros
+
+ %1$d a carregar um ficheiro
+ a carregar %1$d ficheiros
- Meus carregamentos
+ Meus carregamentos recentesEm esperaFalhou%1$d%% concluído
@@ -39,8 +40,9 @@
Não foi possível iniciar sessão - falha de redeNão foi possível iniciar sessão - verifique o seu nome de utilizador(a)Não foi possível iniciar sessão - verifique a sua palavra-passe
- Demasiadas tentativas mal sucedidas. Por favor, tente de novo dentro de alguns minutos
+ Demasiadas tentativas malsucedidas. Por favor, tente de novo dentro de alguns minutos.Desculpe, este utilizador foi bloqueado no Commons
+ Precisa fornecer o seu código de ativação de dois fatores.Falha ao iniciar sessãoCarregarNomeie este conjunto
@@ -48,42 +50,54 @@
EnviarPesquisar categoriasGravar
-
- Sem carregamentos ainda
- 1 carregamento
- %d carregamentos
+ Atualizar
+ O GPS está desativado no seu dispositivo. Gostarias de ativá-lo?
+ Ativar GPS
+ Ainda não foram enviados ficheiros
+
+ \@string/contributions_subtitle_zero
+ %1$d carregamento
+ %1$d carregamentos
-
- A iniciar um carregamento
- A iniciar %d carregamentos
+
+ A iniciar %1$d carregamento
+ A iniciar %1$d carregamentos
-
- 1 carregamento
- %d carregamentos
+
+ %1$d carregamento
+ %1$d carregamentosNenhuma categoria correspondente %1$s encontrada
- Adicione categorias para tornar as suas imagens mais fáceis de encontrar no Wikimedia Commons. \n\nComece a digitar para adicionar categorias.\nCarregue nesta mensagem (ou carregue para voltar) para saltar este passo
+ Adicione categorias para tornar as suas imagens mais fáceis de encontrar no Wikimedia Commons.\nComece a digitar para adicionar categorias.CategoriasConfigurações
+ Registar-seSobre
- Software em código aberto distribuído sob <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. O Wikimedia Commons e o seu logótipo são marcas registadas da Wikimedia Foundation e são usadas com permissão da Wikimedia Foundation. Não somos endossados ou afiliados à Wikimedia Foundation.
- Código no <a href=\"https://github.com/commons-app/android-commons\">GitHub</a>. Erros no <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a>.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Política de privacidade</a>
+ A aplicação do Wikimedia Commons é uma aplicação de código aberto criada e mantida por bolseiros e voluntários da comunidade Wikimedia. A Wikimedia Foundation não está envolvida na criação, desenvolvimento ou manutenção da aplicação.
+ Criar uma nova <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidência no GitHub</a> para reportar erros e sugestões.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Política de privacidade</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Créditos</a>SobreEnviar comentários (por e-mail)
+ Não foi instalado nenhum cliente de correio eletrónicoCategorias usadas recentementeA aguardar pela primeira sincronização…Não carregou ainda nenhuma foto.Tente novamenteCancelarEssa imagem será licenciada sob %1$s
+ Ao carregar esta imagem, declaro que esta é a minha própria obra, que não contém material protegido ou selfies, e que adere às <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/pt\">políticas do Wikimedia Commons</a>.DescarregarLicençaUsar título/descrição anterioresObter automaticamente a localização atualRecuperar localização atual para oferecer sugestões da categoria se a imagem não é georreferenciada
+ Modo noturno
+ Utilizar tema escuro
+ Atribuição-CompartilhaIgual 4.0
+ Atribuição 4.0 Atribuição – Compartilhamento pela mesma Licença
- CC Atribuição 3.0
+ Atribuição 3.0CC0CC BY-SA 3.0CC BY-SA 3.0 (Áustria)
@@ -98,10 +112,16 @@
CC BY-SA 3.0 (Roménia)CC BY 3.0CC-BY-SA 4.0
+ CC BY 4.0CC ZeroWikimedia Commons armazena a maioria das imagens que são usadas na Wikipédia.As suas imagens ajudam a educar pessoas em todo o mundo!
+ Por favor, carregue apenas imagens tiradas ou criadas exclusivamente por si:
+ - Objetos naturais (flores, animais, montanhas)\n- Objetos úteis (bicicletas, estações de comboio)\n- Pessoas famosas (o seu presidente da câmara, atletas olímpicos que conheça)
+ Por favor, NÃO carregue:
+ - Autorretratos ou imagens dos seus amigos\n- Imagens descarregadas da Internet\n- Capturas de ecrã de aplicações com direitos de autorExemplo de carregamento:
+ - Título: Ópera de Sydney\n- Descrição: A Ópera de Sydney vista do outro lado da baía\n- Categorias: Ópera de Sydney vista do ocidente, Vistas à distância da Ópera de SydneyContribua com as suas imagens. Ajude os artigos da Wikipédia a ganhar vida!As imagens na Wikipédia provêm do Wikimedia Commons.As suas imagens ajudam a educar as pessoas em todo o mundo.
@@ -109,13 +129,90 @@
Acha que conseguiu?Sim!Categorias
- A carregar…
+ A carregar…Nenhuma selecionadaSem descriçãoLicença desconhecidaActualizar
+ Permissão necessária: Ler a armazenagem externa. A aplicação não pode funcionar sem isto.
+ Permissão necessária: Escrever na armazenagem externa. A aplicação não pode funcionar sem isto.
+ Permissão opcional: Obter a localização atual para sugestões de categoriaOKLocais Próximos
+ Não foram encontrados locais próximos.
+ Aviso
+ Este ficheiro já existe no Commons. Tem a certeza de que deseja continuar?SimNão
+ Título
+ Título do ficheiro multimédia
+ Descrição
+ A descrição do ficheiro multimédia é colocada aqui. Ela pode ser bastante longa e precisar de ser dividida em várias linhas. No entanto, esperamos que tenha bom aspeto.
+ Data de carregamento
+ Licença
+ Coordenadas
+ Não fornecido
+ Torne-se um Testador Beta
+ Opte pelo nosso canal beta no Google Play e obtenha acesso antecipado a novas funcionalidades e a correções de erros
+ Utilizar o Wikidata
+ (Aviso: desativar isto pode causar um grande consumo de dados móveis)
+ Código de autenticação de dois fatores
+ O meu limite de carregamentos recentes
+ Limite máximo
+ Não é possível apresentar mais de 500
+ Definir o limite de carregamentos recentes
+ Atualmente, a autenticação de dois fatores não é suportada.
+ Deseja realmente sair?
+ Logótipo do Commons
+ Sítio do Commons
+ Página do Commons no Facebook
+ Código-fonte do Commons no Github
+ Imagem de fundo
+ Falha na imagem de média
+ Nenhuma imagem encontrada
+ Carregar imagem
+ Monte Zao
+ Lamas
+ Ponte de Arco-Íris
+ Túlipa
+ Nada de autorretratos
+ Imagem proprietária
+ Bem-vindo(a) à Wikipédia
+ Direitos de autor de boas-vindas
+ Ópera de Sydney
+ Cancelar
+ Abrir
+ Fechar
+ Início
+ Carregar
+ Próximo
+ Acerca
+ Configurações
+ Comentários
+ Sair
+ Tutorial
+ Notificações
+ Os sítios aqui perto não podem ser apresentados sem permissões de localização
+ não foi encontrada nenhuma descrição
+ Página do ficheiro no Commons
+ Item do Wikidata
+ Erro ao colocar imagens na cache
+ Um título descritivo exclusivo para o ficheiro, que servirá como um nome de ficheiro. Pode utilizar uma linguagem simples com espaços. Não inclua a extensão do ficheiro
+ Por favor, descreva o ficheiro da melhor forma possível: Onde foi tirado? O que isso mostra? Qual é o contexto? Por favor, descreva os objetos ou as pessoas. Indique as informações que não podem ser facilmente adivinhadas, por exemplo, a hora do dia, se for uma paisagem. Se o ficheiro mostrar algo incomum, explique o que torna incomum.
+ Permitir
+ Utilizar a armazenagem externa
+ Gravar as fotografias tiradas com a câmara da aplicação no seu dispositivo
+ Enviar ficheiro de registo
+ Enviar o ficheiro de registo aos programadores por correio eletrónico
+ Inicie sessão na sua conta
+ A localização não foi alterada.
+ A localização não está disponível.
+ É necessária a permissão para mostrar uma lista dos sítios aqui perto
+ OBTER DIREÇÕES
+ LER ARTIGO
+ Bem-vindo ao Wikimedia Commons, %1$s! É um prazer tê-lo aqui.
+ %1$s deixou uma mensagem na sua página de discussão
+ Obrigado por ter realizado uma edição
+ %1$s fez menção a si em %2$s.
+ Alternar modo de visionamento
diff --git a/app/src/main/res/values-qq/strings.xml b/app/src/main/res/values-qq/strings.xml
index 7e1bfb282..3f2035941 100644
--- a/app/src/main/res/values-qq/strings.xml
+++ b/app/src/main/res/values-qq/strings.xml
@@ -20,8 +20,8 @@
Title for notification about upload failing. %1$s represents file nameText for notification about upload being completed.
- Status text about number of uploads left.\n* %d represents number of uploads left, including current one\n
- Status text about number of uploads left.\n* %d represents number of uploads left, including current one\n
+ Status text about number of uploads left.\n* %1$d represents number of uploads left, including current one\n
+ Status text about number of uploads left.\n* %1$d represents number of uploads left, including current one\nTitle for screen showing my contributions.Show status of upload as currently queued.\n{{Identical|Queue}}
@@ -58,7 +58,7 @@
{{Identical|About}}License and legal notice. %1$s is {{msg-wm|Commons-android-strings-trademarked name}}{{Ignored}}\n\nUsed in {{msg-wm|Commons-android-strings-about license}}\n\n{{Identical|Wikimedia Commons}}
- {{doc-important|Please make sure that your translation of \"source\" means \"source code\", not \"reference source\".}}\nSource and Bugs
+ Indicator for reporting bugs and suggestions.HTML fragment linking to Wikimedia\'s privacy policy. Note: avoid percent-encoding in the URL, as this causes problems with Android\'s resource compiler. Use accented/non-ASCII characters \"as is\" if possible. (We can fix it manually, so don\'t worry too much about it.)\n{{Identical|Privacy policy}}{{Identical|Credit}}{{Identical|About}}
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index adb2659d4..8021d2970 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -22,8 +22,8 @@
Atingeți pentru a vizualiza1 fișier se încarcă
- %d fișiere se încarcă
- %d de fișiere se încarcă
+ %1$d fișiere se încarcă
+ %1$d de fișiere se încarcăÎncărcările meleÎn așteptare
@@ -52,28 +52,28 @@
Salvare\@string/contributions_subtitle_zero
- %d încărcare
- %d încărcări
- %d de încărcări
+ %1$d încărcare
+ %1$d încărcări
+ %1$d de încărcăriSe pornește 1 încărcare
- Se pornesc %d încărcări
- Se pornesc %d de încărcări
+ Se pornesc %1$d încărcări
+ Se pornesc %1$d de încărcări
-
+ 1 încărcare
- %d încărcări
- %d de încărcări
+ %1$d încărcări
+ %1$d de încărcăriNu s-a găsit nicio categorie corespunzătoare cu %1$s
- Adăugați categorii pentru a facilita găsirea imaginilor dumneavoastră la Wikimedia Commons.\n\nÎncepeți să tastați pentru a adăuga categorii.\nAtingeți acest mesaj (sau mergeți înapoi) pentru a omite acest pas.
+ Adăugați categorii pentru a facilita găsirea imaginilor dumneavoastră la Wikimedia Commons.\n\nÎncepeți să tastați pentru a adăuga categorii.\nAtingeți acest mesaj (sau mergeți înapoi) pentru a omite acest pas.CategoriiSetăriÎnregistrareDespreSoftware cu sursă deschisă lansat sub <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Licența Apache v2</a>
- <a href=\"https://github.com/commons-app/apps-android-commons\">Sursa</a> și <a href=\"https://commons-app.github.io/\">pagina web</a> pe GitHub. Deschide un nou <a href=\"https://github.com/commons-app/apps-android-commons/issues\">ticket pe GitHub</a> pentru a raporta un bug sau a oferi o sugestie.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Sursa</a> și <a href=\"https://commons-app.github.io/\">pagina web</a> pe GitHub. Deschide un nou <a href=\"https://github.com/commons-app/apps-android-commons/issues\">ticket pe GitHub</a> pentru a raporta un bug sau a oferi o sugestie.<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Politica de confidențialitate</a>DespreTrimitere reacții (prin e-mail)
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index bfa61805b..eece93a93 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -8,7 +8,7 @@
ЗарегистрироватьсяВход в системуПожалуйста, подождите…
- Опознание прошло успешно
+ Вход выполнен успешно!Ошибка входа в систему!Файл не найден. Попробуйте другой файл.Ошибка аутентификации!
@@ -21,12 +21,12 @@
Загрузка %1$s не удаласьНажмите для просмотра
- %d файл загружается
- %d файла загружается
- %d файлов загружается
+ %1$d файл загружается
+ %1$d файла загружается
+ %1$d файлов загружаетсяМои недавние загрузки
- Очередь
+ В очередиОшибка загрузки.Завершено %1$d%%Загрузка
@@ -52,34 +52,34 @@
Выбор категорийСохранитьОбновить
- GPS отключен в вашем устройстве. Вы хотите включить его?
+ GPS отключён в вашем устройстве. Хотите включить его?Включить GPSЗагрузок пока нет\@string/contributions_subtitle_zero
- %d загрузка
- %d загрузки
- %d загрузок
+ %1$d загрузка
+ %1$d загрузки
+ %1$d загрузок
- Начинается %d загрузка
- Начинается %d загрузки
- Начинается %d загрузок
+ Начинается %1$d загрузка
+ Начинается %1$d загрузки
+ Начинается %1$d загрузок
- %d загрузка
- %d загрузки
- %d загрузок
+ %1$d загрузка
+ %1$d загрузки
+ %1$d загрузокКатегории, соответствующие %1$s, не найдены
- Добавьте категории, чтобы ваши изображения можно было легко найти на Викискладе.\n\nНачните вводить название для добавления категорий.\nНажмите на это сообщение (или за ним), чтобы пропустить этот шаг.
+ Добавьте категории, чтобы ваши изображения можно было легко найти на Викискладе.\nНачните вводить название для добавления категорий.КатегорииНастройкиЗарегистрироватьсяО приложении
- Приложение с открытым исходным кодом, выпущено по лицензии <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. %1$s и его логотип являются товарными знаками Фонда Викимедиа и используются с разрешения Фонда Викимедиа. Мы не поддерживаемся и не связаны с Фондом Викимедиа.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Исходный код</a> и <a href=\"https://commons-app.github.io/\">сайт</a> на GitHub. Создайте новый <a href=\"https://github.com/commons-app/apps-android-commons/issues\">запрос на GitHub</a>, чтоб сообщить об ошибке или внести предложение.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy/ru\">Политика конфиденциальности</a>
+ Приложение «Викисклад» - это программа с открытым кодом, которую создали волонтёры и участники грантов Викимедиа. Фонд Викимедиа не участвует в создании, разработке или обслуживании этого приложения.
+ Вы можете создать <a href=\"https://github.com/commons-app/apps-android-commons/issues\">запрос на GitHub</a>, чтобы сообщить об ошибке или внести предложение.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Политика конфиденциальности</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Благодарности</a>О приложенииОтправить отзыв (по эл. почте)
@@ -90,11 +90,12 @@
ПовторитьОтменаЭто изображение будет лицензировано под %1$s
+ Отправляя это изображение, я подтверждаю, что это моя собственная работа, которая не содержит защищённых авторским правом материалов или селфи, а также отвечает <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/ru\">правилам Викисклада</a>.СкачатьЛицензияИспользовать предыдущие заголовок/описаниеАвтоматически получить текущее местоположение
- Получить текущее местоположение, чтобы получить предложения категорий, если изображение не содержит геотеги
+ Получить текущее местоположение, чтобы были предложены категории, если изображение не содержит геотеговНочной режимИспользовать тёмную тему Attribution-ShareAlike 4.0
@@ -120,15 +121,15 @@
Викисклад содержит большую часть изображений, которые используются в Википедии.Ваши изображения помогают образованию людей во всём мире!Пожалуйста, загрузите фотографии, которые были сняты или созданы исключительно вами:
- — Природные объекты (цветы, животные, горы)\n— Полезные предметы (велосипеды, вокзалы)\n— Известные люди (ваш мэр, спортсмены-олимпийцы, которых вы встретили)
+ — Природные объекты (например, цветы, животные, горы)\n— Полезные предметы (например, велосипеды, вокзалы)\n— Известные люди (например, ваш мэр, спортсмены-олимпийцы, которых вы встретили)Пожалуйста, НЕ загружайте:— Селфи или фотографии ваших друзей\n— Фотографии, которые вы скачали из Интернета\n— Скриншоты несвободных приложенийПример загрузки:
- — Название: Сиднейский оперный театр\n— Описание: Сиднейский оперный театр, вид через залив\n— Категории: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ — Название: Сиднейский оперный театр\n— Описание: Сиднейский оперный театр, вид через залив\n— Категории: Sydney Opera House from the west, Sydney Opera House remote viewsЗагрузите свои изображения. Помогите Википедии оживить статьи!Изображения в Википедии хранятся на Викискладе.Ваши изображения помогают образованию людей во всём мире.
- Избегайте материалов, найденных в Интернете и защищённых авторским правом, а также изображений плакатов, книжных обложек и т.п.
+ Избегайте материалов, защищённых авторским правом, например, найденных в Интернете, изображений плакатов, книжных обложек и т.п.Вам это понятно?Да!Категории
@@ -158,7 +159,7 @@
Стать бета-тестеромПодпишитесь на наш канал бета-версии на Google Play и получите ранний доступ к новым функциям и исправлениям ошибокИспользовать Викиданные
- (Предупреждение: отключение может привести к большому потреблению мобильных данных)
+ (Предупреждение: отключение может привести к значительному расходу мобильных данных)Код 2ФАЛимит моих недавних загрузокМаксимальный лимит
@@ -167,6 +168,9 @@
Двухфакторная аутентификация в настоящее время не поддерживается.Вы действительно хотите выйти?Логотип Викисклада
+ Веб-сайт Commons
+ Facebook-страница Commons
+ Исходные коды Commons на гитхабеФоновое изображениеОшибка медиаизображенияИзображение не найдено
@@ -191,11 +195,28 @@
Обратная связьВыйтиРуководство
- Ближайшие места не могут быть отображены без разрешения на геолокацию
+ Уведомления
+ Места поблизости не могут быть отображены без разрешения на геолокациюописание не найденоСтраница файла на ВикискладеЭлемент ВикиданныхОшибка при кэшировании картинок
+ Уникальное описание, которое будет сохранено как имя файла. Вы можете использовать естественный язык, разделяя слова пробелами. Пожалуйста, не указывайте расширение файла.
+ Пожалуйста, подробно опишите загружаемый файл: где он был снят? что на нём изображено? каков его контекст? Пожалуйста опишите изображённых персон или объекты. Добавьте информацию, о которой нельзя легко догадаться, например, время суток, когда снимался файл. Если снято что-то необычное, постарайтесь пояснить, что именно в этом необычного.
+ Дать разрешениеИспользовать внешнее хранилищеСохранять изображения, сделанные с помощью встроенной камеры на устройстве
+ Выслать лог-файл
+ Выслать лог-файл разработчикам по е-мейлу
+ Войдите в свою учётную запись
+ Местоположение не изменено.
+ Местоположение недоступно.
+ Необходимо разрешение для отображения списка мест поблизости
+ Показать на карте во внешней программе
+ ЧИТАТЬ СТАТЬЮ
+ Добро пожаловать на Викисклад, %1$s! Рады вас здесь видеть.
+ %1$s оставил сообщение на вашей странице обсуждения
+ Спасибо за правку
+ %1$s упомянул вас на %2$s.
+ Переключить режим просмотра
diff --git a/app/src/main/res/values-sat/error.xml b/app/src/main/res/values-sat/error.xml
new file mode 100644
index 000000000..8a52aebfb
--- /dev/null
+++ b/app/src/main/res/values-sat/error.xml
@@ -0,0 +1,4 @@
+
+
+ ᱥᱟᱨᱦᱟᱣ!
+
diff --git a/app/src/main/res/values-sd/strings.xml b/app/src/main/res/values-sd/strings.xml
index 6c4490286..c42a38464 100644
--- a/app/src/main/res/values-sd/strings.xml
+++ b/app/src/main/res/values-sd/strings.xml
@@ -21,8 +21,8 @@
%1$s جو چاڙھڻ ناڪام ويوڏسڻ لاءِ ٺونگيو
- %d فائيل چاڙھيندي
- %d فائيلَ چاڙھيندي
+ %1$d فائيل چاڙھيندي
+ %1$d فائيلَ چاڙھينديمنھنجا تازا چاڙھقطار ۾
@@ -53,25 +53,25 @@
جي پي ايس چالو ڪيو (اين ايبل جي پي ايس)اڃان تائين ڪو به ڄاڙهه (اَپلوڊ) نه ٿيو آهي
- اڃان تائين ڪو چاڙھ ناھي
- 1 چاڙھ
- %d چاڙھَ
+ \@string/contributions_subtitle_zero
+ %1$d چاڙھ
+ %1$d چاڙھَ
- ھڪ 1 چاڙھ شروع ڪندي
- چاڙھَ %d شروع ڪندي
+ چاڙھ %1$d شروع ڪندي
+ چاڙھَ %d$1 شروع ڪندي
-
- 1 چاڙھ
- %d چاڙھَ
+
+ %1$d چاڙھ
+ %1$d چاڙھَ%1$s سان ملندڙ ڪوبہ زمرو نہ لڌو
- پنھنجي عڪسن ۾ زمرا وجھو تہ جيئن وڪيميڊيا العام تي وڌيڪ ڳولا لائق ٿي سگھن.\n\nزمرا وجھڻ لاءِ لکڻ شروع ڪريو.\nھن پيغام تي ٺونگو ھڻو (يا پوئتي ڌڪ ھڻو) ھي قدم ڇڏڻ لاءِ.
+ پنھنجي عڪسن ۾ زمرا وجھو تہ جيئن وڪيميڊيا العام تي وڌيڪ ڳولا لائق ٿي سگھن.\n\nزمرا وجھڻ لاءِ لکڻ شروع ڪريو.زمراترتيبونکاتو کوليوبابت
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">ذاتيات پاليسي</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">ذاتيات پاليسي</a>بابتپذيرائي موڪليو (برقٽپال ذريعي)ڪوبہ برقٽپال ڪلائينٽ تنصيبيل ناھي
@@ -115,7 +115,7 @@
براءِ مھرباني نہ چاڙھيو:u2022 سيلفيون يا پنھنجي دوستن جو تصويرون \nu2022 اھي تصويرون جيڪي توھان انٽرنيٽ تان ڊائونلوڊ ڪيون \nu2022 پروپرائيٽري ايپس جا اسڪرين شاٽمثال چاڙھ:
- u2022 عنوان: سڊني اوپيرا گھر \nu2022 تشريح: سڊني اوپيرا گھر نھر جي پاسي کان ڏيک \nu2022 زمرا: سڊني اوپيرا گھر، سڊني اوپيرا گھر اولھ کان، سڊني اوپيرا گھر ڏورانھان ڏيک
+ - عنوان: سڊني اوپيرا گھر \n- تشريح: سڊني اوپيرا گھر نھر جي پاسي کان ڏيک \n- زمرا: سڊني اوپيرا گھر، سڊني اوپيرا گھر اولھ کان، سڊني اوپيرا گھر ڏورانھان ڏيکپنھنجي عڪسن جي ڀاڱيداري ڪريو. وڪيپيڊيا ڪي مضمونن ۾ زندگي آڻيو!وڪيپيڊيا تي عڪس وڪيميڊيا العام تان اچن ٿا.توھان جا عڪس سڄي دنيا ۾ ماڻھن کي تعليم يافتا ڪرڻ ۾ مدد ڪن ٿا.
@@ -139,16 +139,19 @@
عنوانابلاغ جو عنوانتشريح
- چاڙهڻ جي تاريخ (اَپلوڊ ڊيٽ)
+ چاڙھيل تاريخلائسنس (اجازت نامو)آزمائشي آزمائيندڙ ٿيووڪيڊيٽا استعماڪ ڪريومنهنجي تازي چاڙهڻ (اَپلوڊ) جي حد (لِمٽ)وڌ ۾ وڌ حد (ميگزيمم لِمٽ)
+ 500 کان ٿي ڏيکارڻ کان قاصر آھي
+ ڇا توھان سچ ۾ خارج ٿيڻ ٿا چاھيو؟العام جي سڃاڻپ جو نشان (Commons Logo)پسمنظر جي تصويرڪوبہ عڪس نہ لڌوعڪس چاڙهيو
+ انڊلٺ پلگل لالا (ٽيولپ)ڪي به پاڻفيون نه (نو سيلفيز)سڃاڻپ واري تصوير (پروپرائٽري اميج)
@@ -157,14 +160,28 @@
سڊني اوپيرا هائوسردکوليو
+ بند ڪريومکيه صفحوچاڙهيو
+ ويجھابابتترتيبون (سيٽنگس)اوهان جي راءِٻاهر نڪروسکيا (ٽيوٽوريل)
+ ويجھيو جڳھون بغير مڪانيت اجازت جي نٿيون ڏيکاري سگھجنڪا به وضاحت نه مليڪامن فائيل جو ورقوڪيڊيٽا جزو (وڪيڊيٽا آئيٽم)
+ اجازت ڏيو
+ ٻاھري سنڀار استعمال ڪريو
+ ايپ ۾ ڪئمرا سان ڪڍيل تصويرون پنھنجي ڊوائيس تي سانڍيو
+ لاگ فائيل موڪليو
+ لاگ فائيل سرجڻھارن کي برقٽپال ذريعي موڪليو
+ پنھنجي کاتي ۾ داخل ٿيو
+ مڪانيت تبديلي ناھي ٿي.
+ مڪانيت موجود ناھي.
+ ويجھين جڳھن جي فھرست ڏيکارڻ لاءِ اجازت گھربل آھي
+ ھدايتون وٺو
+ مضمون پڙھو
diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml
index 1d46d16c4..6af78dbe6 100644
--- a/app/src/main/res/values-si/strings.xml
+++ b/app/src/main/res/values-si/strings.xml
@@ -24,7 +24,7 @@
1 ගොනුවක් උඩුගත කෙරේගොනු %d ක් උඩුගත කෙරේ
- මගේ උඩුගතකිරීම්
+ මගේ අලුත්ම උඩුගතකිරීම්පෝලිමට එක්වූවාඅසාර්ථකයි%1$d%% ක් සම්පුර්ණයි
@@ -42,6 +42,7 @@
පිවිසීමට නොහැකිය-කරුණාකර ඔබගේ මුරපදය පරික්ෂා කරන්න.බොහෝ අසාර්ථක උත්සාහයන් කර ඇත. කරුණාකර මිනිත්තු කිහිපයකට පසුව උත්සාහ කරන්නකණගාටුයි,මෙම පරිශීලක වාරණයට ලක්කර ඇත.
+ ඔබ ඔබගේ ද්විත්ව සහතික කිරීමේ කේතය ඇතුලත් කළ යුතුය.පිවිසීම අසාර්ථකයිඋඩුගත කරන්නමේවාට පොදු නමක් යොදන්න
@@ -49,18 +50,22 @@
උඩුගත කරන්නසෙවුම් ප්රවර්ගසුරකින්න
+ නැවත ප්රාණවත් කරන්න
+ ඔබගේ උපකරණයේ GPS ක්රියා විරහිත කර ඇත. ඔබ එය නැවත ක්රියා කිරීමට කැමතිද?
+ GPS ක්රියා කරවන්න
+ තවම උඩුගත කිරීම් කිසිවක් නැතතවමත් කිසිදු උඩුගත කිරීමක් නැත
- එක් උඩුගත කිරීමක්
- උඩුගත කිරීම් %d ක්
+ එක් උඩුගත කිරීමක් ඇත
+ උඩුගත කිරීම් %1$d ක් ඇත1 උඩුගත කිරීමක් ආරම්භ කරමින්
- උඩුගත කිරීම් %d ක් ආරම්භ කරමින්
+ උඩුගත කිරීම් %1$d ක් ආරම්භ කරමින්1 උඩුගත කිරීමක්
- උඩුගත කිරීම් %d ක්
+ උඩුගත කිරීම් %1$d ක්ප්රවර්ග සැසඳේන්නේ නැත %1$s හමුවිsයප්රවර්ග
@@ -81,6 +86,9 @@
බලපත්රයපෙර මාතෘකාව/විස්තරය භාවිතා කරන්නවත්මන් ස්ථානය ස්වයංක්රීයව ලබාගන්න
+ රාත්රී ආකාරය
+ අඳුරු තේමාව භාවිතා කරන්න
+ ඇට්රිබ්යුශන්-ශෙයාඅලයික් 4.0ඇට්රිබ්යුශන්-ශෙයාඅලයික් 3.0ඇට්රිබ්යුශන්CC0
@@ -101,17 +109,70 @@
CC Zeroවිකිමාධ්ය කොමන්ස් විසින් විකිපීඩියාහි බාවිතා වන ඡායාරූප වලින් වැඩි කොටසක් සදහා සත්කාරකත්වය දරයි.ඔබේ ඡායාරූප ලොව පුරා ජනතාව දැනුවත් කිරීමට උදව් වේ!
+ කරුණාකර ඔබ විසින් නිපදවූ හෝ ඔබ විසින් ගනු ලැබූ චායාරූප පමණක් උඩුගත කරන්න:
+ කරුණාකර උඩුගත කිරීමෙන් වැලකී සිටින්න:උදාහරණ උඩුගත කිරීම:
+ ඔබගේ චායාරූප වලින් දායකත්වය ලබාදෙන්න. විකිපීඩියා ලිපි වලට ජීවය දෙන්න උදව් වෙන්න!
+ විකිපීඩියාවෙහි ඇති පින්තූර විකිමීඩියා කොමන්ස් වෙතින් පැමිණි ඒවායි.
+ ඔබගේ චායාරූප ලොව පුරා මිනිසුන්ව දැනුවත් කිරීමට උඩව් දෙයි.
+ ඔබ හිතන්නෙ ඔබට එය තේරුම් ගත්තා කියලද?ඔව්!ප්රවර්ග
- ප්රවේශනය වෙමින් පවතී…
+ පූරණය වෙමින්කිසිවක් තෝරාගෙන නැතවිස්තරයක් නැතනොදන්නා බලපත්රයනැවත ප්රාණවත් කරන්නහරිඅවට ස්ථාන
+ ළඟපාත තැන් කිසිවක් සොයා ගැනීමට නොහැකි වියඅනතුරු හැඟවීමයි
+ මෙම ගොනුව දැනටමත් කොමන්ස් හි ඇත. ඔබට ඉදිරියට යෑම ගැන විශ්වාසද?ඔව්නැත
+ මාතෘකාව
+ මෙම මාධ්ය අන්ගයේ මාතෘකාව
+ විස්තරය
+ උඩුගත කර දිනය
+ බලපත්රය
+ ඛණ්ඩාංක
+ කිසිවක් සපයා නොමැත
+ විකිදත්ත භාවිතා කරන්න
+ දිවිත්ව තහවුරු කිරීමේ කේතය
+ මගේ අලුත්ම උඩුගත කිරීම් පෙන්වීමේ සීමාව
+ උපරිම සීමාව
+ 500 කට වඩා පෙන්විය නොහැක
+ අලුත්ම උඩුගත කිරීම් පෙන්වීමට සීමාවක් දමන්න
+ ද්විත්ව තහවුරු කිරීමේ ක්රියා මාර්ගය තවම ක්රියා කරන්නෙ නැත.
+ ඔබට ඇත්තටම නික්මීමට අවශ්යද?
+ කොමන්ස් ලාන්චනය
+ කොමන්ස් වෙබ් අඩවිය
+ කොමන්ස් ෆේස්බුක් පිටුව
+ කිසිම චායාරූපයක් සොයාගැනීමට නොහැකි විය
+ පිතූරය උඩුගත කරන්න
+ ලාමා සතුන්
+ ටියුලිප්
+ විකිපීඩියා වෙතට ඔබව සාදරයෙන් පිළිගනිමු.
+ සිඩ්නි ඔපෙරා නිවස
+ අවලංගු කරන්න
+ විවෘත කරන්න
+ වසන්න
+ මුල් පිටුව
+ උඩුගතකරන්න
+ අවට
+ පිළිබඳ
+ සැකසුම්
+ ප්රතිචාරය
+ නික්මීම
+ පාඩම
+ නිවේදන
+ කිසිම තොරතුරක් සොයාගැනීමට නොමැත
+ විකිදත්ත අයිතමය
+ භාහිර ගබඩාව භාවිතා කරන්න
+ ඔබගේ ගිණුමට පිවිසෙන්න
+ ස්ථානය වෙනස් වී නොමැත
+ ස්ථානය දී නොමැත
+ මෙමේ ලිපිය කියවන්න
+ විකිමීඩියා කොමන්ස් වෙත ඔබව සාදරයෙන් පිළිගනිමු! ඔබ මෙහි පැමිණීම ගැන අපි හද පත්ලෙන්ම සතුටු වෙමු.
+ ඔබගේ කතා පිටුවේ %1$s විසින් පණිවුඩයක් තබා ඇත
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 66b685dca..003e169c4 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -21,8 +21,8 @@
Nahrávanie %1$s zlyhaloKliknutím zobrazíte
- nahráva sa %d súbor
- nahrávajú sa %d súbory
+ nahráva sa %1$d súbor
+ nahrávajú sa %1$d súboryMoje nedávno nahranéNahrávanie je v poradí
@@ -52,14 +52,14 @@
ObnoviťPovoliť GPSŽiadne kategórie nezodpovedajú „%1$s“
- Pridajte kategórie, aby boli vaše obrázky možné na Wikimedia Commons nájsť.\n\nNa pridanie kategórií začnite písať.\nKliknite na tento odkaz (alebo na späť) ak chcete tento krok vynechať.
+ Pridajte kategórie, aby bolo vaše obrázky možné na Wikimedia Commons nájsť.\nPre pridanie kategórií začnite písať.KategórieNastaveniaZaregistrovať saO aplikáciiOpen Source softvér dostupný za podmienok <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>Zdroj na <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>. Bugy na <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a>.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Ochrana osobných údajov</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Zásady ochrany súkromia</a>O aplikáciiOdoslať spätnú väzbu (emailom)Nemáte nainštalovaného žiadneho e-mailového klienta
@@ -102,7 +102,7 @@
Prosím NENAHRÁVAJTE:- Selfies alebo fotky vašich priateľov\n- Obrázky prevzaté z internetu\n- Snímky obrazovky proprietárnych aplikáciiPríklad nahratia:
- - Názov: Opera v Sydney\n- Popis: Opera v Sydney - pohľad spoza zátoky\n- Kategórie: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Názov: Opera v Sydney\n- Opis: Opera v Sydney - pohľad spoza zátoky\n- Kategórie: Sydney Opera House from the west, Sydney Opera House remote viewsPrispejte svojimi obrázkami. Pomôžte oživiť články na Wikipédií.Obrázky na Wikipédií pochádzajú z Wikimedia Commons.Vaše obrázky pomáhajú vzdelávať ľudí po celom svete.
@@ -151,5 +151,6 @@
Spätná väzbaOdhlásiť saNávod
+ Upozornenianenašiel sa žiaden popis
diff --git a/app/src/main/res/values-skr/error.xml b/app/src/main/res/values-skr/error.xml
new file mode 100644
index 000000000..1061a7b8c
--- /dev/null
+++ b/app/src/main/res/values-skr/error.xml
@@ -0,0 +1,4 @@
+
+
+ شکریہ!
+
diff --git a/app/src/main/res/values-skr/strings.xml b/app/src/main/res/values-skr/strings.xml
index a1c32f2ee..f83eebb4a 100644
--- a/app/src/main/res/values-skr/strings.xml
+++ b/app/src/main/res/values-skr/strings.xml
@@ -11,8 +11,10 @@
لاگ ان کامیاب!لاگ ان ناکام!فائل کائنی لبھی،ٻئی فائل کیتے کوشش کرو۔
+ تصدیق ناکام!اپ لوڈ شروع!%1$s اپ لوڈ تھی ڳیا!
+ آپݨی اپلوڈ ݙیکھݨ کیتے ٹیپ کرواپ لوڈ %1$s شروع تھیندا پئے%1$s اپ لوڈ تھیندا پئے%1$s اپ لوڈ پورا تھیندا پئے
@@ -53,6 +55,9 @@
رات آلا مزاجگھاٹا تھیم ورتوسی سی او
+ سی سی بی وائی ٣.٠
+ سی سی بی وائی۔ایس اے ٤.٠
+ سی سی بی وائی ٤.٠سی سی زیروبراہ مہربانی اپ لوڈ نہ کرومثال اپ لوڈ:
@@ -98,5 +103,9 @@
ترتیباںتہاڈی رائےلاگ آؤٹ
+ ٹیٹوریلوکی ڈیٹا آئٹم
+ اجازت ݙیوو
+ لاگ فائل بھیجو
+ آپݨے کھاتے وچ لاگ ان تھیوو
diff --git a/app/src/main/res/values-sr/error.xml b/app/src/main/res/values-sr/error.xml
index 0cd823ccd..f8287ca14 100644
--- a/app/src/main/res/values-sr/error.xml
+++ b/app/src/main/res/values-sr/error.xml
@@ -3,5 +3,5 @@
Остава се срушилаУпс! Нешто је пошло наопако.Реците нам шта сте радили па то сазнање поделите с нама, путем е-поште. Тиме ћете нам помоћи да решимо проблем!
- Хвала вам!
+ Хвала Вам!
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index 7bc02be38..cb3ba3419 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -1,7 +1,7 @@
Остава
- Поставке
+ ПодешавањаКорисничко имеЛозинкаПријави ме
@@ -10,19 +10,19 @@
Сачекајте…Успешно сте пријављени.Пријављивање није успело.
- Датотека није пронађена. Покушајте са другом датотеком.
+ Нисам пронашла датотеку. Покушајте са другом датотеком.Провера идентитета није успела.Отпремање је започето.Датотека „%1$s“ је отпремљена.
- Тапните да бисте видели отпремање
+ Додирните да бисте видели отпремањеПочињем са отпремањем датотеке „%1$s“
- Отпремање датотеке „%1$s“
- Завршавам са отпремањем датотеке „%1$s“
- Не могу да отпремим „%1$s“
- Тапните да бисте видели
+ Отпремам датотеку „%1$s“
+ Завршавам отпремање датотеке „%1$s“
+ Отпремање „%1$s“ неуспешно
+ Додирните да бисте видели
- %d датотека се отпрема
- %d датотеке се отпремају
+ %1$d датотека се отпрема
+ %1$d датотеке се отпремајуМоја скорашња отпремањаНа чекању
@@ -33,16 +33,16 @@
ФотографишиУ близиниМоја отпремања
- Дели
+ ПоделиОтвори у прегледачуНасловОпис
- Не могу да вас пријавим – мрежа не ради
- Не могу да вас пријавим – проверите своје корисничко име
- Не могу да вас пријавим – проверите своју лозинку
+ Неуспешно пријављивање – грешка на мрежи
+ Неуспешно пријављивање – проверите Ваше корисничко име
+ Неуспешно пријављивање – проверите Вашу лозинкуПревише неуспешних покушаја. Пробајте поново за неколико минута.
- Нажалост, корисник је блокиран на Остави
- Морате унети свој двофакторски код за аутентификацију.
+ Нажалост, овај корисник је блокиран на Остави
+ Морате унети Ваш двофакторски код за аутентификацију.Пријава није успелаОтпремиДајте име овом комплету
@@ -54,38 +54,39 @@
GPS је онемогућен на Вашем уређају. Желите ли га омогућити?Омогући GPSЈош увек нема отпремања
-
+ \@string/contributions_subtitle_zero
- %d отпремање
- %d отпремања
+ %1$d отпремање
+ %1$d отпремања
-
- Започни %d отпремање
- Започни %d отпремања
+
+ Започни %1$d отпремање
+ Започни %1$d отпремања
- %d отпремање
- %d отпремања
+ %1$d отпремање
+ %1$d отпремањаНема категорија које одговарају %1$s
- Додајте категорије на слике да бисте олакшали корисницима њихово проналажење на Остави.\n\nДа бисте додали категорију, почните са писањем њеног имена.\nТапните на ову поруку (или притисните назад) да бисте прескочили овај корак.
+ Додајте категорије на слике да бисте олакшали корисницима њихово проналажење на Остави.\n\nДа бисте додали категорију, почните са писањем њеног имена.Категорије
- Поставке
+ ПодешавањаОтвори налогО апликацији
- Софтвер отвореног кода доступан под лиценцом <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache вер. 2</a> Викимедијина Остава и њен лого су заштитни знаци Викимедијине Фондације и користе се са дозволом Викимедијине Фондацине. Ми не одобравамо или подржавмо Викимедијину Фондацију.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Изворни кôд</a> и <a href=\"https://commons-app.github.io/\">веб-сајт</a> на GitHub-у. Направите нови <a href=\"https://github.com/commons-app/apps-android-commons/issues\">захтев на GitHub-у</a> да бисте пријавили грешке или дали предлоге.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Политика приватности</a>
+ Софтвер отвореног кода доступан под лиценцом <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache вер. 2</a> Викимедијина Остава и њен лого су заштитни знаци Викимедијине Фондације и користе се са дозволом Викимедијине Фондацине. Ми не одобравамо или подржавмо Викимедијину Фондацију.\n\nАпликација за Викимедијину оставу је апликација отвореног кода која је направљена и која се одржава помоћу грантова и волонтера Викимедијине заједнице. Задужбина Викимедија није укључена у стварање, развој или одржавање апликације.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Изворни кôд</a> и <a href=\"https://commons-app.github.io/\">веб-сајт</a> на GitHub-у. Направите нови <a href=\"https://github.com/commons-app/apps-android-commons/issues\">захтев на GitHub-у</a> да бисте пријавили грешке или дали предлоге.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Политика приватности</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Заслуге</a>О апликацијиПошаљите повратне информације (путем е-поште)Није инсталиран имејл клијентНедавно коришћене категорије
- Чекам на прву синхронизацију…
- Још нисте отпремили ниједну фотографију.
+ Чека се прва синхронизација…
+ Још увек нисте отпремили ниједну фотографију.Покушај поновоОткажиСлика ће се водити под лиценцом %1$s
+ Слањем ове слике, ја тврдим да је у питању мој рад, да не садржи материјал или селфије заштићене ауторским правима, те да је на остале начине у складу са <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">смерницама Викимедијине оставе</a>.ПреузмиЛиценцаКористи претходан наслов/опис
@@ -95,7 +96,7 @@
Користити тамну темуАуторство-Делити под истим условима 4.0Ауторство 4.0
- Ауторство-Делити под истим условимаu 3.0
+ Ауторство-Делити под истим условима 3.0Ауторство 3.0CC0CC BY-SA 3.0
@@ -114,16 +115,16 @@
CC BY 4.0CC НулаВикимедијина Остава садржи већину слика која се користи на Википедији.
- Ваше слике помажу у образовању људи широм света.
- Молимо Вас да поставите слике које сте преузели или креирали у потпуности сами:
+ Ваше слике помажу у образовању људи широм света!
+ Поставите слике које сте преузели или креирали у потпуности сами:- Природне објекте (цвеће, животиње, планине)\n- Корисне објекте (бицикле, железничке станице)\n- Познате људе (ваш градоначелник, Олимпијце које сте срели)
- Молимо НЕ отпремајте:
- - Селфије и слике твојих пријатеља\n- Слике које сте преузели са интернета\n- Скриншотове из сопствених апликација
+ НЕ отпремајте:
+ - Селфије и слике Ваших пријатеља\n- Слике које Сте преузели са интернета\n- Скриншотове из сопствених апликацијаПример отпремања:
- — Наслов: Сиднејска опера\n— Опис: Сиднејска опера, поглед преко залива\n— Категорије: Сиднејска опера, Сиднејска опера са запада, погледи на Сиднејксу оперу из даљине
- Делите своје слике. Оживите чланке на Википедији!
+ — Наслов: Сиднејска опера\n— Опис: Сиднејска опера, поглед преко залива\n— Категорије: Сиднејска опера са запада, погледи на Сиднејксу оперу из даљине
+ Делите Ваше слике. Оживите чланке на Википедији!Слике на Википедији долазе из Оставе.
- Са вашим сликама помажете у образовању људи широм света.
+ Ваше слике помажу у образовању људи широм света.Избегавајте материјале које сте нашли на интернету, као и слике плаката, корица књига итд.Јесте ли разумели?Јесам!
@@ -133,8 +134,9 @@
Нема описаНепозната лиценцаОсвежи
- Потребна дозвола: Провера спољашње меморије. Апликација без овога не може да функционише.
- Опциона дозвола: Преузми тренутну локацију за предлоге категорија
+ Потребна дозвола: читање спољашње меморије. \nАпликација не може да функционише без овога.
+ Потребна дозвола: писање у спољашњој меморији. \nАпликација не може да функционише без овога.
+ Необавезна дозвола: преузми тренутну локацију за предлоге категоријаУ редуМеста у близиниНису пронађена оближња места
@@ -149,21 +151,22 @@
Датум отпремањаЛиценцаКоординате
- Постани Бета Тестер
- Прикључите се на наш бета канал на Гугл плеју и приступајте новим информацијама и поправкама багова
+ Ништа није унето
+ Постани бета тестер!
+ Прикључите се на наш бета канал на Гугл плеју и приступите новим информацијама и исправкама баговаКористи Википодатке
- (Упозорење: онемогућавањем овога може се изазвати велика потрошња мобилних података)
+ (Упозорење: онемогућавање овога може изазвати велику потрошњу мобилних података)2FA код
- Мој лимит за скорашња отпремања
- Максимални лимит
+ Моја ограничења за скорашња отпремања
+ Максимално ограничењеНије могуће приказати више од 500
- Постави лимит за скорашња отпремања
+ Постави ограничење за скорашња отпремањаДвофакторска аутентификација тренутно није подржана.Заиста желите да се одјавите?Лого ОставеПозадинска сликаМедијска слика неуспешна
- Слика није пронађена
+ Нисам пронашла сликуОтпреми сликуПланина ЗаоЛаме
@@ -178,12 +181,30 @@
ОтвориЗатвориПочетна
- Отпремање
+ ОтпремиУ близиниО намаПодешавањаПовратне информацијеОдјави ме
- Чланак на Остави
- Чланак на Википодацима
+ Туторијал
+ Обавештења
+ Оближња места не могу да се приказују без дозволе за локацију
+ опис није пронађен
+ Страница датотеке на Остави
+ Ставка на Википодацима
+ Грешка при кеширању слика
+ Јединствен описни наслов за датотеку, који ће бити име датотеке. Можете да користите обични језик са размацима. Не треба уносити екстензију датотеке
+ Опишете датотеку колико је то могуће: Где је направљена? Шта приказује? Шта је контекст? Опишите објекте и/или особе. Откријте информације које се не могу лако погодити, на пример доба дана ако је у питању пејзаж. Ако датотека приказује нешто необично, молимо да објасните шта је то чини необичном.
+ Давање дозволе
+ Употреба спољашње меморије
+ Спремање слика направљених камером апликације на Вашем уређају
+ Пошаљи дневничку датотеку
+ Пошаљи дневничку датотеку девелоперима преко имејла
+ Пријавите се на Ваш налог
+ Локација није промењена.
+ Локација није доступна.
+ Потребна је дозвола за приказивање листе локација у близини
+ Добити упутства
+ Прочитај чланак
diff --git a/app/src/main/res/values-su/strings.xml b/app/src/main/res/values-su/strings.xml
index 9900c5de7..042cae3fc 100644
--- a/app/src/main/res/values-su/strings.xml
+++ b/app/src/main/res/values-su/strings.xml
@@ -20,9 +20,9 @@
Méréskeun unjalan %1$sNgunjal %1$s gagalToél pikeun nempo
-
- ngunjal %d berkas
- ngunjal %d berkas
+
+ ngunjal %1$d berkas
+ ngunjal %1$d berkasUnjalan panungtung kuringAntrian
@@ -56,26 +56,26 @@
Can aya nu diunjal\@string/contributions_subtitle_zero
- %d unjalan
- %d unjalan
+ %1$d unjalan
+ %1$d unjalanNgamimitian saunjalan
- Ngamimitian %d unjalan
+ Ngamimitian %1$d unjalan
-
+ saunjalan
- %d unjalan
+ %1$d unjalanTeu mendak kategori anu cocog jeung %1$s
- Tambahkeun kategori hambéh gambar anjeun beuki gampang katarana di Wikimédia Commons.\n\nMimitian ku ngetik kategori.\nToél ieu talatah (atawa toél deui) pikeun ngaliwatkeun ieu bagęan.
+ Tambahkeun kategori hambéh gambar anjeun beuki gampang katarana di Wikimédia Commons.\n\nMimitian ku ngetik kategori.\nToél ieu talatah (atawa toél deui) pikeun ngaliwatkeun ieu bagęan.KategoriSétingDaptarNgeunaan
- Pakakas lemes kodeu nembrak dirilis di handapeun <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Lisénsi Apache v2</a>. %1$s sarta logona téh mérk dagang anu Yayasan Wikimédia ogé dipakéna kudu meunang widi ti Yayasan Wikimédia. Kami teu disatujuan ku atawa digawé bareng jeung Yayasan Wikimédia.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Sumber</a> sarta <a href=\"https://commons-app.github.io/\">situ wéb</a> dina GitHub. Jieun anyar <a href=\"https://github.com/commons-app/apps-android-commons/issues\">perkara GitHub</a> pikeun saran jeung laporan kutu.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Kawijakan privasi</a>
+ Aplikasi Wikimédia Commons mangrupa aplikasi sumber nembrak nu dijieun jeung dikokolakeun ku panampa hibah sarta rélawan komunitas Wikimédia. Wikimedia Foundation teu pépérodeun dina nyieun, ngamekarkeun, atawa mulasara ieu aplikasi.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Sumber</a> sarta <a href=\"https://commons-app.github.io/\">situ wéb</a> dina GitHub. Jieun anyar <a href=\"https://github.com/commons-app/apps-android-commons/issues\">perkara GitHub</a> pikeun saran jeung laporan kutu.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Kawijakan privasi</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Pangajén</a>NgeunaanKirim eupan balik (via Surélék)
@@ -121,7 +121,7 @@
Wayahna ULAH ngunjal:- Sélpi atawa poto sobat anjeun\n- Poto anu diundeur ti Internét\n- Poto layar aplikasiConto unjalan:
- - Judul: Gedung Opera Sydney\n- Pedaran: Gedung Opera Sydney (Sydney Opera House) ditempo ti basisir peuntas\n- Kategori: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Judul: Gedung Opera Sydney\n- Pedaran: Gedung Opera Sydney (Sydney Opera House) ditempo ti basisir peuntas\n- Kategori: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsSumbangkeun gambar Anjeun. Bantuan ngahirupkeun artikel Wikipédia!Gambar di Wikipédia asalna ti Wikimedia Commons.Gambar-gambar anjeun ngabantu ngatik jalma di sakuliah dunya.
@@ -195,6 +195,10 @@
Kasalahan nalika muat gambarJudul déskriptif anu unik pikeun berkas, anu bakal miboga fungsi minangka ngaran berkas. Anjeun bisa maké basa basajan kalawan spasi. Ulah ngawuwuhkeun éksténsi berkasPék émbarkeun wincikan média saloba-lobabana: Dimana éta dicokot? Naon nu titojokeunna? Naon kontéksna? Pék jéntrékeun obyék atawa jalmana. Ébré informasi anu teu gampang kajudi, kawas wayah mun éta mangrupa pamandangan. Ari média nu némbongkeun perkara nu teu guyub, pék jéntrékeun naon nu ngabalukarkeun éta téh teu guyub.
+ Béré idinPaké panyimpenan éksternalSimpen gambar nu nyomotna ku aplikasi kaména na parangkat anjeun
+ Kirim berkas log
+ Kirim berkas log ka pamekar liwat surélék
+ Asup log kana akun anjeun
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index d306caa57..a0450280b 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -21,8 +21,8 @@
Uppladdning av %1$s misslyckadesTryck för att visa
- %d fil laddas upp
- %d filer laddas upp
+ %1$d fil laddas upp
+ %1$d filer laddas uppMina senaste uppladdningarKöade
@@ -56,25 +56,25 @@
Inga uppladdningar ännu\@string/contributions_subtitle_zero
- %d uppladdning
- %d uppladdningar
+ %1$d uppladdning
+ %1$d uppladdningar
- Startar %d uppladdning
- Startar %d uppladdningar
+ Startar %1$d uppladdning
+ Startar %1$d uppladdningar
- %d uppladdning
- %d uppladdningar
+ %1$d uppladdning
+ %1$d uppladdningarInga kategorier som stämmer överens med %1$s hittades
- Lägg till kategorier för att göra dina bilder synliga på Wikimedia Commons.\n\nBörja skriva för att lägga till kategorier.\nTryck på detta meddelande (eller gå tillbaka) för att hoppa över detta steg.
+ Lägg till kategorier för att göra dina bilder enklare att hitta på Wikimedia Commons.\n\nBörja skriva för att lägga till kategorier.KategorierInställningarRegistreraOmWikimedia Commons är en app med öppen källkod som skapas och underhålls av frivilliga från Wikimedias gemenskap. Wikimedia Foundation är inte involverad i skapandet, utvecklingen eller underhållet av appen.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Källkoden</a> och <a href=\"https://commons-app.github.io/\">webbplatsen</a> på GitHub. Skapa ett nytt <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-ärende</a> för att rapportera buggar och förslag.
+ Skapa ett nytt <a href=\"https://github.com/commons-app/apps-android-commons/issues\">ärende på GitHub</a> för att rapportera buggar och förslag.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Integritetspolicy</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Erkännande</a>Om
@@ -121,7 +121,7 @@
Ladda INTE upp:- Selfies eller bilder på dina vänner\n- Bilder som du har laddat ned från Internet\n- Skärmdumpar på proprietära apparUppladdningsexempel:
- - Titel: Sydneys operahus\n- Beskrivning: Sydneys operahus sedd från andra sidan bukten\n- Kategorier: Sydneys operahus, Sydneys operahus från väst, Sydneys operahus utifrån
+ - Titel: Sydneys operahus\n- Beskrivning: Sydneys operahus sedd från andra sidan bukten\n- Kategorier: Sydneys operahus från väst, Sydneys operahus utifrånBidra med dina bilder. Hjälp Wikipedia-artiklar att komma till liv!Bilder på Wikipedia kommer från Wikimedia Commons.Dina bilder hjälper till att utbilda människor runt om i världen.
@@ -164,6 +164,9 @@
Tvåstegsverifiering stöds för tillfället inte.Vill du verkligen logga ut?Commons-logotyp
+ Commons webbplats
+ Commons Facebook-sida
+ Commons Github-källkodBakgrundsbildMediabild misslyckadesIngen bild hittades
@@ -188,6 +191,7 @@
ÅterkopplingLogga utGuide
+ MeddelandenPlatser i närheten kan inte visas utan platsbehörigheteringen beskrivning hittadesCommons-filsida
@@ -198,4 +202,17 @@
Ge behörighetAnvänd extern lagringSpara bilder som tas med kameran i appen på din enhet
+ Skicka loggfil
+ Skicka loggfilen till utvecklarna via e-post
+ Logga in på ditt konto
+ Platsen har inte ändrats.
+ Platsen är inte tillgänglig.
+ Behörighet krävs för att visa en lista över platser i närheten
+ FÅ VÄGBESKRIVNINGAR
+ LÄS ARTIKEL
+ Välkommen till Wikimedia Commons, %1$s! Vi är glada att du är här.
+ %1$s lämnade ett meddelande på din diskussionssida
+ Tack för att du gjorde en redigering
+ %1$s nämnde dig på %2$s.
+ Växla vy
diff --git a/app/src/main/res/values-ta/error.xml b/app/src/main/res/values-ta/error.xml
new file mode 100644
index 000000000..0ad08846c
--- /dev/null
+++ b/app/src/main/res/values-ta/error.xml
@@ -0,0 +1,4 @@
+
+
+ நன்றி!
+
diff --git a/app/src/main/res/values-tcy/strings.xml b/app/src/main/res/values-tcy/strings.xml
index 4ece0c348..deda69193 100644
--- a/app/src/main/res/values-tcy/strings.xml
+++ b/app/src/main/res/values-tcy/strings.xml
@@ -20,9 +20,9 @@
%1$s ಅಪ್ಲೋಡ್ ಕೈದ್ ಆವೊಂದುಂಡು.%1$s ಅಪ್ಲೋಡ್ ಸರಿ ಆತಿಜಿತುಯಾರ ಮೆಲ್ಲ ಒತ್ತುಲೆ
-
- %d ಕಡತ ಅಪ್ಲೊಡ್ ಆವೊಂದುಂಡು
- %d ಕಡತೊಲು ಅಪ್ಲೋಡ್ ಆವೊಂದುಂಡು
+
+ %1$d ಕಡತ ಅಪ್ಲೊಡ್ ಆವೊಂದುಂಡು
+ %1$d ಕಡತೊಲು ಅಪ್ಲೋಡ್ ಆವೊಂದುಂಡುಎನ್ನ ದಿಂಜಯೀನಾ ವಿಚಾರೊಳುದಿಂಜೊಂತುಂಡು
diff --git a/app/src/main/res/values-te/strings.xml b/app/src/main/res/values-te/strings.xml
index 4a2c7dfbd..bfd0e79f2 100644
--- a/app/src/main/res/values-te/strings.xml
+++ b/app/src/main/res/values-te/strings.xml
@@ -1,10 +1,11 @@
- వికీమీడియా కామన్స్
+ కామన్స్అమరికలువాడుకరిపేరుసంకేతపదంలాగినవండి
+ నమోదవ్వండిలాగినవుతున్నారువేచివుండండి…లాగిన్ విజయవంతమైంది!
@@ -25,6 +26,7 @@
ఎక్కిస్తున్నాంప్రదర్శనశాల నుంచిఫోటో తీయండి
+ చుట్టుపక్కలనా ఎక్కింపులుపంచుకోండివిహారిణిలో చూపు
@@ -33,7 +35,7 @@
లాగిన్ చెయ్యలేకపోయాం - నెట్వర్కు విఫలంలాగిన్ చెయ్యలేకపోయాం - మీ వాడుకరిపేరును సరిచూసుకోండిలాగిన్ చెయ్యలేకపోయాం - మీ సంకేతపదాన్ని సరిచూసుకోండి
- మరీ ఎక్కువ విఫల యత్నాలు చేసారు. కొద్ది నిముషాలాగి ప్రయత్నించండి
+ మరీ ఎక్కువ విఫల యత్నాలు చేసారు. కొద్ది నిముషాలాగి ప్రయత్నించండిఈ వాడుకరి కామన్స్ లో నిరోధించబడ్డారు, సారీ.లాగిన్ విఫలమైందిఎక్కింపు
@@ -43,9 +45,10 @@
వర్గాల్లో వెతకండిభద్రపరచు%1$s తో సరిపోలే వర్గాలేమీ లేవు
- వికీమీడియా కామన్స్ లో వెతికేటపుడు మీ బొమ్మలు మరింత సులువుగా కనబడేందుకు వాటికి వర్గాలను చేర్చండి.\n\nవర్గాలను చేర్చేందుకు టైపండి.\nఈ అంగను దాటేసి ముందుకు పోయేందుకు, ఈ సందేశాన్ని నొక్కండి (లేదా ’బ్యాక్’ నొక్కండి)
+ వికీమీడియా కామన్స్ లో వెతికేటపుడు మీ బొమ్మలు మరింత సులువుగా కనబడేందుకు వాటికి వర్గాలను చేర్చండి.\n\nవర్గాలను చేర్చేందుకు టైపండి.\nఈ అంగను దాటేసి ముందుకు పోయేందుకు, ఈ సందేశాన్ని నొక్కండి (లేదా ’బ్యాక్’ నొక్కండి)వర్గాలుఅమరికలు
+ నమోదవ్వండిగురించిఓపెన్ సోర్సు సాఫ్టువేరు <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a> కు లోబడి విడుదలైంది<a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">గోప్యతా విధానం</a>
@@ -86,9 +89,20 @@
వివరణ లేదుతెలియని లైసెన్సుతాజాకరించు
+ సరేహెచ్చరికఅవునుకాదుశీర్షికవివరణ
+ లైసెన్సు
+ గరిష్ఠ పరిమితి
+ రద్దుచేయి
+ మూసివేయి
+ ముంగిలి
+ ఎక్కించు
+ గురించి
+ అమరికలు
+ ప్రతిస్పందన
+ గమనింపులు
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
new file mode 100644
index 000000000..21eeb12f1
--- /dev/null
+++ b/app/src/main/res/values-th/strings.xml
@@ -0,0 +1,112 @@
+
+
+ คอมมอนส์
+ การตั้งค่า
+ ชื่อผู้ใช้
+ รหัสผ่าน
+ เข้าสู่ระบบ
+ สมัครสมาชิก
+ กำลังเข้าสู่ระบบ
+ กรุณารอสักครู่…
+ การเข้าสู่ระบบสำเร็จแล้ว!
+ การเข้าสู่ระบบล้มเหลว!
+ ไม่พบไฟล์ กรุณาลองใช้ไฟล์อื่น
+ การตรวจสอบความถูกต้องล้มเหลว!
+ เริ่มการอัปโหลดแล้ว!
+ อัปโหลด %1$s แล้ว!
+ แตะเพื่อดูการอัปโหลดของคุณ
+ กำลังเริ่มการอัปโหลด %1$s
+ กำลังอัปโหลด %1$s
+ กำลังเสร็จสิ้นการอัปโหลด %1$s
+ การอัปโหลด %1$s ล้มเหลว
+ แตะเพื่อดู
+ กำลังอัปโหลดไฟล์ %1$d ไฟล์
+ การอัปโหลดล่าสุดของฉัน
+ อยู่ในคิว
+ ล้มเหลว
+ %1$d%% เสร็จสมบูรณ์
+ กำลังอัปโหลด
+ จากคลังภาพ
+ ถ่ายรูปภาพ
+ ใกล้เคียง
+ การอัปโหลดของฉัน
+ แชร์
+ ดูในเบราว์เซอร์
+ ชื่อเรื่อง
+ คำอธิบาย
+ ไม่สามารถเข้าสู่ระบบได้ - ความล้มเหลวของเครือข่าย
+ ไม่สามารถเข้าสู่ระบบได้ - กรุณาตรวจสอบชื่อผู้ใช้ของคุณ
+ ไม่สามารถเข้าสู่ระบบได้ - กรุณาตรวจสอบรหัสผ่านของคุณ
+ จำนวนครั้งที่พยายามไม่สำเร็จมากเกินไป กรุณาลองอีกครั้งในอีกสักครู่
+ ขออภัย ผู้ใช้นี้ถูกบล็อกบนคอมมอนส์อยู่
+ คุณต้องระบุโค้ดการตรวจสอบความถูกต้องสองปัจจัยของคุณ
+ การเข้าสู่ระบบล้มเหลว
+ อัปโหลด
+ ตั้งชื่อชุดนี้
+ การแก้ไข
+ อัปโหลด
+ ค้นหาหมวดหมู่
+ บันทึก
+ รีเฟรช
+ GPS ถูกปิดใช้งานในอุปกรณ์ของคุณอยู่ คุณต้องการเปิดใช้งานหรือไม่?
+ เปิดใช้งาน GPS
+ ยังไม่มีการอัปโหลด
+ การอัปโหลด %1$d รายการ
+
+ กำลังเริ่มอัปโหลด %1$d รายการ
+ กำลังเริ่มอัปโหลด %1$d รายการ
+
+
+ การอัปโหลด %1$d รายการ
+ การอัปโหลด %1$d รายการ
+
+ ไม่พบหมวดหมู่ที่ตรงกับ %$1s
+ เพิ่มหมวดหมู่เพื่อทำให้รูปภาพของคุณค้นพบได้ง่ายขึ้นบน Wikimedia Commons\n\nเริ่มพิมพ์เพื่อเพิ่มหมวดหมู่\nแตะข้อความนี้ (หรือกดปุ่มย้อนกลับ) เพื่อข้ามขั้นตอนนี้
+ หมวดหมู่
+ การตั้งค่า
+ สมัครใช้งาน
+ เกี่ยวกับ
+ แอป Wikimedia Commons เป็นแอปโอเพนซอร์สที่สร้างขึ้นและดูแลโดยผู้มีสิทธิและอาสาสมัครของชุมชนวิกิมีเดีย มูลนิธิวิกิมีเดียไม่มีส่วนเกี่ยวข้องในการสร้าง พัฒนา หรือการบำรุงรักษาแอปใดๆ ทั้งสิ้น
+ <a href=\"https://github.com/commons-app/apps-android-commons\">ซอร์สโค้ด</a>และ<a href=\"https://commons-app.github.io/\">เว็บไซต์</a>บน GitHub สร้าง <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub issue</a> ใหม่เพื่อรายงานบั๊กและส่งข้อเสนอแนะ
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">นโยบายความเป็นส่วนตัว</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">เครดิต</a>
+ เกี่ยวกับ
+ ส่งคำติชม (ผ่านทางอีเมล)
+ ไม่ได้ติดตั้งไคลเอนต์อีเมล
+ หมวดหมู่ที่ใช้ล่าสุด
+ กำลังรอการซิงค์ครั้งแรก…
+ คุณยังไม่ได้อัปโหลดรูปภาพใดๆ
+ ลองใหม่
+ ยกเลิก
+ รูปภาพนี้จะอนุญาตให้ใช้ได้ภายใต้สัญญาอนุญาต %1$s
+ โดยการส่งรูปภาพนี้ ฉันยืนยันว่านี่เป็นงานของฉันเอง ซึ่งไม่ประกอบด้วยเนื้อหาที่ละเมิดลิขสิทธิ์หรือภาพเซลฟี หรืออื่นๆ ตามที่ระบุใน<a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">นโยบายของ Wikimedia Commons</a>
+ ดาวน์โหลด
+ สัญญาอนุญาต
+ ใช้ชื่อเรื่อง/คำอธิบายก่อนหน้านี้
+ รับข้อมูลตำแหน่งที่ตั้งปัจจุบันโดยอัตโนมัติ
+ ดึงข้อมูลตำแหน่งที่ตั้งปัจจุบันเพื่อรับข้อเสนอแนะเกี่ยวกับหมวดหมู่ถ้ารูปภาพไม่ได้ติดแท็กตำแหน่งที่ตั้งเอาไว้
+ โหมดกลางคืน
+ ใช้ธีมสีเข้ม
+ โปรดอัปโหลดรูปภาพที่ถ่ายหรือสร้างด้วยตัวคุณเองทั้งหมด:
+ - วัตถุธรรมชาติ (ดอกไม้ สัตว์ ภูเขา)\n- วัตถุที่สามารถใช้งานได้ (จักรยาน สถานีรถไฟ)\n- บุคคลที่มีชื่อเสียง (นายกเทศมนตรีของคุณ นักกีฬาโอลิมปิกที่คุณรู้จัก)
+ โปรดอย่าอัปโหลด:
+ - ภาพเซลฟีหรือภาพที่มีเพื่อนของคุณ\n- ภาพที่คุณดาวน์โหลดจากอินเทอร์เน็ต\n- ภาพหน้าจอแอปที่เป็นซอฟต์แวร์กรรมสิทธิ์
+ ตัวอย่างการอัปโหลด:
+ ใช่!
+ หมวดหมู่
+ กำลังโหลด…
+ ไม่ได้เลือกไว้
+ ไม่มีคำอธิบาย
+ สัญญาอนุญาตที่ไม่รู้จัก
+ รีเฟรช
+ สิทธิที่ต้องการ: อ่านที่เก็บข้อมูลภายนอก แอปไม่สามารถทำงานได้โดยไม่มีสิทธินี้
+ สิทธิที่ต้องการ: เขียนที่เก็บข้อมูลภายนอก แอปไม่สามารถทำงานได้โดยไม่มีสิทธินี้
+ สิทธิทางเลือก: รับข้อมูลตำแหน่งที่ตั้งปัจจุบันสำหรับข้อเสนอแนะหมวดหมู่
+ ตกลง
+ สถานที่ใกล้เคียง
+ ไม่พบสถานที่ใกล้เคียง
+ คำเตือน
+ ไฟล์นี้มีอยู่แล้วบนคอมมอนส์ คุณแน่ใจหรือว่าคุณต้องการดำเนินการต่อ?
+ ใช่
+ ไม่
+
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 2ba679a7d..f5df91381 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -1,6 +1,6 @@
- Wikimedia Commons
+ CommonsAyarlarKullanıcı adıParola
@@ -21,8 +21,8 @@
%1$s dosyasının yüklemesi başarısız olduGörüntülemek için dokunun
- %d dosya karşıya yükleniyor
- %d dosya karşıya yükleniyor
+ %1$d dosya karşıya yükleniyor
+ %1$d dosya karşıya yükleniyorYakın Zamandaki YüklemelerimSırada
@@ -31,6 +31,7 @@
YükleniyorGaleri\'denFotoğraf çek
+ YakınındakilerYüklemelerimPaylaşTarayıcıda görüntüle
@@ -41,6 +42,7 @@
Oturum açılamıyor - lütfen parolanızı kontrol edinÇok sayıda başarısız girişimde bulundunuz. Birkaç dakika sonra tekrar deneyin.Üzgünüz, bu kullanıcı Commons\'ta engellendi
+ İki faktörlü kimlik doğrulama kodunu sağlamalısınız.Oturum açma başarısızYükleBu grubun adı
@@ -49,39 +51,53 @@
Kategorileri araKaydetYenile
+ GPS, cihazınızda devre dışı bırakılmıştır. Etkinleştirmek ister misiniz?
+ GPS\'i etkinleştir
+ Henüz yüklenmedi
- Henüz yükleme yok
- 1 yükleme
- %d yükleme
+ \@string/contributions_subtitle_zero
+ %1$d yükleme
+ %1$d yükleme
- 1 yüklemeye başlanıyor
- %d yüklemeye başlanıyor
+ %1$d yüklenmeye başlanıyor
+ %1$d yüklenmeye başlanıyor
- 1 yükleme
- %d yükleme
+ %1$d yükleme
+ %1$d yükleme%1$s ile eşleşen bir kategori bulunamadı
- Resimlerinizi Wikimedia Commons\'ta daha bulunabilir duruma getirmek için kategoriler ekleyin.\n\nKategori eklemek için yazmaya başlayın.\nBu adımı atlamak için bu iletiye dokunun ya da geri tıklayın.
+ Resimlerinizi Wikimedia Commons\'ta daha bulunabilir duruma getirmek için kategoriler ekleyin.\nKategori eklemek için yazmaya başlayın.KategorilerAyarlar
+ KaydolHakkında
- <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache Lisansı s2</a> altında yayımlanan Açık Kaynak yazılımı
- Kaynak: <a href=\"https://github.com/commons-app/apps-android-commons\">GitHub</a>\n\nYazılım hataları: <a href=\" https://github.com/commons-app/apps-android-commons/issues\">Github</a>
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Gizlilik Politikası</a>
+ Wikimedia Commons uygulaması, Wikimedia topluluğunun imtiyaz sahibi ve gönüllüleri tarafından oluşturulmuş ve sürdürülmüş açık kaynak kodlu bir uygulamadır. Vikipedi Vakfı, uygulamanın oluşturulması, geliştirilmesi veya bakımına dahil değildir.
+ GitHub üzerinde <a href=\"https://github.com/commons-app/apps-android-commons\">Kaynak</a> ve <a href=\"https://commons-app.github.io/\">website</a>. Hata raporları ve önerileri için yeni bir <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub sorunu</a> oluştur.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Gizlilik Politikası</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Katkıda bulunanlar</a>HakkındaGeri Bildirim Gönder (E-posta ile)
+ Yüklü bir e-posta istemcisi yokSon kullanılan kategorilerİlk eşitleme için bekleniyor…Henüz yüklenmiş hiç fotoğrafınız yok.Tekrar deneİptalBu resmi %1$s lisansı altında olacak.
+ Bu resmi göndererek bunun kendi eserim olduğumu, telif hakkıyla korunan materyal veya selfie içermediğini ve aksi takdirde <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">Wikimedia Commons politikalarına</a> uyduğunu beyan ederim.İndirLisans
- CC Attribution-ShareAlike 3.0
- CC Attribution 3.0
+ Önceki başlığı/açıklamayı kullan
+ Otomatik olarak mevcut konumu al
+ Resim koordinat olarak etiketlendirilmemişse kategori önerileri için mevcut konum bulun
+ Gece modu
+ Koyu temayı kullanın
+ Attribution-ShareAlike 4.0
+ Attribution 4.0
+ Attribution-ShareAlike 3.0
+ Attribution 3.0CC0CC BY-SA 3.0CC BY-SA 3.0 (Avusturya)
@@ -95,10 +111,17 @@
CC BY-SA 3.0 (Polonya)CC BY-SA 3.0 (Romanya)CC BY 3.0
+ CC BY-SA 4.0
+ CC BY 4.0CC0
+ Wikimedia Commons, Vikipedi\'de kullanılan resimlerin çoğunu barındırır.
+ Resimleriniz dünyanın dört bir yanındaki insanlara eğitim vermeye yardımcı olur!
+ Lütfen tamamen kendiniz çektiğiniz veya oluşturduğunuz resimleri yükleyin:
+ - Doğal nesneler (çiçekler, hayvanlar, dağlar)\n- Faydalı nesneler (bisiklet, tren istasyonları)\n- Ünlü insanlar (belediye başkanınız, tanıştığınız Olimpik atletler)Lütfen şunları YÜKLEMEYİN:- Öz çekimlerinizi ya da arkadaşlarınızın fotoğraflarını\n- İnternetten indirdiğiniz resimleri\n- Tescilli uygulamaların ekran görüntüleriniYüklenebileceklere örnekler:
+ - Başlık: Sydney Opera Binası\n- Tanım: Körfezin genelinden bakıldığında Sydney Opera Binası\n- Kategoriler: Sydney Opera Binası, batıdan Sydney Opera BinasıResimleriniz ile Vikipedi maddelerinin canlandırılmasına katkıda bulunabilirsiniz!Vikipedi\'ye Wikimedia Commons\'tan gelen görüntüler.Görüntüler dünya insanlarının eğitiminde yardımcı olur.
@@ -106,11 +129,82 @@
Bunu anladınız mı?Evet!Kategoriler
- Yükleniyor…
+ Yükleniyor...Hiçbir şey seçilmediAçıklama yokBilinmeyen lisansYenile
+ Gerekli izinler: Harici depolama biriminin okunması. Uygulama buna izin verilmeden çalışmaz.
+ Gerekli izin: Harici depolama birimi üzerine yazma. Uygulama buna izin verilmeden çalışmaz.
+ İsteğe bağlı izin: Kategori önerileri için geçerli konum alma
+ Tamam
+ Yakındaki yerler
+ Yakınlardaki yer bulunamadı
+ Uyarı
+ Bu dosya zaten Commons\'da var. Devam etmek istediğinizden emin misiniz?
+ Evet
+ Hayır
+ Başlık
+ Medyanın başlığı
+ Açıklama
+ Medya için yapılan tanımı/açıklamayı buraya yazınız. Açıklamanız uzun olabilir ve birden fazla satıra sığabilir. Umuyoruz ki güzel ve bilgilendirici olacaktır.
+ Yükleme tarihiLisansKoordinatlar
+ Sağlanmamış
+ Beta Deneycisi Olun
+ Google Play\'deki beta kanalımıza dahil olarak yeni özelliklere ve hata düzeltmelerine erken erişin
+ Vikiveri\'yi kullanın
+ (Uyarı: devre dışı bırakılması mobil veri tüketimine neden olabilir)
+ 2 Faktörlü Kimlik Doğrulama (2FA) Kodu
+ Son Yükleme Limitim
+ Maksimum Limit
+ 500\'den fazla görüntülenemiyor
+ Yeni Yükleme Sınırı Ayarla
+ İki faktörlü kimlik doğrulama şu anda desteklenmiyor.
+ Gerçekten çıkış yapmak istiyor musunuz?
+ Commons Logo
+ Arka plan görüntüsü
+ Medya Görüntüsü Başarısız Oldu
+ Resim Bulunamadı
+ Resim Yükle
+ Zao Dağı
+ Lamalar
+ Gökkuşağı Köprüsü
+ Lale
+ Selfie (\'\'özçekim\'\') göndermeyin
+ Tescilli Resim
+ Vikipedi\'ye Hoş Geldiniz
+ Telif Haklarına Hoş Geldiniz
+ Sidney Opera Binası
+ İptal
+ Aç
+ Kapat
+ Anasayfa
+ Yükle
+ Yakınındakiler
+ Hakkında
+ Ayarlar
+ Geri bildirim
+ Çıkış
+ Eğitim
+ Bildirimler
+ Yakındaki yerler, konum izinleri olmadan görüntülenemez
+ hiçbir açıklama bulunamadı
+ Commons dosya sayfası
+ Vikiveri ögesi
+ Resimler önbelleğe alınırken hata oluştu
+ Dosya için dosya adı olarak kullanılacak benzersiz açıklayıcı bir başlık olmalıdır. Boşluk bırakarak sade bir dil kullanabilirsiniz. Dosya uzantısını dahil etmeyin
+ Lütfen medyayı mümkün olduğunca açıklayın: Nerede çekildi? Ne gösteriyor? Bağlam nedir? Lütfen nesneleri veya kişileri tanımlayın. Kolay tahmin edilemeyen bilgileri açıklayın, örneğin bir manzara ise günün saatini belirtin. Medya alışılmadık bir şey gösteriyorsa lütfen olağandışı yapan şeyleri açıklayın.
+ İzin ver
+ Harici depolamayı kullanın
+ Uygulama kamerası kullanıldığında çekilen fotoğrafları cihazına kaydedin
+ Kayıt dosyasını gönder
+ Kayıt dosyasını, e-posta aracılığıyla geliştiricilere gönderin
+ Hesabınızda oturum açın
+ Konum değiştirilmedi
+ Konum kullanılamıyor.
+ Yakındaki yerler listesini görüntülemek için izin vermeniz gerekiyor
+ TALİMATLAR
+ MADDE OKU
diff --git a/app/src/main/res/values-ug/error.xml b/app/src/main/res/values-ug/error.xml
new file mode 100644
index 000000000..8b2c00876
--- /dev/null
+++ b/app/src/main/res/values-ug/error.xml
@@ -0,0 +1,5 @@
+
+
+ بايلىقتىن ئورتاق بەھرىمەن بولۇش ، ھالاك بولدى
+ رەھمەت سىزگە!
+
diff --git a/app/src/main/res/values-ug/strings.xml b/app/src/main/res/values-ug/strings.xml
index b5e76081a..b0f0333db 100644
--- a/app/src/main/res/values-ug/strings.xml
+++ b/app/src/main/res/values-ug/strings.xml
@@ -5,30 +5,53 @@
ئىشلەتكۇچى ئىسمىپارولتىزىمغا كىرىڭ
+ خەتلىتىشتىزىمغا كىرىۋاتىدۇسەل كۈتۈڭ…تىزىمغا كىرىش مۇۋەپپەقىيەتلىك!تىزىمغا كىرەلمىدى!
+ ھۆججەت تېپىلمىدى . سىناپ بېقىڭ ، باشقا ھۆججەتلەر .
+ سالاھىيەتنى ئىسپاتىنى تەكشۈرۈش مەغلۇپ بولدى !يۈكلەش باشلاندى!%1$s يۈكلەندى!
+ چېكىپ كۆرۈپ بېقىڭ ، سىزنىڭ يۇقىرىغا يوللاشباشلىنىۋاتىدۇ %1$s يۈكلەندى%1$s يۈكلىنىۋاتىدۇ! %1$s يۈكلەش تاماملىنىۋاتىدۇيۈكلىنىۋاتىدۇ %1$s مەغلۇپ بولدى
- يۈكلىگەنلىرىم
+ چېكىپ كۆرۈش
+ يۈكلىگەنلىرىم
+ ئادەم كىردى
+ مەغلۇپ بولدى%1$d%% تاماملاندىيۈكلەۋاتىدۇ
+ تىن رەسىم ئامبىرىسۈرەتكە تارتىڭ
+ يېقىندىكىيۈكلىگەنلىرىمئورتاقلىشىشتوركۆرگۈدە ئېچىڭ
+ ماۋزۇچۈشەندۈرۈش
+ تىزىملىتىش - تور كاشىلىسى
+ تىزىملاشقا ئامالسىز - سىزنىڭ ئابونت نامىڭىزنى تەكشۈرۈپ بېقىڭ
+ تىزىملاشقا ئامالسىز مەخپىي نومۇرىڭىزنى تەكشۈرۈپ بېقىڭ
+ مەغلۇپ بولغان قېتىم سانى بەك كۆپ . نەچچە مىنۇتتىن كېيىن قايتا سىناڭ .
+ كەچۈرۈڭ، بۇ ئابونت ئاللىقاچان ئورتاق بەھرىمەن بولىدىغان بايلىق مەنبەسى دائىرىلىك
+ سىز چوقۇم سىزنىڭ قوش ئامىل تەكشۈرۈش كودىنى تاپشۇرىسىز .
+ تىزىملاش مەغلۇپ بولدىيۈكلەڭ
+ نام بېرىش بۇ گۇرۇپپا سۈرەت
+ ئۇقتۇرۇشلاريۈكلەكاتېگورىيىنى ئىزدەڭساقلاڭ
+ يېڭىلاش
+ GPS سىزنىڭ ئۈسكۈنەم ئىشلىتىشتىن توختىدى . سىز قوزغىتامسىز يوق ؟
+ ئىشلىشكە باشلاشGPSتەڭشەكلەرھەققىدە
+ ۋاز كەچچۈشۈرۈڭئىجازەتنامەCC Attribution-ShareAlike 3.0
@@ -46,8 +69,39 @@
CC BY 3.0CC0ھەئە!
- كاتېگورىيە
- يۈكلەۋاتىدۇ…
+ تۈرلەر
+ يۈكلەۋاتىدۇ…تاللانمىغانچۈشەندۈرۈلۈشى يوق
+ جەزملەش
+ ئاگاھلاندۇرۇش
+ ھەئە
+ ياق
+ ماۋزۇ
+ ۋاستە ماۋزۇسى
+ چۈشەندۈرۈش
+ بۇ يەردە تولدۇرۇپ ، ئاخبارات ۋاستىلىرى توغرىسىدا ئىزاھات بەردى . بۇ بەلكىم خېلى ئۇزۇن ، ھەم كۆپ يۈككە مۇھتاج بولىدۇ . بىز ئۇ قارىماققا بەك ئۈمىد قىلىمەن .
+ ئۈستىگە يەتكۈزۈش چىسلاسى
+ ئىجازەتنامە
+ كوئوردىناتلار
+ تەمىنلىمەيدۇ
+ سىناق نۇسخىدىكى سىناق قىلغۇچى بولۇپ قالدى
+ بىز كىرگەن Google Play، ئۈستىدىكى ئۆلچەش قانىلى ، ھەمدە تېخىمۇ بالدۇر يېڭى ئىقتىدارى ۋە خاتا ئەسلىگە كەلتۈرۈش
+ ئىشلەتكۈچى ۋىكى سانلىق مەلۇماتى
+ ( ئەسكەرتىش : بۇ ئىقتىدارى بەلكىم زور مىقداردا مەنئى قىلىنغان كۆچمە سانلىق مەلۇمات ئېقىم مىقدارى ھەققى )
+ 2FAكودى
+ مېنىڭ يېقىندىن بۇيان يۇقىرىغا يوللاش چەكلەش
+ ئەڭ چوڭ چەكلەش
+ يېقىندا چىقىرىش چەكلىمىسى تەڭشەش
+ نۆۋەتتىكى قوش ئامىل دەلىللەشنى قوللىمايدۇ
+ سىز راستتىنلا چېكىنىپ چىقامسىز ؟
+ بايلىقتىن ئورتاق بەھرىمەن بولۇش بەلگىسى
+ ئورتاق بەھرىمان بولىدىغان توربەت بايلىقى
+ كۆرۈنۈش رەسىمى
+ ۋاستە رەسىمى مەغلۇپ بولدى
+ رەسىمنى تاپالمىدى
+ رەسىم يەتكۈزۈش
+ ھەسەت ھۈسەن كۆۋرۈك
+ تەھرىرلەپ چىقارغىنىڭىزغا رەھمەت
+ مۇۋاپىق كۆرۈنۈش ئالماشتۇرۇش
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 867c4b17d..809bfc84b 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -21,10 +21,10 @@
Не вдалося завантажити %1$sТоркніться, щоб переглянути
- Завантажується %d файл
- Завантажуються %d файли
- Завантажується %d файлів
- Завантажується %d файлів
+ Завантажується %1$d файл
+ Завантажуються %1$d файли
+ Завантажується %1$d файлів
+ Завантажується %1$d файлівМої останні завантаженняУ черзі
@@ -53,37 +53,37 @@
Пошук категорійЗберегтиОновити
- GPS вимкнено на Вашому пристрої. Чи хотіли б Ви його увімкнути?
+ GPS вимкнено на Вашому пристрої. Бажаєте увімкнути його?Увімкнути GPSЩе нема завантажень\@string/contributions_subtitle_zero
- %d завантаження
- %d завантаження
- %d завантажень
- %d завантажень
+ %1$d завантаження
+ %1$d завантаження
+ %1$d завантажень
+ %1$d завантажень
- Починається %d завантаження
- Починаються %d завантаження
- Починаються %d завантажень
- Починаються %d завантажень
+ Починається %1$d завантаження
+ Починаються %1$d завантаження
+ Починаються %1$d завантажень
+ Починаються %1$d завантажень
- %d завантаження
- %d завантаження
- %d завантажень
- %d завантажень
+ %1$d завантаження
+ %1$d завантаження
+ %1$d завантажень
+ %1$d завантаженьКатегорій, відповідних %1$s, не знайдено
- Додайте категорії, щоб Ваші зображення було легше знайти у Вікісховищі. \n\nПочніть ввід, щоб додати категорії.\nНатисніть на це повідомлення (або поверніться назад), щоб пропустити цей крок.
+ Додайте категорії, щоб Ваші зображення було легше знайти у Вікісховищі. \n\nПочніть вводити текст, щоб додати категорії.КатегоріїНалаштуванняЗареєструватисяПро програму
- Програмне забезпечення з відкритим кодом випущено під <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">ліцензією Apache в.2</a>. 1$s і його логотип є товарними знаками Фонду Вікімедіа і використовуються за дозволом Фонду Вікімедіа. Ми не користуємось підтримкою Фонду Вікімедіа і не є його афіліатом.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Вихідний код</a> і <a href=\"https://commons-app.github.io/\">веб-сайт</a> на GitHub. Створіть нове <a href=\"https://github.com/commons-app/apps-android-commons/issues\">завдання на GitHub</a> для повідомлення про баги або для висловлення пропозицій.
- <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Політика приватності</a>
+ Додаток «Вікісховище» — це програма з відкритим кодом, яку створили отримувачі грантів та волонтери спільноти Вікімедіа. Фонд Вікімедіа не брав участі у створенні, розробці чи обслуговуванні цього додатка.
+ Ви можете створити новий <a href=\"https://github.com/commons-app/apps-android-commons/issues\">запит на GitHub</a>, щоб повідомити про помилки, або висловити пропозиції.
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Політика конфіденційності</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Автори</a>Про програмуНадіслати відгук (електронною поштою)
@@ -123,13 +123,13 @@
CC BY 4.0CC ZeroНа Вікісховищі зберігається більшість зображень, що використовуються у Вікіпедії.
- Ваші зображення допомагають навчати людей по всьому світі!
+ Ваші зображення допомагають освіті людей по всьому світу!Будь ласка, завантажуйте зображення, повністю виконані або створені Вами:u2022 Природні об\'єкти (квіти, тварини, гори) \nu2022 Корисні об\'єкти (велосипеди, залізничні вокзали) \nu2022 Відомі люди (Ваш мер, олімпійські атлети, яких Ви зустрічали)Будь ласка, НЕ завантажуйте:u2022 Селфі або фото своїх друзів \nu2022 Зображення, які Ви завантажили з інтернету \nu2022 Скріншоти патентованих програмПриклад завантаження:
- u2022 Назва: Сіднейський оперний театр \nu2022 Опис: Вид на Сіднейський оперний театр з боку бухти \nu2022 Категорії: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Назва: Сіднейський оперний театр \n- Опис: Вид на Сіднейський оперний театр з боку бухти \n- Категорії: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsНадсилайте Ваші зображення. Допоможіть оживити статті Вікіпедії!Зображення у Вікіпедії надходять з Вікісховища.Ваші зображення допомагають освіті людей у всьому світі.
@@ -142,7 +142,8 @@
Немає описуНевідома ліцензіяОновити
- Обов\'язковий дозвіл: читання зовнішньої пам\'яті. Програмка не може працювати без цього.
+ Обов\'язковий дозвіл: читання зовнішньої пам\'яті. Програма не може працювати без цього.
+ Обов\'язковий дозвіл: записування на зовнішнє сховище. Програма не може працювати без цього.Додатковий дозвіл: отримувати поточне розташування для підказок категорійГараздМісця поблизу
@@ -160,9 +161,9 @@
КоординатиНе передбаченоСтаньте бета-тестером
- Увійдіть на наш бета-канал на Google Play і отримайте ранній доступ до нових функцій та виправлень багів
+ Підпишіться на наш бета-канал на Google Play і отримайте ранній доступ до нових функцій та виправлень багівВикористати Вікідані
- (Попередження: вимкнення цього може спричинити використання значного обсягу мобільних даних)
+ (Попередження: вимкнення може спричинити використання значного обсягу мобільних даних)Код 2FAЛіміт моїх останніх завантаженьМаксимальний ліміт
@@ -171,6 +172,9 @@
Двофакторна автентифікація наразі не підтримується.Ви справді хочете вийти із системи?Логотип Вікісховища
+ Веб-сайт Commons
+ Фейсбук сторінка Commons
+ Програмний код Commons на GitHubФонове зображенняПомилка медіазображенняНе знайдено зображення
@@ -195,6 +199,7 @@
Зворотний зв\'язокВийтиПосібник
+ СповіщенняМісця поблизу неможливо показати без дозволу на визначення місця розташування.опис не знайденоСторінка файлу у Вікісховищі
@@ -202,4 +207,20 @@
Помилка кешування зображеньУнікальна описова назва файлу. Ви можете використовувати простий текст з пробілами. Не вказуйте розширення файлуБудь ласка, докладно опишіть файл: де його було зроблено? що на ньому зображено? який контекст? Будь ласка, опишіть об\'єкти чи осіб. Додайте інформацію, яку не можна легко здогадатися, наприклад, пору доби для фотографії пейзажу. Якщо зображено щось незвичайне, постарайтеся пояснити, що робить його незвичайним.
+ Надати дозвіл
+ Використовувати зовнішнє сховище
+ Зберігати зображення, виконані вбудованою камерою Вашого пристрою
+ Надіслати лог-файл
+ Надіслати лог-файл розробникам електронною поштою
+ Увійдіть у свій обліковий запис
+ Розташування не змінено
+ Місцезнаходження недоступне
+ Потрібний дозвіл для показу списку місць поблизу
+ Показати на мапі у зовнішній програмі
+ ЧИТАТИ СТАТТЮ
+ Вітаємо у Wikimedia Commons, %1$s! Раді вас бачити.
+ %1$s залишив повідомлення на вашій сторінці обговорення
+ Дякуємо за правку
+ %1$s згадав вас на %2$s.
+ Перемкнути режим перегляду
diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml
new file mode 100644
index 000000000..a78c0d7e4
--- /dev/null
+++ b/app/src/main/res/values-ur/strings.xml
@@ -0,0 +1,127 @@
+
+
+ کامنز
+ ترتیبات
+ صارف نام
+ پاس ورڈ
+ داخل ہوں
+ کھاتہ بنائیں
+ لاگ آن
+ براہ مہربانی کچھ دیر انتظار کریں۔۔۔
+ لاگ ان کامیاب۔
+ داخل ہونے میں ناکامی ہوئی!
+ فائل نہیں ملی، براہ کرم دوسری فائل آزمائیں۔
+ تصدیق ناکام!
+ اپلوڈ شروع!
+ %1$s اپلوڈ شد!
+ اپنی اپلوڈ دیکھنے کے لیے ٹیپ کریں۔
+ %1$s کی اپلوڈنگ شروع ہو رہی ہے
+ %1$s اپلوڈ جاری
+ %1$s کی اپلوڈنگ مکمل ہو رہی ہے
+ اپلوڈ %1$s ہونے میں ناکام
+ دیکھنے کے لیے ٹیپ کریں
+
+ %1$d فائل اپلوڈ ہورہی ہے
+ %1$d فائلیں اپلوڈ ہورہی ہے
+
+ میں حالیہ اپلوڈ
+ قطار
+ ناکام
+ %1$d%% مکمل
+ اپلوڈ جاری ہے
+ از نگار خانہ
+ تصویر لیں
+ قریبی
+ میری اپلوڈ
+ شیئر
+ براؤزر میں کھولیں
+ عنوان
+ وضاحت
+ لاگ ان ہونے میں ناکام - نیٹ ورک ناکامی
+ لاگ ان ہونے میں ناکام - براہ مہربانی اپنا صارف نام کی جانچ کریں
+ لاگ ان ہونے میں ناکام - براہ مہربانی - اپنے پاس ورڈ کی جانچ کریں
+ بے شمار ناکام کوششیں کچھ منٹوں میں دوبارہ کوشش کریں۔
+ معذرت، یہ صارف کومنز پر بلاک کردیا گیا ہے
+ آپ کو اپنے دو عامل کے تصدیق کوڈ فراہم کرنا چاہیے۔
+ داخل ہونے میں ناکام
+ اپلوڈ کریں
+ سیٹ کو نام دیں
+ اصلاحات
+ اپلوڈ کریں
+ زمرہ جات تلاش کریں
+ محفوظ کریں
+ تازہ کریں
+ جی پی ایس آپ کے آلے میں غیر فعال ہے۔ آپ اس کو فعال کرنا چاہینگے؟
+ جی پی ایس فعال کریں
+ ابھی تک کوئی اپلوڈ نہیں
+
+ \@string/contributions_subtitle_zero
+ %1$d اپلوڈ
+ %1$d اپلوڈ
+
+
+ شروع %1$d اپلوڈ
+ شروع %d$1 اپلوڈ
+
+
+ %1$d اپلوڈ
+ %1$d اپلوڈ
+
+ %1$s سے کوئی زمرہ جات میل نہیں کھاتے
+ ویکیمیڈیا کامنز پر اپنی تصاویر کو قابل دریافت بنانے کے لیے زمرے شامل کریں۔\n\nزمرے شامل کرنے کے لیے لکھنا شروع کریں۔\n\nاس مرحلے کو نظر انداز کرنے کے لیے اس پیغام یا (یا پیچھے) پر ٹیپ کریں۔
+ زمرہ جات
+ ترتیبات
+ کھاتہ بنائیں
+ بابت
+ ویکیمیڈیا کامنز ایپ ایک اوپن سورس ایپ ہے جو موہوب الیہ اور رضاکاروں کی جانب سے برقرار رکھی جاتی اور بنائی گئی ہے۔ ویکیمیڈیا فاؤنڈیش کا اس ایپ کی برقراری اور تخلیق سے کوئی واسطہ نہیں ہے۔
+ <a href=\"https://github.com/commons-app/apps-android-commons\">ماخذ</a> اور <a href=\"https://commons-app.github.io/\">ویب سائٹ</a> در گٹ ہب۔<a href=\"https://github.com/commons-app/apps-android-commons/issues\">نیا گٹ ہب اجرا بنائیں</a> تاکہ آپ bug شکایتیں اور تجایز دیں سکیں
+ <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">سرگرمی کی تدبیر</a>
+ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">کریڈٹ</a>
+ تعارف
+ فیڈبیک بھیجیے (براستہ ای میل)
+ ای میل کلائنٹ انسٹال نہیں
+ حال ہی میں استعمال کیے گئے زمرے
+ سب سے پہلے sync کے انتظار میں۔۔۔
+ آپ نے ابھی تک کوئی تصاویر اپلوڈ نہیں کی ہے۔
+ دوبارہ کوشش کریں
+ منسوخ
+ %1$s تلے یہ تصویر لائسنس یافتہ ہو گی
+ اس تصویر کو درج کر کے، میں اقرار کرتا ہوں کہ یہ میرا اپنا کام ہے، اس ميں کاپی رائٹ مواد یا سیلفیاں نہیں ہیں، اور بصورت دیگر میں <a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\">ویکیمیڈیا کامنز پالیسیوں</a> کا پابند ہوں۔
+ ڈاؤنلوڈ
+ اجازت نامہ
+ گزشتہ عنوان/وضاحت استعمال کریں
+ خودکارانہ طریقے سے حالیہ جگہ حاصل کریں
+ اگر تصویر جغرافیائی نہیں ہے تو قسم کے تجاویز پیش کرنے کیلئے موجودہ مقام کو دوبارہ حاصل کریں
+ نائٹ موڈ
+ کالا تھیم استعمال کریں
+ انتباہ-شراکت 4.0
+ انتباہ 4.0
+ انتباہ-شراکت 3.0
+ انتباہ 3.0
+ CC0
+ CC BY-SA 3.0 (آسٹریا)
+ CC BY-SA 3.0 (جرمنی)
+ CC BY-SA 3.0 (اسٹونیا)
+ CC BY-SA 3.0 (اسپین)
+ CC BY-SA 3.0 (کرویئشا)
+ CC BY-SA 3.0 (لکسمبرگ)
+ ہاں!
+ زمرہ جات
+ لوڈ ہو رہا ہے۔۔۔
+ غیر منتخب
+ کوئی وضاحت نہیں
+ نامعلوم اجازت نامہ
+ ریفریش کریں
+ ٹھیک ہے
+ جی
+ نہیں
+ اجازت نامہ
+ متناسقات
+ کامنز لوگو
+ کوئی سیلفی نہیں
+ تعارف
+ ترتیبات
+ آپ کی رائے
+ لاگ آوٹ
+ معلمی
+
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index ab0ecfd08..7bfdf837f 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -20,7 +20,7 @@
Đang hoàn thành việc tải lên tập tin %1$sTải lên tập tin %1$s thất bạiChạm để xem
- %d tập tin đang được tải lên
+ %d tập tin đang được tải lênTập tin Tôi đã Tải lên Gần đâyĐang chờThất bại
@@ -51,20 +51,20 @@
Chức năng GPS đang tắt trên thiết bị của bạn. Bạn có muốn bật nó lên?Bật GPSChưa có tập tin tải lên
-
+ \@string/contributions_subtitle_zero
- %d tập tin tải lên
+ %1$d tập tin tải lên
- Đang bắt đầu tải lên %d tập tin
- %d tập tin tải lên
+ Đang bắt đầu tải lên %1$d tập tin
+ %1$d tập tin tải lênKhông tìm thấy thể loại khớp với %1$s
- Xếp các hình ảnh vào thể loại để cho chúng dễ tìm kiếm hơn trên Wikimedia Commons.\n\nHãy bắt đầu nhập tên thể loại để tìm kiếm.\nChạm vào thông điệp này (hoặc bấm Quay lại) để bỏ qua bước này.
+ Xếp các hình ảnh vào thể loại để cho chúng dễ tìm kiếm hơn trên Wikimedia Commons.\n\nHãy bắt đầu nhập tên thể loại để tìm kiếm.\nChạm vào thông điệp này (hoặc bấm Quay lại) để bỏ qua bước này.Thể loạiCài đặtMở tài khoảnGiới thiệuPhần mềm mã nguồn mở được phát hành theo <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Giấy phép Apache v2</a>. %1$s và biểu trưng của nó là nhãn hiệu của Quỹ Wikimedia và được sử dụng do Quỹ Wikimedia cho phép. Chúng tôi không được Quỹ Wikimedia ủng hộ hoặc nhận làm chi nhánh.
- <a href=\"https://github.com/commons-app/apps-android-commons\">Mã nguồn</a> và <a href=\"https://commons-app.github.io/\">trang chủ</a> tại GitHub. <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Tạo vấn đề GitHub mới</a> để báo cáo lỗi hoặc gợi ý thay đổi.
+ <a href=\"https://github.com/commons-app/apps-android-commons\">Mã nguồn</a> và <a href=\"https://commons-app.github.io/\">trang chủ</a> tại GitHub. <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Tạo vấn đề GitHub mới</a> để báo cáo lỗi hoặc gợi ý thay đổi.<a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Công trạng</a>Giới thiệu
@@ -111,7 +111,7 @@
Xin DỪNG tải lên:- Hình tự sướng hoặc hình bạn bè\n- Hình ảnh tải về từ Internet\n- Ảnh chụp màn hình của ứng dụng thương mạiTập tin tải lên ví dụ:
- - Tiêu đề: Nhà hát Opera Sydney\n- Miêu tả: Nhà hát Opera Sydney nhìn qua cảng\n- Thể loại: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Tiêu đề: Nhà hát Opera Sydney\n- Miêu tả: Nhà hát Opera Sydney nhìn qua cảng\n- Thể loại: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsĐóng góp hình ảnh của bạn. Làm sinh động các bài viết Wikipedia!Các hình ảnh trên Wikipedia được cung cấp bởi Wikimedia Commons.Các hình ảnh của bạn giúp giáo dục người dân trên khắp thế giới.
diff --git a/app/src/main/res/values-xmf/strings.xml b/app/src/main/res/values-xmf/strings.xml
index 23933ab25..cec936e7c 100644
--- a/app/src/main/res/values-xmf/strings.xml
+++ b/app/src/main/res/values-xmf/strings.xml
@@ -20,9 +20,9 @@
ეხარგუაშ თება %1$%1$ ეხარგუაქ ვემიხუჯინუოძირაფალო ქეგუწკანტეთ
-
- %d ფაილი იხარგუ
- %d ფაილი იხარგუ
+
+ %1$d ფაილი იხარგუ
+ %1$d ფაილი იხარგუჩქიმი ბოლო ეხარგუეფირადი
@@ -56,16 +56,16 @@
ეხარგუეფი ვა რე\@string/contributions_subtitle_zero
- %d ეხარგუა
- %d ეხარგუა
+ %1$d ეხარგუა
+ %1$d ეხარგუა
- იჭყაფუ %d ეხარგუა
- იჭყაფუ %d ეხარგუა
+ იჭყაფუ %1$d ეხარგუა
+ იჭყაფუ %1$d ეხარგუა
-
- %d ეხარგუა
- %d ეხარგუა
+
+ %1$d ეხარგუა
+ %1$d ეხარგუაკატეგორიაპარამეტრეფი
@@ -105,7 +105,7 @@
ქორთხინთ ვეხარგათ:- სელფი ვარდა მაჸალეეფიშ სურათეფი\n- ინტერნეტშე გჷმოხარგილი ფოტოეფი\n- ვადუდიშული აპლიკაციეფიშ სკრინშოტეფიეხარგუაშ მინუში:
- - დუდჯოხო: სიდნეიშ ოპერაშ თეატრი\n- ეჭარუა: სიდნეიშ ოპერაშ თეატრიშ მიოჯინი ლუბაშე\n- კატეგორიეფი: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - დუდჯოხო: სიდნეიშ ოპერაშ თეატრი\n- ეჭარუა: სიდნეიშ ოპერაშ თეატრიშ მიოჯინი ლუბაშე\n- კატეგორიეფი: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote viewsგეხარგეთ თქვანი ფოტოეფი. ქემეხვარით ვიკიპედიაშ სტატიეფიშ გაჭყანიერებას!ვიკიპედიაშ ფოტოეფი ვიკიოწკარუეს იჩუალუაფუ.თქვანი სურათეფი კათაშ გონათუას ოხვარუ ედომუშამ მოსოფელს.
diff --git a/app/src/main/res/values-yi/strings.xml b/app/src/main/res/values-yi/strings.xml
index 0c0c3287b..27ea76605 100644
--- a/app/src/main/res/values-yi/strings.xml
+++ b/app/src/main/res/values-yi/strings.xml
@@ -20,7 +20,7 @@
דרוקט צו באקוקן1 טעקע לאדט אן
- %d טעקעס לאדן אן
+ %1$d טעקעס לאדן אןמײַנע ארויפלאדןאין ריי
@@ -43,7 +43,7 @@
נאך נישט קיין ארויפֿלאדן 1 ארויפֿלאד
- %d ארויפֿלאדן
+ %1$d ארויפֿלאדןקאַטעגאריעסבאניץ באריכטן
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 2d5756fe8..379cc42ae 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -19,8 +19,8 @@
上传%1$s失败轻触以查看
- %d个文件上传中
- %d个文件上传中
+ %1$d个文件上传中
+ %1$d个文件上传中我上传的内容队列
@@ -47,17 +47,17 @@
搜索分类保存
- %d次上传
- %d次上传
+ %1$d次上传
+ %1$d次上传没有上传
- 开始%d次上传
- 开始%d次上传
+ 开始%1$d次上传
+ 开始%1$d次上传
- %d次上传
- %d次上传
+ %1$d次上传
+ %1$d次上传没有找到匹配%1$s的分类为您的图片添加分类,让它们在维基共享上更容易被找到。
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 24faaab74..fa6265e52 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -21,8 +21,8 @@
上傳%1$s失敗輕觸檢視
- 正在上載 %d 個檔案
- 正在上載 %d 個檔案
+ 正在上載 %1$d 個檔案
+ 正在上載 %1$d 個檔案我的最近上傳已佇列
@@ -56,25 +56,25 @@
尚未上傳\@string/contributions_subtitle_zero
- %d 次上傳
- %d 次上傳
+ %1$d 次上傳
+ %1$d 次上傳
- 開始 %d 次上傳
- 開始 %d 次上傳
+ 開始 %1$d 次上傳
+ 開始 %1$d 次上傳
- %d 次上傳
- %d 次上傳
+ %1$d 次上傳
+ %1$d 次上傳沒有發現與 %1$s 相符的分類
- 為您的圖片添加分類,使別人在維基共享資源更容易找到。\n\n開始輸入以添加分類。\n按此(或按返回)跳過此步驟。
+ 為您的圖片添加分類,使別人在維基共享資源更容易找到。\n\n開始輸入以添加分類。分類設定註冊關於
- 維基共享資源應用程式是透過維基媒體社群上的受讓人、與志願者們所建立及維護的開放原始碼應用程式。維基媒體基金會並不涉及此應用程式的建立、開發、與維護方面。
- <a href=\"https://github.com/commons-app/apps-android-commons\">原始碼</a>和<a href=\"https://commons-app.github.io/\">網站</a>位於GitHub上。建立新的<a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub問題</a>來回報問題和提出建議。
+ 維基共享資源應用程式是透過維基媒體社群上的受讓人,與志願者們所建立及維護的開放原始碼應用程式。維基媒體基金會並不涉及此應用程式的建立、開發,與維護方面。
+ 建立新的<a href=\"https://github.com/commons-app/apps-android-commons/issues\"> GitHub 問題</a>來回報程式錯誤和提出建議。<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">隱私方針</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">製作群</a>關於
@@ -94,11 +94,11 @@
若圖片未有地理標記,就以目前位置來作為分類建議。夜間模式使用暗黑佈景主題
- 姓名標示-相同方式分享4.0
+ 姓名標示-相同方式分享4.0姓名標示4.0 姓名標示-相同方式分享3.0姓名標示3.0
- CC0
+ 公眾領域貢獻宣告CC姓名標示-相同方式分享3.0CC姓名標示-相同方式分享3.0(奧地利)CC姓名標示-相同方式分享3.0(德國)
@@ -113,7 +113,7 @@
CC姓名標示3.0創用CC姓名標示-相同方式分享4.0創用CC姓名標示4.0
- CC0
+ 公眾領域貢獻宣告維基共享資源管理大部份使用在維基百科的圖片。您的圖片幫助教育了世界上的人們!請上傳完全由您自己拍攝或創作的圖片:
@@ -121,7 +121,7 @@
請*不要*上傳:- 您自己的自拍照,或您朋友的照片\n- 從網路下載來的圖片\n- 專利版權應用程式的截取圖片上傳範例:
- - 標題:雪梨歌劇院\n- 說明:從對面海灣所看到的雪梨歌劇院\n- 分類:Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - 標題:雪梨歌劇院\n- 說明:從對面海灣所看到的雪梨歌劇院\n- 分類:Sydney Opera House from the west、Sydney Opera House remote views貢獻您的圖片,使維基百科的文章更加生動!維基百科的圖片,來自維基共享資源。您的圖片可以幫助教育世界各地的人。
@@ -164,6 +164,9 @@
目前不支援雙重因素身分核對。真要登出嗎?共享資源標誌
+ 共享資源網站
+ 共享資源臉書頁面
+ Github 上的共享資源原始碼背景圖片媒體圖片失敗找不到圖片
@@ -188,14 +191,28 @@
意見回饋登出教程
+ 通知附近地點需要位置權限才可顯示找不到說明共享資源檔案頁面維基數據項目在快取圖片時發生錯誤用於本檔案的唯一描述性標題。您可以使用帶有空格的簡明語言,另外請不要包含副檔名。
- 請盡可能說明媒體內容:拍攝於何處?是顯示什麼事物?有什麼脈絡?請描述對象或人物。透露出一些較不易猜測的訊息,例如是風景的話;可以是一天裡的時間。如果媒體顯示出一些不正常的事物,請說明出造成不正常原因。
+ 請盡可能說明媒體內容:拍攝於何處?是顯示什麼事物?有什麼脈絡?請描述對象或人物。透露出一些較不易猜測的訊息,例如是風景的話,可以是一天裡的時間。如果媒體顯示出一些不正常的事物,請說明出造成不正常原因。給予權限使用外部存儲裝置在您的裝置上使用照相機應用程式來儲存照片
+ 寄送日誌檔案
+ 經由電子郵件寄送日誌檔案給開發人員
+ 登入您的帳號
+ 位置無法更改。
+ 位置無效。
+ 需權限來顯示附近地點清單
+ 取得導向
+ 讀取條目
+ %1$s,歡迎來到維基共享資源!我們很高興您來到這裡。
+ %1$s 留了訊息在您的對話頁
+ 感謝您所做的編輯
+ %1$s 在 %2$s 提到了您。
+ 切換檢視
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 4d9f6ef7d..201f93f42 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -21,8 +21,8 @@
上传%1$s失败点击查看
- %d个文件正在上传
- %d个文件正在上传
+ %1$d个文件正在上传
+ %1$d个文件正在上传我的最近上传已入队列
@@ -56,25 +56,25 @@
尚无上传\@string/contributions_subtitle_zero
- %d次上传
- %d次上传
+ %1$d次上传
+ %1$d次上传
- 开始%d次上传
- 开始%d次上传
+ 开始%1$d次上传
+ 开始%1$d次上传
- %d次上传
- %d次上传
+ %1$d次上传
+ %1$d次上传没有找到匹配%1$s的分类
- 添加分类使您的图像更容易在维基共享资源被找到。\n\n开始输入以添加分类。点击该信息(或按返回)以跳过此步。
+ 添加分类使您的图像更容易在维基共享资源被找到。开始输入以添加分类。分类设置注册关于维基共享资源应用程序是由维基媒体社区的受助者和志愿者创建和维护的开源应用程序。维基媒体基金会不参与该应用程序的创立,开发或维护。
- <a href=\"https://github.com/commons-app/apps-android-commons\">源代码</a>和<a href=\"https://commons-app.github.io/\">网站</a>位于GitHub。创建新的<a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub问题</a>以发送错误报告和建议。
+ 创建新的<a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub问题</a>以发送错误报告和建议。<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">隐私政策</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">制作人员</a>关于
@@ -121,7 +121,7 @@
请不要上传:- 您朋友的自拍照或图片\n- 您从互联网下载的图片\n- 专利应用的截图示例上传:
- - 标题:悉尼歌剧院\n- 描述:从海湾对面看到的悉尼歌剧院\n- 分类:Sydney Opera House、Sydney Opera House from the west、Sydney Opera House remote views
+ - 标题:悉尼歌剧院\n- 描述:从海湾对面看到的悉尼歌剧院\n- 分类:Sydney Opera House from the west、Sydney Opera House remote views贡献您的图像。使维基百科的条目更加生动!维基百科上的图像来自维基共享资源。您的图像可以帮助教育世界各地的人。
@@ -164,6 +164,9 @@
目前不支持双因素验证。您真的想要退出么?共享资源标志
+ 共享资源网站
+ 共享资源Facebook页面
+ 共享资源Github源代码背景图片媒体图片失败找不到图片
@@ -188,6 +191,7 @@
反馈退出教程
+ 通知附近地点不能在没有位置权限的情况下显示找不到描述共享资源文件页面
@@ -198,4 +202,17 @@
提供权限使用外部存储在您的设备上,使用应用中的照相机保存照片
+ 发送日志文件
+ 通过电子邮件将日志文件发送给开发人员
+ 登录您的账户
+ 位置没有更新。
+ 位置不可用。
+ 需要权限以显示附近地点列表
+ 获得指示
+ 阅读条目
+ 欢迎来到维基共享资源,%1$s!我们很高兴您来这里。
+ %1$s在您的讨论页上留下了一条消息
+ 感谢您做出编辑
+ %1$s在%2$s提到了您。
+ 切换视图
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 22da8c207..38331d635 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -2,17 +2,12 @@
-
-
-
-
-
-
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 828d5a600..ccca64215 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,16 +1,26 @@
+
+ #303030
+ #fafafa
+
+
+ #0c609c
+ #00376d
+ #528dcd
+ #d7e8fb
+ #f38b04
+ #ba5d00
+ #ffbc46
+ #ffffff
+ #000000
+
#ffffffff
- #000000
- #ffffff#33FFFFFF#33FFFFFF
- #0c609c
- #0c609c
- #0c609c#08436d#20ffffff#20ffffff
@@ -20,4 +30,5 @@
#B0000000#77000000#44000000
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 40c859118..1e98a1e04 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,4 +1,26 @@
240dp
+
+
+ 16dp
+ 16dp
+
+
+ 48dp
+ 32dp
+ 16dp
+ 8dp
+ 4dp
+
+
+ 56dp48dp
+ 24dp
+ 12dp
+
+
+ 24sp
+ 20sp
+ 16sp
+ 14sp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9e7825b6b..ceba924e1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -21,8 +21,8 @@
Uploading %1$s failedTap to view
- %d file uploading
- %d files uploading
+ %1$d file uploading
+ %1$d files uploadingMy Recent UploadsQueued
@@ -57,29 +57,26 @@
@string/contributions_subtitle_zero
- %d upload
- %d uploads
+ %1$d upload
+ %1$d uploads
- Starting %d upload
- Starting %d uploads
+ Starting %1$d upload
+ Starting %1$d uploads
- %d upload
- %d uploads
+ %1$d upload
+ %1$d uploadsNo categories matching %1$s found
- Add categories to make your images more discoverable on Wikimedia Commons.
-
-Start typing to add categories.
-Tap this message (or hit back) to skip this step.
+ Add categories to make your images more discoverable on Wikimedia Commons.\nStart typing to add categories.CategoriesSettingsSign UpAboutThe Wikimedia Commons app is an open-source app created and maintained by grantees and volunteers of the Wikimedia community. The Wikimedia Foundation is not involved in the creation, development, or maintenance of the app. Wikimedia Commons
- <a href=\"https://github.com/commons-app/apps-android-commons\">Source</a> and <a href=\"https://commons-app.github.io/\">website</a> on GitHub. Create a new <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub issue</a> for bug reports and suggestions.
+ Create a new <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub issue</a> for bug reports and suggestions.<a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Privacy policy</a><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Credits</a>About
@@ -126,7 +123,7 @@ Tap this message (or hit back) to skip this step.Please do NOT upload:- Selfies or pictures of your friends\n- Pictures you downloaded from the Internet\n- Screenshots of proprietary appsExample upload:
- - Title: Sydney Opera House\n- Description: Sydney Opera House as viewed from across the bay\n- Categories: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views
+ - Title: Sydney Opera House\n- Description: Sydney Opera House as viewed from across the bay\n- Categories: Sydney Opera House from the west, Sydney Opera House remote viewsContribute your images. Help Wikipedia articles come to life!Images on Wikipedia come from Wikimedia Commons.Your images help educate people around the world.
@@ -173,6 +170,9 @@ Tap this message (or hit back) to skip this step.Two factor authentication is currently not supported.Do you really want to logout?Commons Logo
+ Commons Website
+ Commons Facebook Page
+ Commons Github Source CodeBackground ImageMedia Image FailedNo Image Found
@@ -197,6 +197,7 @@ Tap this message (or hit back) to skip this step.FeedbackLogoutTutorial
+ NotificationsNearby places cannot be displayed without location permissionsno description foundCommons file page
@@ -207,4 +208,19 @@ Tap this message (or hit back) to skip this step.Give permissionUse external storageSave pictures taken with the in-app camera on your device
+ Send log file
+ Send log file to developers via email
+ Login to your account
+
+ Location has not changed.
+ Location not available.
+ Permission required to display a list of nearby places
+ GET DIRECTIONS
+ READ ARTICLE
+
+ Welcome to Wikimedia Commons, %1$s! We\'re glad you\'re here.
+ %1$s left a message on your talk page
+ Thank you for making an edit
+ %1$s mentioned you on %2$s.
+ Toggle view
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a49b7b163..a7abfc17d 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -2,37 +2,33 @@