mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
Merge remote-tracking branch 'origin/master' into structured-data
# Conflicts: # app/build.gradle # app/src/main/java/fr/free/nrw/commons/CommonsApplication.java # app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java # app/src/main/java/fr/free/nrw/commons/contributions/model/DisplayableContribution.java # app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java # app/src/main/java/fr/free/nrw/commons/upload/SpinnerLanguagesAdapter.java # app/src/main/java/fr/free/nrw/commons/upload/UploadService.java # app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java # app/src/main/java/fr/free/nrw/commons/wikidata/WikidataEditService.java
This commit is contained in:
commit
719f32c27e
127 changed files with 1946 additions and 1520 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1,10 +1,13 @@
|
|||
# IDEA files
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
.idea
|
||||
*.iws
|
||||
/.idea/*
|
||||
app/src/main/gen/*
|
||||
|
||||
# IDEA/Android Studio Ignore exceptions
|
||||
!/.idea/codeStyles/
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
local.properties
|
||||
|
|
|
|||
544
.idea/codeStyles/Project.xml
generated
Normal file
544
.idea/codeStyles/Project.xml
generated
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<option name="OTHER_INDENT_OPTIONS">
|
||||
<value>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</value>
|
||||
</option>
|
||||
<AndroidXmlCodeStyleSettings>
|
||||
<option name="LAYOUT_SETTINGS">
|
||||
<value>
|
||||
<option name="INSERT_BLANK_LINE_BEFORE_TAG" value="false" />
|
||||
</value>
|
||||
</option>
|
||||
</AndroidXmlCodeStyleSettings>
|
||||
<JSCodeStyleSettings>
|
||||
<option name="INDENT_CHAINED_CALLS" value="false" />
|
||||
</JSCodeStyleSettings>
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<Objective-C>
|
||||
<option name="INDENT_NAMESPACE_MEMBERS" value="0" />
|
||||
<option name="INDENT_C_STRUCT_MEMBERS" value="2" />
|
||||
<option name="INDENT_CLASS_MEMBERS" value="2" />
|
||||
<option name="INDENT_VISIBILITY_KEYWORDS" value="1" />
|
||||
<option name="INDENT_INSIDE_CODE_BLOCK" value="2" />
|
||||
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||
<option name="FUNCTION_PARAMETERS_WRAP" value="5" />
|
||||
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="5" />
|
||||
<option name="TEMPLATE_CALL_ARGUMENTS_WRAP" value="5" />
|
||||
<option name="TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE" value="true" />
|
||||
<option name="ALIGN_INIT_LIST_IN_COLUMNS" value="false" />
|
||||
<option name="SPACE_BEFORE_SUPERCLASS_COLON" value="false" />
|
||||
</Objective-C>
|
||||
<Objective-C-extensions>
|
||||
<extensions>
|
||||
<pair source="cc" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<Python>
|
||||
<option name="USE_CONTINUATION_INDENT_FOR_ARGUMENTS" value="true" />
|
||||
</Python>
|
||||
<TypeScriptCodeStyleSettings>
|
||||
<option name="INDENT_CHAINED_CALLS" value="false" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<XML>
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
<codeStyleSettings language="CSS">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ECMA Script Level 4">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="THROWS_KEYWORD_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="WRAP_COMMENTS" value="true" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JSON">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||
<option name="IF_BRACE_FORCE" value="3" />
|
||||
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||
<option name="FOR_BRACE_FORCE" value="3" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="ObjectiveC">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
|
||||
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||
<option name="BLANK_LINES_AFTER_IMPORTS" value="0" />
|
||||
<option name="BLANK_LINES_AROUND_CLASS" value="0" />
|
||||
<option name="BLANK_LINES_AROUND_METHOD" value="0" />
|
||||
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="false" />
|
||||
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="PROTO">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Python">
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SASS">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="SCSS">
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:.*Style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_width</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_height</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_weight</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_margin</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginTop</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginBottom</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginStart</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginEnd</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginLeft</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_marginRight</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:layout_.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:padding</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingTop</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingBottom</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingStart</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingEnd</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingLeft</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:paddingRight</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res-auto</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>http://schemas.android.com/tools</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="protobuf">
|
||||
<option name="RIGHT_MARGIN" value="80" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="2" />
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||
<option name="TAB_SIZE" value="2" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
|
|
@ -8,7 +8,7 @@ What changes did you make and why?
|
|||
|
||||
Tested {build variant, e.g. ProdDebug} on {name of device or emulator} with API level {API level}.
|
||||
|
||||
**Screenshots showing what changed (optional - for UI changes)**
|
||||
**Screenshots (for UI changes only)**
|
||||
|
||||
Need help? See https://support.google.com/android/answer/9075928
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ apply from: '../gitutils.gradle'
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: "com.hiya.jacoco-android"
|
||||
apply from: 'quality.gradle'
|
||||
|
||||
|
|
@ -38,6 +39,7 @@ dependencies {
|
|||
implementation 'com.github.pedrovgs:renderers:3.3.3'
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:8.6.2'
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v8:0.11.0'
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v9:0.4.0'
|
||||
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
|
||||
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
|
||||
implementation 'com.karumi:dexter:5.0.0'
|
||||
|
|
@ -105,13 +107,18 @@ dependencies {
|
|||
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
|
||||
|
||||
//Room
|
||||
def room_version= '2.2.3'
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
|
||||
implementation "androidx.room:room-runtime:$ROOM_VERSION"
|
||||
implementation "androidx.room:room-ktx:$ROOM_VERSION"
|
||||
implementation "androidx.room:room-rxjava2:$ROOM_VERSION"
|
||||
kapt "androidx.room:room-compiler:$ROOM_VERSION" // For Kotlin use kapt instead of annotationProcessor
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
|
||||
implementation "androidx.room:room-rxjava2:$room_version"
|
||||
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||
|
||||
// Pref
|
||||
// Java language implementation
|
||||
implementation "androidx.preference:preference:$PREFERENCE_VERSION"
|
||||
// Kotlin
|
||||
implementation "androidx.preference:preference-ktx:$PREFERENCE_VERSION"
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -293,3 +300,7 @@ if(isRunningOnTravisAndIsNotPRBuild) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1 +0,0 @@
|
|||
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":90,"versionName":"2.8.3","enabled":true,"outputFile":"app-commons-v2.8.3-acra-prod-release.apk","fullName":"prodRelease","baseName":"prod-release"},"path":"app-commons-v2.8.3-acra-prod-release.apk","properties":{}}]
|
||||
|
|
@ -76,6 +76,10 @@
|
|||
-keepattributes SourceFile,LineNumberTable
|
||||
-keepattributes *Annotation*
|
||||
|
||||
# --- /recycler view ---
|
||||
-keep class androidx.recyclerview.widget.RecyclerView {
|
||||
public androidx.recyclerview.widget.RecyclerView$ViewHolder findViewHolderForPosition(int);
|
||||
}
|
||||
# --- Parcelable ---
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
static ** CREATOR;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Instrumentation
|
||||
import android.content.Intent
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import fr.free.nrw.commons.utils.ConfigUtils
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AboutActivityTest {
|
||||
@get:Rule
|
||||
var activityRule: ActivityTestRule<*> = ActivityTestRule(AboutActivity::class.java)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Intents.init()
|
||||
Intents.intending(CoreMatchers.not(IntentMatchers.isInternal()))
|
||||
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuildNumber() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.about_version))
|
||||
.check(ViewAssertions.matches(withText(ConfigUtils.getVersionNameWithSha(getApplicationContext()))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchWebsite() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.website_launch_icon)).perform(ViewActions.click())
|
||||
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
|
||||
IntentMatchers.hasData(Urls.WEBSITE_URL)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchFacebook() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.facebook_launch_icon)).perform(ViewActions.click())
|
||||
Intents.intended(IntentMatchers.hasAction(Intent.ACTION_VIEW))
|
||||
Intents.intended(CoreMatchers.anyOf(IntentMatchers.hasData(Urls.FACEBOOK_WEB_URL),
|
||||
IntentMatchers.hasPackage(Urls.FACEBOOK_PACKAGE_NAME)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchGithub() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.github_launch_icon)).perform(ViewActions.click())
|
||||
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
|
||||
IntentMatchers.hasData(Urls.GITHUB_REPO_URL)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchRateUs() {
|
||||
val appPackageName = InstrumentationRegistry.getInstrumentation().targetContext.packageName
|
||||
Espresso.onView(ViewMatchers.withId(R.id.about_rate_us)).perform(ViewActions.click())
|
||||
Intents.intended(IntentMatchers.hasAction(Intent.ACTION_VIEW))
|
||||
Intents.intended(CoreMatchers.anyOf(IntentMatchers.hasData("${Urls.PLAY_STORE_URL_PREFIX}$appPackageName"),
|
||||
IntentMatchers.hasData("${Urls.PLAY_STORE_URL_PREFIX}$appPackageName")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchAboutPrivacyPolicy() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.about_privacy_policy)).perform(ViewActions.click())
|
||||
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
|
||||
IntentMatchers.hasData(BuildConfig.PRIVACY_POLICY_URL)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchTranslate() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click())
|
||||
Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click())
|
||||
val langCode = CommonsApplication.getInstance().languageLookUpTable.codes[0]
|
||||
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
|
||||
IntentMatchers.hasData("${Urls.TRANSLATE_WIKI_URL}$langCode")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchAboutCredits() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.about_credits)).perform(ViewActions.click())
|
||||
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
|
||||
IntentMatchers.hasData(Urls.CREDITS_URL)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLaunchAboutFaq() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.about_faq)).perform(ViewActions.click())
|
||||
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
|
||||
IntentMatchers.hasData(Urls.FAQ_URL)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun orientationChange() {
|
||||
UITestHelper.changeOrientation(activityRule)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,12 @@ import androidx.test.espresso.action.ViewActions
|
|||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import org.hamcrest.BaseMatcher
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
class UITestHelper {
|
||||
companion object {
|
||||
fun skipWelcome() {
|
||||
|
|
@ -34,7 +38,7 @@ class UITestHelper {
|
|||
closeSoftKeyboard()
|
||||
onView(ViewMatchers.withId(R.id.login_button))
|
||||
.perform(ViewActions.click())
|
||||
sleep(5000)
|
||||
sleep(10000)
|
||||
} catch (ignored: NoMatchingViewException) {
|
||||
}
|
||||
|
||||
|
|
@ -68,5 +72,22 @@ class UITestHelper {
|
|||
activityRule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
assert(activityRule.activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
|
||||
}
|
||||
|
||||
fun <T> first(matcher: Matcher<T>): Matcher<T>? {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun matches(item: Any): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("should return first matching item")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@ import android.graphics.Bitmap
|
|||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.NoMatchingViewException
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
|
|
@ -24,6 +26,8 @@ import androidx.test.rule.ActivityTestRule
|
|||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import fr.free.nrw.commons.auth.LoginActivity
|
||||
import fr.free.nrw.commons.upload.DescriptionsAdapter
|
||||
import fr.free.nrw.commons.util.MyViewAction
|
||||
import fr.free.nrw.commons.utils.ConfigUtils
|
||||
import org.hamcrest.core.AllOf.allOf
|
||||
import org.junit.After
|
||||
|
|
@ -65,7 +69,6 @@ class UploadTest {
|
|||
}
|
||||
UITestHelper.skipWelcome()
|
||||
UITestHelper.loginUser()
|
||||
saveToInternalStorage()
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -73,59 +76,15 @@ class UploadTest {
|
|||
Intents.release()
|
||||
}
|
||||
|
||||
private fun saveToInternalStorage() {
|
||||
val bitmapImage = randomBitmap
|
||||
|
||||
// path to /data/data/yourapp/app_data/imageDir
|
||||
val mypath = File(Environment.getExternalStorageDirectory(), "image.jpg")
|
||||
|
||||
Timber.d("Filepath: %s", mypath.path)
|
||||
|
||||
Timber.d("Absolute Filepath: %s", mypath.absolutePath)
|
||||
|
||||
var fos: FileOutputStream? = null
|
||||
try {
|
||||
fos = FileOutputStream(mypath)
|
||||
// Use the compress method on the BitMap object to write image to the OutputStream
|
||||
bitmapImage.compress(Bitmap.CompressFormat.JPEG, 100, fos)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
fos?.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uploadTest() {
|
||||
fun testUploadWithDescription() {
|
||||
if (!ConfigUtils.isBetaFlavour()) {
|
||||
throw Error("This test should only be run in Beta!")
|
||||
}
|
||||
|
||||
// Uri to return by our mock gallery selector
|
||||
// Requires file 'image.jpg' to be placed at root of file structure
|
||||
val imageUri = Uri.parse("file://mnt/sdcard/image.jpg")
|
||||
setupSingleUpload("image.jpg")
|
||||
|
||||
// Build a result to return from the Camera app
|
||||
val intent = Intent()
|
||||
intent.data = imageUri
|
||||
val result = ActivityResult(Activity.RESULT_OK, intent)
|
||||
|
||||
// Stub out the File picker. When an intent is sent to the File picker, this tells
|
||||
// Espresso to respond with the ActivityResult we just created
|
||||
intending(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*"))).respondWith(result)
|
||||
|
||||
// Open FAB
|
||||
onView(allOf<View>(withId(R.id.fab_plus), isDisplayed()))
|
||||
.perform(click())
|
||||
|
||||
// Click gallery
|
||||
onView(allOf<View>(withId(R.id.fab_gallery), isDisplayed()))
|
||||
.perform(click())
|
||||
openGallery()
|
||||
|
||||
// Validate that an intent to get an image is sent
|
||||
intended(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*")))
|
||||
|
|
@ -158,7 +117,7 @@ class UploadTest {
|
|||
UITestHelper.sleep(3000)
|
||||
|
||||
try {
|
||||
onView(allOf(isDisplayed(), withParent(withId(R.id.rv_categories))))
|
||||
onView(allOf(isDisplayed(), UITestHelper.first(withParent(withId(R.id.rv_categories)))))
|
||||
.perform(click())
|
||||
} catch (ignored: NoMatchingViewException) {
|
||||
}
|
||||
|
|
@ -188,4 +147,206 @@ class UploadTest {
|
|||
} catch (ignored: NoMatchingViewException) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUploadWithoutDescription() {
|
||||
if (!ConfigUtils.isBetaFlavour()) {
|
||||
throw Error("This test should only be run in Beta!")
|
||||
}
|
||||
|
||||
setupSingleUpload("image.jpg")
|
||||
|
||||
openGallery()
|
||||
|
||||
// Validate that an intent to get an image is sent
|
||||
intended(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*")))
|
||||
|
||||
// Create filename with the current time (to prevent overwrites)
|
||||
val dateFormat = SimpleDateFormat("yyMMdd-hhmmss")
|
||||
val commonsFileName = "MobileTest " + dateFormat.format(Date())
|
||||
|
||||
// Try to dismiss the error, if there is one (probably about duplicate files on Commons)
|
||||
dismissWarning("Yes")
|
||||
|
||||
onView(allOf<View>(isDisplayed(), withId(R.id.et_title)))
|
||||
.perform(replaceText(commonsFileName))
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.btn_next)))
|
||||
.perform(click())
|
||||
|
||||
UITestHelper.sleep(10000)
|
||||
dismissWarning("Yes")
|
||||
|
||||
UITestHelper.sleep(3000)
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.et_search)))
|
||||
.perform(replaceText("Test"))
|
||||
|
||||
UITestHelper.sleep(3000)
|
||||
|
||||
try {
|
||||
onView(allOf(isDisplayed(), UITestHelper.first(withParent(withId(R.id.rv_categories)))))
|
||||
.perform(click())
|
||||
} catch (ignored: NoMatchingViewException) {
|
||||
}
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.btn_next)))
|
||||
.perform(click())
|
||||
|
||||
dismissWarning("Yes, Submit")
|
||||
|
||||
UITestHelper.sleep(500)
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.btn_submit)))
|
||||
.perform(click())
|
||||
|
||||
UITestHelper.sleep(10000)
|
||||
|
||||
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
|
||||
commonsFileName.replace(' ', '_') + ".jpg"
|
||||
Timber.i("File should be uploaded to $fileUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUploadWithMultilingualDescription() {
|
||||
if (!ConfigUtils.isBetaFlavour()) {
|
||||
throw Error("This test should only be run in Beta!")
|
||||
}
|
||||
|
||||
setupSingleUpload("image.jpg")
|
||||
|
||||
openGallery()
|
||||
|
||||
// Validate that an intent to get an image is sent
|
||||
intended(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*")))
|
||||
|
||||
// Create filename with the current time (to prevent overwrites)
|
||||
val dateFormat = SimpleDateFormat("yyMMdd-hhmmss")
|
||||
val commonsFileName = "MobileTest " + dateFormat.format(Date())
|
||||
|
||||
// Try to dismiss the error, if there is one (probably about duplicate files on Commons)
|
||||
dismissWarningDialog()
|
||||
|
||||
onView(allOf<View>(isDisplayed(), withId(R.id.et_title)))
|
||||
.perform(replaceText(commonsFileName))
|
||||
|
||||
onView(withId(R.id.rv_descriptions)).perform(
|
||||
RecyclerViewActions
|
||||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(0,
|
||||
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description")))
|
||||
|
||||
onView(withId(R.id.btn_add_description))
|
||||
.perform(click())
|
||||
|
||||
onView(withId(R.id.rv_descriptions)).perform(
|
||||
RecyclerViewActions
|
||||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(1,
|
||||
MyViewAction.selectSpinnerItemInChildViewWithId(R.id.spinner_description_languages, 2)))
|
||||
|
||||
onView(withId(R.id.rv_descriptions)).perform(
|
||||
RecyclerViewActions
|
||||
.actionOnItemAtPosition<DescriptionsAdapter.ViewHolder>(1,
|
||||
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Description")))
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.btn_next)))
|
||||
.perform(click())
|
||||
|
||||
UITestHelper.sleep(5000)
|
||||
dismissWarning("Yes")
|
||||
|
||||
UITestHelper.sleep(3000)
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.et_search)))
|
||||
.perform(replaceText("Test"))
|
||||
|
||||
UITestHelper.sleep(3000)
|
||||
|
||||
try {
|
||||
onView(allOf(isDisplayed(), UITestHelper.first(withParent(withId(R.id.rv_categories)))))
|
||||
.perform(click())
|
||||
} catch (ignored: NoMatchingViewException) {
|
||||
}
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.btn_next)))
|
||||
.perform(click())
|
||||
|
||||
dismissWarning("Yes, Submit")
|
||||
|
||||
UITestHelper.sleep(500)
|
||||
|
||||
onView(allOf(isDisplayed(), withId(R.id.btn_submit)))
|
||||
.perform(click())
|
||||
|
||||
UITestHelper.sleep(10000)
|
||||
|
||||
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
|
||||
commonsFileName.replace(' ', '_') + ".jpg"
|
||||
Timber.i("File should be uploaded to $fileUrl")
|
||||
}
|
||||
|
||||
private fun setupSingleUpload(imageName: String) {
|
||||
saveToInternalStorage(imageName)
|
||||
singleImageIntent(imageName)
|
||||
}
|
||||
|
||||
private fun saveToInternalStorage(imageName: String) {
|
||||
val bitmapImage = randomBitmap
|
||||
|
||||
// path to /data/data/yourapp/app_data/imageDir
|
||||
val mypath = File(Environment.getExternalStorageDirectory(), imageName)
|
||||
|
||||
Timber.d("Filepath: %s", mypath.path)
|
||||
|
||||
Timber.d("Absolute Filepath: %s", mypath.absolutePath)
|
||||
|
||||
var fos: FileOutputStream? = null
|
||||
try {
|
||||
fos = FileOutputStream(mypath)
|
||||
// Use the compress method on the BitMap object to write image to the OutputStream
|
||||
bitmapImage.compress(Bitmap.CompressFormat.JPEG, 100, fos)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
fos?.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun singleImageIntent(imageName: String) {
|
||||
// Uri to return by our mock gallery selector
|
||||
// Requires file 'image.jpg' to be placed at root of file structure
|
||||
val imageUri = Uri.parse("file://mnt/sdcard/$imageName")
|
||||
|
||||
// Build a result to return from the Camera app
|
||||
val intent = Intent()
|
||||
intent.data = imageUri
|
||||
val result = ActivityResult(Activity.RESULT_OK, intent)
|
||||
|
||||
// Stub out the File picker. When an intent is sent to the File picker, this tells
|
||||
// Espresso to respond with the ActivityResult we just created
|
||||
intending(allOf(hasAction(Intent.ACTION_GET_CONTENT), hasType("image/*"))).respondWith(result)
|
||||
}
|
||||
|
||||
private fun dismissWarningDialog() {
|
||||
try {
|
||||
onView(withText("Yes"))
|
||||
.check(matches(isDisplayed()))
|
||||
.perform(click())
|
||||
} catch (ignored: NoMatchingViewException) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openGallery() {
|
||||
// Open FAB
|
||||
onView(allOf<View>(withId(R.id.fab_plus), isDisplayed()))
|
||||
.perform(click())
|
||||
|
||||
// Click gallery
|
||||
onView(allOf<View>(withId(R.id.fab_gallery), isDisplayed()))
|
||||
.perform(click())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import android.view.View
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
object ViewActions {
|
||||
fun clickChildViewWithId(id: Int): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "Click on a child view with specified id."
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<View>(id)
|
||||
v.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package fr.free.nrw.commons.util
|
||||
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.widget.AppCompatSpinner
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
class MyViewAction {
|
||||
companion object {
|
||||
fun typeTextInChildViewWithId(id: Int, textToBeTyped: String): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View>? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "Click on a child view with specified id."
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<View>(id) as EditText
|
||||
v.setText(textToBeTyped)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectSpinnerItemInChildViewWithId(id: Int, position: Int): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View>? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "Click on a child view with specified id."
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<View>(id) as AppCompatSpinner
|
||||
v.setSelection(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clickItemWithId(id: Int, position: Int): ViewAction {
|
||||
return object : ViewAction {
|
||||
override fun getConstraints(): Matcher<View>? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "Click on a child view with specified id."
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
val v = view.findViewById<View>(id) as View
|
||||
v.performClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ public class AboutActivity extends NavigationBaseActivity {
|
|||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.share_app_icon:
|
||||
String shareText = String.format(getString(R.string.share_text), Urls.PLAY_STORE_URL);
|
||||
String shareText = String.format(getString(R.string.share_text), Urls.PLAY_STORE_URL_PREFIX + this.getPackageName());
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE;
|
||||
import static org.acra.ReportField.ANDROID_VERSION;
|
||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||
import static org.acra.ReportField.APP_VERSION_NAME;
|
||||
import static org.acra.ReportField.PHONE_MODEL;
|
||||
import static org.acra.ReportField.STACK_TRACE;
|
||||
import static org.acra.ReportField.USER_COMMENT;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.app.NotificationChannel;
|
||||
|
|
@ -9,32 +17,13 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||
import com.facebook.imagepipeline.core.ImagePipelineConfig;
|
||||
import com.facebook.imagepipeline.producers.NetworkFetcher;
|
||||
import com.mapbox.mapboxsdk.Mapbox;
|
||||
import com.squareup.leakcanary.LeakCanary;
|
||||
import com.squareup.leakcanary.RefWatcher;
|
||||
|
||||
import org.acra.ACRA;
|
||||
import org.acra.annotation.AcraCore;
|
||||
import org.acra.annotation.AcraDialog;
|
||||
import org.acra.annotation.AcraMailSender;
|
||||
import org.acra.data.StringFormat;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.language.AppLanguageLookUpTable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
||||
|
|
@ -54,17 +43,20 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.internal.functions.Functions;
|
||||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.OkHttpClient;
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.acra.ACRA;
|
||||
import org.acra.annotation.AcraCore;
|
||||
import org.acra.annotation.AcraDialog;
|
||||
import org.acra.annotation.AcraMailSender;
|
||||
import org.acra.data.StringFormat;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.language.AppLanguageLookUpTable;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE;
|
||||
import static org.acra.ReportField.ANDROID_VERSION;
|
||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||
import static org.acra.ReportField.APP_VERSION_NAME;
|
||||
import static org.acra.ReportField.PHONE_MODEL;
|
||||
import static org.acra.ReportField.STACK_TRACE;
|
||||
import static org.acra.ReportField.USER_COMMENT;
|
||||
|
||||
@AcraCore(
|
||||
buildConfigClass = BuildConfig.class,
|
||||
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
||||
|
|
@ -90,9 +82,6 @@ public class CommonsApplication extends Application {
|
|||
|
||||
@Inject @Named("default_preferences") JsonKvStore defaultPrefs;
|
||||
|
||||
@Inject
|
||||
OkHttpClient okHttpClient;
|
||||
|
||||
/**
|
||||
* Constants begin
|
||||
*/
|
||||
|
|
@ -157,15 +146,9 @@ public class CommonsApplication extends Application {
|
|||
}
|
||||
|
||||
// Set DownsampleEnabled to True to downsample the image in case it's heavy
|
||||
ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(this)
|
||||
.setDownsampleEnabled(true);
|
||||
|
||||
if(ConfigUtils.isBetaFlavour()){
|
||||
NetworkFetcher networkFetcher=new CustomNetworkFetcher(okHttpClient);
|
||||
imagePipelineConfigBuilder.setNetworkFetcher(networkFetcher);
|
||||
}
|
||||
|
||||
ImagePipelineConfig config = imagePipelineConfigBuilder.build();
|
||||
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
|
||||
.setDownsampleEnabled(true)
|
||||
.build();
|
||||
try {
|
||||
Fresco.initialize(this, config);
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import com.facebook.imagepipeline.common.BytesRange;
|
||||
import com.facebook.imagepipeline.image.EncodedImage;
|
||||
import com.facebook.imagepipeline.producers.BaseNetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks;
|
||||
import com.facebook.imagepipeline.producers.Consumer;
|
||||
import com.facebook.imagepipeline.producers.FetchState;
|
||||
import com.facebook.imagepipeline.producers.NetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.ProducerContext;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/** Network fetcher that uses OkHttp 3 as a backend. */
|
||||
public class CustomNetworkFetcher
|
||||
extends BaseNetworkFetcher<CustomNetworkFetcher.OkHttpNetworkFetchState> {
|
||||
|
||||
public static class OkHttpNetworkFetchState extends FetchState {
|
||||
|
||||
public long submitTime;
|
||||
public long responseTime;
|
||||
public long fetchCompleteTime;
|
||||
|
||||
public OkHttpNetworkFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext producerContext) {
|
||||
super(consumer, producerContext);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String QUEUE_TIME = "queue_time";
|
||||
private static final String FETCH_TIME = "fetch_time";
|
||||
private static final String TOTAL_TIME = "total_time";
|
||||
private static final String IMAGE_SIZE = "image_size";
|
||||
|
||||
private final Call.Factory mCallFactory;
|
||||
private final CacheControl mCacheControl;
|
||||
|
||||
private Executor mCancellationExecutor;
|
||||
|
||||
/** @param okHttpClient client to use */
|
||||
public CustomNetworkFetcher(OkHttpClient okHttpClient) {
|
||||
this(okHttpClient, okHttpClient.dispatcher().executorService());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
||||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||
* cancellation is requested from the UI Thread
|
||||
*/
|
||||
public CustomNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor) {
|
||||
this(callFactory, cancellationExecutor, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
||||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||
* cancellation is requested from the UI Thread
|
||||
* @param disableOkHttpCache true if network requests should not be cached by OkHttp
|
||||
*/
|
||||
public CustomNetworkFetcher(
|
||||
Call.Factory callFactory, Executor cancellationExecutor, boolean disableOkHttpCache) {
|
||||
mCallFactory = callFactory;
|
||||
mCancellationExecutor = cancellationExecutor;
|
||||
mCacheControl = disableOkHttpCache ? new CacheControl.Builder().noStore().build() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpNetworkFetchState createFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext context) {
|
||||
return new OkHttpNetworkFetchState(consumer, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetch(
|
||||
final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
|
||||
fetchState.submitTime = SystemClock.elapsedRealtime();
|
||||
final Uri uri = fetchState.getUri();
|
||||
|
||||
try {
|
||||
final Request.Builder requestBuilder = new Request.Builder().url(uri.toString()).get();
|
||||
|
||||
if (mCacheControl != null) {
|
||||
requestBuilder.cacheControl(mCacheControl);
|
||||
}
|
||||
|
||||
final BytesRange bytesRange = fetchState.getContext().getImageRequest().getBytesRange();
|
||||
if (bytesRange != null) {
|
||||
requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue());
|
||||
}
|
||||
|
||||
fetchWithRequest(fetchState, callback, requestBuilder.build());
|
||||
} catch (Exception e) {
|
||||
// handle error while creating the request
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
Map<String, String> extraMap = new HashMap<>(4);
|
||||
extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
|
||||
extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
|
||||
extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
|
||||
extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
|
||||
return extraMap;
|
||||
}
|
||||
|
||||
protected void fetchWithRequest(
|
||||
final OkHttpNetworkFetchState fetchState,
|
||||
final NetworkFetcher.Callback callback,
|
||||
final Request request) {
|
||||
final Call call = mCallFactory.newCall(request);
|
||||
|
||||
fetchState
|
||||
.getContext()
|
||||
.addCallbacks(
|
||||
new BaseProducerContextCallbacks() {
|
||||
@Override
|
||||
public void onCancellationRequested() {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
call.cancel();
|
||||
} else {
|
||||
mCancellationExecutor.execute(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
call.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
call.enqueue(
|
||||
new okhttp3.Callback() {
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
fetchState.responseTime = SystemClock.elapsedRealtime();
|
||||
final ResponseBody body = response.body();
|
||||
try {
|
||||
if (!response.isSuccessful()) {
|
||||
handleException(
|
||||
call, new IOException("Unexpected HTTP code " + response), callback);
|
||||
return;
|
||||
}
|
||||
|
||||
BytesRange responseRange =
|
||||
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
|
||||
if (responseRange != null
|
||||
&& !(responseRange.from == 0
|
||||
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
|
||||
// Only treat as a partial image if the range is not all of the content
|
||||
fetchState.setResponseBytesRange(responseRange);
|
||||
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
|
||||
}
|
||||
|
||||
long contentLength = body.contentLength();
|
||||
if (contentLength < 0) {
|
||||
contentLength = 0;
|
||||
}
|
||||
callback.onResponse(body.byteStream(), (int) contentLength);
|
||||
} catch (Exception e) {
|
||||
handleException(call, e, callback);
|
||||
} finally {
|
||||
body.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
handleException(call, e, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles exceptions.
|
||||
*
|
||||
* <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
|
||||
* request cancellation, then the exception is interpreted as successful cancellation and
|
||||
* onCancellation is called. Otherwise onFailure is called.
|
||||
*/
|
||||
private void handleException(final Call call, final Exception e, final Callback callback) {
|
||||
if (call.isCanceled()) {
|
||||
callback.onCancellation();
|
||||
} else {
|
||||
callback.onFailure(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ public class Media implements Parcelable {
|
|||
};
|
||||
|
||||
// Primary metadata fields
|
||||
@Nullable
|
||||
public Uri localUri;
|
||||
public String thumbUrl;
|
||||
public String imageUrl;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import org.wikipedia.dataclient.okhttp.HttpStatusException;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import fr.free.nrw.commons.di.SslUtils;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
|
@ -31,17 +29,13 @@ public final class OkHttpConnectionFactory {
|
|||
|
||||
@NonNull
|
||||
private static OkHttpClient createClient() {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
return new OkHttpClient.Builder()
|
||||
.cookieJar(SharedPreferenceCookieManager.getInstance())
|
||||
.cache(NET_CACHE)
|
||||
.addInterceptor(getLoggingInterceptor())
|
||||
.addInterceptor(new UnsuccessfulResponseInterceptor())
|
||||
.addInterceptor(new CommonHeaderRequestInterceptor());
|
||||
|
||||
if(ConfigUtils.isBetaFlavour()){
|
||||
builder.sslSocketFactory(SslUtils.INSTANCE.getTrustAllHostsSSLSocketFactory());
|
||||
}
|
||||
return builder.build();
|
||||
.addInterceptor(new CommonHeaderRequestInterceptor())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static HttpLoggingInterceptor getLoggingInterceptor() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ internal object Urls {
|
|||
const val WEBSITE_URL = "https://commons-app.github.io"
|
||||
const val CREDITS_URL = "https://github.com/commons-app/apps-android-commons/blob/master/CREDITS"
|
||||
const val FAQ_URL = "https://github.com/commons-app/apps-android-commons/wiki/Frequently-Asked-Questions"
|
||||
const val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=fr.free.nrw.commons"
|
||||
const val PLAY_STORE_PREFIX = "market://details?id="
|
||||
const val PLAY_STORE_URL_PREFIX = "https://play.google.com/store/apps/details?id="
|
||||
const val TRANSLATE_WIKI_URL = "https://translatewiki.net/w/i.php?title=Special:Translate&group=commons-android-strings&filter=%21translated&action=translate&language="
|
||||
const val FACEBOOK_WEB_URL = "https://www.facebook.com/1921335171459985"
|
||||
const val FACEBOOK_APP_URL = "fb://page/1921335171459985"
|
||||
|
|
|
|||
|
|
@ -115,12 +115,12 @@ public class Utils {
|
|||
* @param context
|
||||
*/
|
||||
public static void rateApp(Context context) {
|
||||
final String appPackageName = BuildConfig.class.getPackage().getName();
|
||||
final String appPackageName = context.getPackageName();
|
||||
try {
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName)));
|
||||
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.PLAY_STORE_PREFIX + appPackageName)));
|
||||
}
|
||||
catch (android.content.ActivityNotFoundException anfe) {
|
||||
handleWebUrl(context, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName));
|
||||
handleWebUrl(context, Uri.parse(Urls.PLAY_STORE_URL_PREFIX + appPackageName));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -145,13 +145,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@OnFocusChange(R.id.login_username)
|
||||
void onUsernameFocusChanged(View view, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
ViewUtil.hideKeyboard(view);
|
||||
}
|
||||
}
|
||||
|
||||
@OnFocusChange(R.id.login_password)
|
||||
void onPasswordFocusChanged(View view, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
|
|
|
|||
|
|
@ -111,10 +111,8 @@ public class GridViewAdapter extends ArrayAdapter {
|
|||
*/
|
||||
private void setAuthorView(Media item, TextView author) {
|
||||
if (!TextUtils.isEmpty(item.getCreator())) {
|
||||
String uploadedByTemplate = getContext().getString(R.string.image_uploaded_by);
|
||||
|
||||
String uploadedBy = String.format(Locale.getDefault(), uploadedByTemplate, item.getCreator());
|
||||
author.setText(uploadedBy);
|
||||
author.setVisibility(View.VISIBLE);
|
||||
author.setText(getContext().getString(R.string.image_uploaded_by, item.getCreator()));
|
||||
} else {
|
||||
author.setVisibility(View.GONE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ public class Contribution extends Media {
|
|||
public String decimalCoords;
|
||||
public boolean isMultiple;
|
||||
public String wikiDataEntityId;
|
||||
private String p18Value;
|
||||
public Uri contentProviderUri;
|
||||
public String dateCreatedSource;
|
||||
|
||||
|
|
@ -287,6 +288,19 @@ public class Contribution extends Media {
|
|||
this.wikiDataEntityId = wikiDataEntityId;
|
||||
}
|
||||
|
||||
public String getP18Value() {
|
||||
return p18Value;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the corresponding image property of wiki entity is known as in case of nearby uploads,
|
||||
* it can be set using the setter method
|
||||
* @param p18Value p18 value, image property of the wikidata item
|
||||
*/
|
||||
public void setP18Value(String p18Value) {
|
||||
this.p18Value = p18Value;
|
||||
}
|
||||
|
||||
public void setContentProviderUri(Uri contentProviderUri) {
|
||||
this.contentProviderUri = contentProviderUri;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import androidx.room.OnConflictStrategy;
|
|||
import androidx.room.Query;
|
||||
import androidx.room.Transaction;
|
||||
|
||||
import androidx.room.Update;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
|
|
@ -52,4 +54,7 @@ public abstract class ContributionDao {
|
|||
|
||||
@Query("Delete FROM contribution WHERE state = :state")
|
||||
public abstract void deleteAll(int state);
|
||||
|
||||
@Update
|
||||
public abstract Single<Integer> update(Contribution contribution);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,36 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.collection.LruCache;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
|
||||
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private static final long TIMEOUT_SECONDS = 15;
|
||||
private static final String NO_CAPTION = "No caption";
|
||||
private final Callback callback;
|
||||
@BindView(R.id.contributionImage)
|
||||
SimpleDraweeView imageView;
|
||||
|
|
@ -44,21 +40,13 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
@BindView(R.id.contributionProgress) ProgressBar progressView;
|
||||
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
|
||||
|
||||
@Inject
|
||||
MediaDataExtractor mediaDataExtractor;
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
|
||||
|
||||
@Inject
|
||||
@Named("thumbnail-cache")
|
||||
LruCache<String, String> thumbnailCache;
|
||||
|
||||
private DisplayableContribution contribution;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private int position;
|
||||
private static int TIMEOUT_SECONDS = 15;
|
||||
private static final String NO_CAPTION = "No caption";
|
||||
private Contribution contribution;
|
||||
private Random random = new Random();
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
ContributionViewHolder(View parent, Callback callback) {
|
||||
super(parent);
|
||||
|
|
@ -66,16 +54,22 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
this.callback=callback;
|
||||
}
|
||||
|
||||
public void init(int position, DisplayableContribution contribution) {
|
||||
ApplicationlessInjection.getInstance(itemView.getContext())
|
||||
.getCommonsApplicationComponent().inject(this);
|
||||
this.position=position;
|
||||
public void init(int position, Contribution contribution) {
|
||||
this.contribution = contribution;
|
||||
fetchAndDisplayThumbnail(contribution);
|
||||
fetchAndDisplayCaption(contribution);
|
||||
titleView.setText(contribution.getDisplayTitle());
|
||||
this.position = position;
|
||||
imageView.getHierarchy().setPlaceholderImage(new ColorDrawable(
|
||||
Color.argb(100, random.nextInt(256), random.nextInt(256), random.nextInt(256))));
|
||||
String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri());
|
||||
if (!TextUtils.isEmpty(imageSource)) {
|
||||
final ImageRequest imageRequest =
|
||||
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
||||
.setProgressiveRenderingEnabled(true)
|
||||
.build();
|
||||
imageView.setImageRequest(imageRequest);
|
||||
}
|
||||
|
||||
seqNumView.setText(String.valueOf(contribution.getPosition() + 1));
|
||||
seqNumView.setText(String.valueOf(position + 1));
|
||||
seqNumView.setVisibility(View.VISIBLE);
|
||||
|
||||
switch (contribution.getState()) {
|
||||
|
|
@ -118,7 +112,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
*
|
||||
* @param contribution
|
||||
*/
|
||||
private void fetchAndDisplayCaption(DisplayableContribution contribution) {
|
||||
private void fetchAndDisplayCaption(Contribution contribution) {
|
||||
if ((contribution.getState() != Contribution.STATE_COMPLETED)) {
|
||||
titleView.setText(contribution.getDisplayTitle());
|
||||
} else {
|
||||
|
|
@ -137,40 +131,18 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method fetches the thumbnail url from file name
|
||||
* If the thumbnail url is present in cache, then it is used otherwise API call is made to fetch the thumbnail
|
||||
* This can be removed once #2904 is in place and contribution contains all metadata beforehand
|
||||
* @param contribution
|
||||
* Returns the image source for the image view, first preference is given to thumbUrl if that is
|
||||
* null, moves to local uri and if both are null return null
|
||||
*
|
||||
* @param thumbUrl
|
||||
* @param localUri
|
||||
* @return
|
||||
*/
|
||||
private void fetchAndDisplayThumbnail(DisplayableContribution contribution) {
|
||||
String keyForLRUCache = contribution.getFilename();
|
||||
String cacheUrl = thumbnailCache.get(keyForLRUCache);
|
||||
if (!StringUtils.isBlank(cacheUrl)) {
|
||||
imageView.setImageURI(cacheUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
imageView.setBackground(null);
|
||||
if ((contribution.getState() != Contribution.STATE_COMPLETED) && FileUtils.fileExists(
|
||||
contribution.getLocalUri())) {
|
||||
imageView.setImageURI(contribution.getLocalUri());
|
||||
} else {
|
||||
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
|
||||
Disposable disposable = mediaDataExtractor
|
||||
.getMediaFromFileName(contribution.getFilename())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
thumbnailCache.put(keyForLRUCache, media.getThumbUrl());
|
||||
imageView.setImageURI(media.getThumbUrl());
|
||||
});
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
compositeDisposable.clear();
|
||||
@Nullable
|
||||
private String chooseImageSource(String thumbUrl, Uri localUri) {
|
||||
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
|
||||
localUri != null ? localUri.toString() :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -26,10 +26,15 @@ public class ContributionsContract {
|
|||
}
|
||||
|
||||
public interface UserActionListener extends BasePresenter<ContributionsContract.View> {
|
||||
|
||||
Contribution getContributionsWithTitle(String uri);
|
||||
|
||||
void deleteUpload(Contribution contribution);
|
||||
|
||||
Media getItemAtPosition(int i);
|
||||
|
||||
void updateContribution(Contribution contribution);
|
||||
|
||||
void fetchMediaDetails(Contribution contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import androidx.fragment.app.FragmentManager;
|
|||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -219,6 +221,12 @@ public class ContributionsFragment
|
|||
public Contribution getContributionForPosition(int position) {
|
||||
return (Contribution) contributionsPresenter.getItemAtPosition(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchMediaUriFor(Contribution contribution) {
|
||||
Timber.d("Fetching thumbnail for %s", contribution.filename);
|
||||
contributionsPresenter.fetchMediaDetails(contribution);
|
||||
}
|
||||
});
|
||||
|
||||
if(null==mediaDetailPagerFragment){
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
|
|
@ -10,7 +13,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
|
||||
|
||||
/**
|
||||
* Represents The View Adapter for the List of Contributions
|
||||
|
|
@ -22,7 +24,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
|||
|
||||
public ContributionsListAdapter(Callback callback) {
|
||||
this.callback = callback;
|
||||
contributions=new ArrayList<>();
|
||||
contributions = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,9 +43,12 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
|||
@Override
|
||||
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
|
||||
final Contribution contribution = contributions.get(position);
|
||||
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
|
||||
position);
|
||||
holder.init(position, displayableContribution);
|
||||
if (TextUtils.isEmpty(contribution.getThumbUrl())
|
||||
&& contribution.getState() == Contribution.STATE_COMPLETED) {
|
||||
callback.fetchMediaUriFor(contribution);
|
||||
}
|
||||
|
||||
holder.init(position, contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -51,12 +56,14 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
|||
return contributions.size();
|
||||
}
|
||||
|
||||
public void setContributions(List<Contribution> contributionList) {
|
||||
if(null!=contributionList) {
|
||||
this.contributions.clear();
|
||||
this.contributions.addAll(contributionList);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
public void setContributions(@NonNull List<Contribution> contributionList) {
|
||||
contributions = contributionList;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return contributions.get(position)._id;
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
|
@ -68,5 +75,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
|
|||
void openMediaDetail(int contribution);
|
||||
|
||||
Contribution getContributionForPosition(int position);
|
||||
|
||||
void fetchMediaUriFor(Contribution contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
private void initAdapter() {
|
||||
adapter = new ContributionsListAdapter(callback);
|
||||
adapter.setHasStableIds(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -74,4 +74,8 @@ class ContributionsLocalDataSource {
|
|||
public void set(String key, long value) {
|
||||
defaultKVStore.putLong(key,value);
|
||||
}
|
||||
|
||||
public Single<Integer> updateContribution(Contribution contribution) {
|
||||
return contributionDao.update(contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.lifecycle.LifecycleOwner;
|
|||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -63,6 +64,10 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
|
||||
@Inject
|
||||
MediaDataExtractor mediaDataExtractor;
|
||||
|
||||
private LifecycleOwner lifeCycleOwner;
|
||||
|
||||
@Inject
|
||||
|
|
@ -181,4 +186,24 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
}
|
||||
return contributionList.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateContribution(Contribution contribution) {
|
||||
compositeDisposable.add(repository
|
||||
.updateContribution(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fetchMediaDetails(Contribution contribution) {
|
||||
compositeDisposable.add(mediaDataExtractor
|
||||
.getMediaFromFileName(contribution.filename)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
contribution.thumbUrl=media.thumbUrl;
|
||||
updateContribution(contribution);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,4 +61,8 @@ public class ContributionsRepository {
|
|||
public long getLong(String key) {
|
||||
return localDataSource.getLong(key);
|
||||
}
|
||||
|
||||
public Single<Integer> updateContribution(Contribution contribution) {
|
||||
return localDataSource.updateContribution(contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
package fr.free.nrw.commons.contributions.model;
|
||||
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
|
||||
public class DisplayableContribution extends Contribution {
|
||||
private int position;
|
||||
public DisplayableContribution(Contribution contribution,
|
||||
int position) {
|
||||
super(contribution.getContentUri(),
|
||||
contribution.getFilename(),
|
||||
contribution.getLocalUri(),
|
||||
contribution.getImageUrl(),
|
||||
contribution.getDateCreated(),
|
||||
contribution.getState(),
|
||||
contribution.getDataLength(),
|
||||
contribution.getDateUploaded(),
|
||||
contribution.getTransferred(),
|
||||
contribution.getSource(),
|
||||
contribution.getCaptions(),
|
||||
contribution.getDescription(),
|
||||
contribution.getCreator(),
|
||||
contribution.getMultiple(),
|
||||
contribution.getWidth(),
|
||||
contribution.getHeight(),
|
||||
contribution.getLicense());
|
||||
this._id=contribution._id;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
|
@ -98,6 +98,12 @@ public class DeleteHelper {
|
|||
String userPageString = "\n{{subst:idw|" + media.getFilename() +
|
||||
"}} ~~~~";
|
||||
|
||||
String creator = media.getCreator();
|
||||
if (creator == null || creator.isEmpty()) {
|
||||
throw new RuntimeException("Failed to nominate for deletion");
|
||||
}
|
||||
String creatorName = creator.replace(" (page does not exist)", "");
|
||||
|
||||
return pageEditClient.prependEdit(media.filename, fileDeleteString + "\n", summary)
|
||||
.flatMap(result -> {
|
||||
if (result) {
|
||||
|
|
@ -111,7 +117,7 @@ public class DeleteHelper {
|
|||
throw new RuntimeException("Failed to nominate for deletion");
|
||||
}).flatMap(result -> {
|
||||
if (result) {
|
||||
return pageEditClient.appendEdit("User_Talk:" + username, userPageString + "\n", summary);
|
||||
return pageEditClient.appendEdit("User_Talk:" + creatorName, userPageString + "\n", summary);
|
||||
}
|
||||
throw new RuntimeException("Failed to nominate for deletion");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,8 +55,6 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
|
|||
|
||||
void inject(PicOfDayAppWidget picOfDayAppWidget);
|
||||
|
||||
void inject(ContributionViewHolder viewHolder);
|
||||
|
||||
Gson gson();
|
||||
|
||||
@Component.Builder
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
|
@ -34,7 +32,6 @@ import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
|||
import fr.free.nrw.commons.mwapi.UserInterface;
|
||||
import fr.free.nrw.commons.review.ReviewInterface;
|
||||
import fr.free.nrw.commons.upload.UploadInterface;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.wikidata.WikidataInterface;
|
||||
import fr.free.nrw.commons.upload.WikiBaseInterface;
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
|
||||
|
|
@ -66,20 +63,14 @@ public class NetworkingModule {
|
|||
public OkHttpClient provideOkHttpClient(Context context,
|
||||
HttpLoggingInterceptor httpLoggingInterceptor) {
|
||||
File dir = new File(context.getCacheDir(), "okHttpCache");
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
return new OkHttpClient.Builder().connectTimeout(60, TimeUnit.SECONDS)
|
||||
.writeTimeout(60, TimeUnit.SECONDS)
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.cache(new Cache(dir, OK_HTTP_CACHE_SIZE));
|
||||
|
||||
if(ConfigUtils.isBetaFlavour()){
|
||||
builder.sslSocketFactory(SslUtils.INSTANCE.getTrustAllHostsSSLSocketFactory());
|
||||
}
|
||||
return builder.build();
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
.cache(new Cache(dir, OK_HTTP_CACHE_SIZE))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public HttpLoggingInterceptor provideHttpLoggingInterceptor() {
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
package fr.free.nrw.commons.di
|
||||
|
||||
import java.security.KeyManagementException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
object SslUtils {
|
||||
|
||||
fun getTrustAllHostsSSLSocketFactory(): SSLSocketFactory? {
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
return arrayOf()
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
})
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
||||
// Create an ssl socket factory with our all-trusting manager
|
||||
|
||||
return sslContext.socketFactory
|
||||
} catch (e: KeyManagementException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
public class FilePickerConfiguration implements Constants {
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import android.location.Location;
|
|||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
|
@ -214,6 +213,14 @@ public class LocationServiceManager implements LocationListener {
|
|||
Timber.d("Provider %s disabled", provider);
|
||||
}
|
||||
|
||||
public boolean isNetworkProviderEnabled() {
|
||||
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
|
||||
}
|
||||
|
||||
public boolean isGPSProviderEnabled() {
|
||||
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
|
||||
}
|
||||
|
||||
public enum LocationChangeType{
|
||||
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
|
||||
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
|
||||
|
|
|
|||
|
|
@ -397,69 +397,75 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@OnClick(R.id.nominateDeletion)
|
||||
public void onDeleteButtonClicked(){
|
||||
if(AccountUtil.getUserName(getContext()).equals(media.getCreator())){
|
||||
final ArrayAdapter<String> languageAdapter = new ArrayAdapter<>(getActivity(),
|
||||
R.layout.simple_spinner_dropdown_list, reasonList);
|
||||
final Spinner spinner = new Spinner(getActivity());
|
||||
spinner.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
spinner.setAdapter(languageAdapter);
|
||||
spinner.setGravity(17);
|
||||
if (AccountUtil.getUserName(getContext()) != null && AccountUtil.getUserName(getContext()).equals(media.getCreator())) {
|
||||
final ArrayAdapter<String> languageAdapter = new ArrayAdapter<>(getActivity(),
|
||||
R.layout.simple_spinner_dropdown_list, reasonList);
|
||||
final Spinner spinner = new Spinner(getActivity());
|
||||
spinner.setLayoutParams(
|
||||
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
spinner.setAdapter(languageAdapter);
|
||||
spinner.setGravity(17);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(spinner);
|
||||
builder.setTitle(R.string.nominate_delete)
|
||||
.setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> onDeleteClicked(spinner));
|
||||
builder.setNegativeButton(R.string.about_translate_cancel, (dialog, which) -> dialog.dismiss());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
if(isDeleted) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
}
|
||||
//Reviewer correct me if i have misunderstood something over here
|
||||
//But how does this if (delete.getVisibility() == View.VISIBLE) {
|
||||
// enableDeleteButton(true); makes sense ?
|
||||
else{
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setMessage(getString(R.string.dialog_box_text_nomination,media.getDisplayTitle()));
|
||||
final EditText input = new EditText(getActivity());
|
||||
alert.setView(input);
|
||||
input.requestFocus();
|
||||
alert.setPositiveButton(R.string.ok, (dialog1, whichButton) -> {
|
||||
String reason = input.getText().toString();
|
||||
onDeleteClickeddialogtext(reason);
|
||||
});
|
||||
alert.setNegativeButton(R.string.cancel, (dialog12, whichButton) -> {
|
||||
});
|
||||
AlertDialog d = alert.create();
|
||||
input.addTextChangedListener(new TextWatcher() {
|
||||
private void handleText() {
|
||||
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
if (input.getText().length() == 0) {
|
||||
okButton.setEnabled(false);
|
||||
} else {
|
||||
okButton.setEnabled(true);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(spinner);
|
||||
builder.setTitle(R.string.nominate_delete)
|
||||
.setPositiveButton(R.string.about_translate_proceed,
|
||||
(dialog, which) -> onDeleteClicked(spinner));
|
||||
builder.setNegativeButton(R.string.about_translate_cancel,
|
||||
(dialog, which) -> dialog.dismiss());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
if (isDeleted) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
}
|
||||
//Reviewer correct me if i have misunderstood something over here
|
||||
//But how does this if (delete.getVisibility() == View.VISIBLE) {
|
||||
// enableDeleteButton(true); makes sense ?
|
||||
else {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||
alert.setMessage(
|
||||
getString(R.string.dialog_box_text_nomination, media.getDisplayTitle()));
|
||||
final EditText input = new EditText(getActivity());
|
||||
alert.setView(input);
|
||||
input.requestFocus();
|
||||
alert.setPositiveButton(R.string.ok, (dialog1, whichButton) -> {
|
||||
String reason = input.getText().toString();
|
||||
onDeleteClickeddialogtext(reason);
|
||||
});
|
||||
alert.setNegativeButton(R.string.cancel, (dialog12, whichButton) -> {
|
||||
});
|
||||
AlertDialog d = alert.create();
|
||||
input.addTextChangedListener(new TextWatcher() {
|
||||
private void handleText() {
|
||||
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
if (input.getText().length() == 0) {
|
||||
okButton.setEnabled(false);
|
||||
} else {
|
||||
okButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable arg0) {
|
||||
handleText();
|
||||
}
|
||||
@Override
|
||||
public void afterTextChanged(Editable arg0) {
|
||||
handleText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void onDeleteClicked(Spinner spinner) {
|
||||
String reason = spinner.getSelectedItem().toString();
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
|
|||
@NonNull
|
||||
@Override
|
||||
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = inflater.inflate(callback.isDarkTheme()?R.layout.nearby_search_list_item_dark:R.layout.nearby_search_list_item, parent, false);
|
||||
View itemView = inflater.inflate(callback.isDarkTheme() ? R.layout.nearby_search_list_item_dark : R.layout.nearby_search_list_item, parent, false);
|
||||
return new RecyclerViewHolder(itemView);
|
||||
}
|
||||
|
||||
|
|
@ -79,17 +79,20 @@ public class NearbyFilterSearchRecyclerViewAdapter
|
|||
Label label = displayedLabels.get(position);
|
||||
holder.placeTypeIcon.setImageResource(label.getIcon());
|
||||
holder.placeTypeLabel.setText(label.toString());
|
||||
holder.placeTypeLayout.setSelected(label.isSelected());
|
||||
|
||||
holder.placeTypeLayout.setBackgroundColor(label.isSelected() ? ContextCompat.getColor(context, R.color.divider_grey) : callback.isDarkTheme()?Color.BLACK:Color.WHITE);
|
||||
holder.placeTypeLayout.setOnClickListener(view -> {
|
||||
callback.setCheckboxUnknown();
|
||||
|
||||
if (label.isSelected()) {
|
||||
selectedLabels.remove(label);
|
||||
} else {
|
||||
selectedLabels.add(label);
|
||||
}
|
||||
|
||||
label.setSelected(!label.isSelected());
|
||||
holder.placeTypeLayout.setBackgroundColor(label.isSelected() ? ContextCompat.getColor(context, R.color.divider_grey) : Color.WHITE);
|
||||
holder.placeTypeLayout.setSelected(label.isSelected());
|
||||
|
||||
callback.filterByMarkerType(selectedLabels, 0, false, false);
|
||||
});
|
||||
}
|
||||
|
|
@ -165,7 +168,7 @@ public class NearbyFilterSearchRecyclerViewAdapter
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public interface Callback{
|
||||
public interface Callback {
|
||||
|
||||
void setCheckboxUnknown();
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public interface NearbyParentFragmentContract {
|
|||
void setFABRecenterAction(android.view.View.OnClickListener onClickListener);
|
||||
void animateFABs();
|
||||
void recenterMap(LatLng curLatLng);
|
||||
void showLocationOffDialog();
|
||||
void openLocationSettings();
|
||||
void hideBottomSheet();
|
||||
void hideBottomDetailsSheet();
|
||||
void displayBottomSheetWithInfo(Marker marker);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -52,8 +54,11 @@ import com.mapbox.mapboxsdk.maps.MapView;
|
|||
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||
import com.mapbox.mapboxsdk.maps.Style;
|
||||
import com.mapbox.mapboxsdk.maps.UiSettings;
|
||||
import com.mapbox.pluginscalebar.ScaleBarOptions;
|
||||
import com.mapbox.pluginscalebar.ScaleBarPlugin;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
|
@ -229,8 +234,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
UiSettings uiSettings = mapBoxMap.getUiSettings();
|
||||
uiSettings.setCompassGravity(Gravity.BOTTOM | Gravity.LEFT);
|
||||
uiSettings.setCompassMargins(12, 0, 0, 24);
|
||||
uiSettings.setLogoEnabled(false);
|
||||
uiSettings.setAttributionEnabled(false);
|
||||
uiSettings.setLogoEnabled(true);
|
||||
uiSettings.setAttributionEnabled(true);
|
||||
uiSettings.setRotateGesturesEnabled(false);
|
||||
NearbyParentFragment.this.isMapBoxReady=true;
|
||||
performMapReadyActions();
|
||||
|
|
@ -239,8 +244,19 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
.zoom(ZOOM_LEVEL)
|
||||
.build();
|
||||
mapBoxMap.setCameraPosition(cameraPosition);
|
||||
});
|
||||
|
||||
ScaleBarPlugin scaleBarPlugin = new ScaleBarPlugin(mapView, mapBoxMap);
|
||||
int color = isDarkTheme ? R.color.bottom_bar_light : R.color.bottom_bar_dark;
|
||||
ScaleBarOptions scaleBarOptions = new ScaleBarOptions(getContext())
|
||||
.setTextColor(color)
|
||||
.setTextSize(R.dimen.description_text_size)
|
||||
.setBarHeight(R.dimen.tiny_gap)
|
||||
.setBorderWidth(R.dimen.miniscule_margin)
|
||||
.setMarginTop(R.dimen.tiny_padding)
|
||||
.setMarginLeft(R.dimen.tiny_padding)
|
||||
.setTextBarMargin(R.dimen.tiny_padding);
|
||||
scaleBarPlugin.create(scaleBarOptions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1238,6 +1254,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
@Override
|
||||
public void recenterMap(fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||
if (curLatLng == null) {
|
||||
if (!(locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled())) {
|
||||
showLocationOffDialog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
addCurrentLocationMarker(curLatLng);
|
||||
|
|
@ -1268,6 +1287,28 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
mapBox.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLocationOffDialog() {
|
||||
// This creates a dialog box that prompts the user to enable location
|
||||
DialogUtil
|
||||
.showAlertDialog(getActivity(), getString(R.string.ask_to_turn_location_on), getString(R.string.nearby_needs_location),
|
||||
getString(R.string.yes), getString(R.string.no), this::openLocationSettings, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openLocationSettings() {
|
||||
// This method opens the location settings of the device along with a followup toast.
|
||||
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
PackageManager packageManager = getActivity().getPackageManager();
|
||||
|
||||
if (intent.resolveActivity(packageManager)!= null) {
|
||||
startActivity(intent);
|
||||
Toast.makeText(getContext(), R.string.recommend_high_accuracy_mode, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.cannot_open_location_settings, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideBottomSheet() {
|
||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
|
|
|
|||
|
|
@ -182,11 +182,10 @@ public class UploadRemoteDataSource {
|
|||
* ask the UplaodModel for the image quality of the UploadItem
|
||||
*
|
||||
* @param uploadItem
|
||||
* @param shouldValidateTitle
|
||||
* @return
|
||||
*/
|
||||
public Single<Integer> getImageQuality(UploadItem uploadItem, boolean shouldValidateTitle) {
|
||||
return uploadModel.getImageQuality(uploadItem, shouldValidateTitle);
|
||||
public Single<Integer> getImageQuality(UploadItem uploadItem) {
|
||||
return uploadModel.getImageQuality(uploadItem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -193,11 +193,10 @@ public class UploadRepository {
|
|||
* query the RemoteDataSource for image quality
|
||||
*
|
||||
* @param uploadItem
|
||||
* @param shouldValidateTitle
|
||||
* @return
|
||||
*/
|
||||
public Single<Integer> getImageQuality(UploadItem uploadItem, boolean shouldValidateTitle) {
|
||||
return remoteDataSource.getImageQuality(uploadItem, shouldValidateTitle);
|
||||
public Single<Integer> getImageQuality(UploadItem uploadItem) {
|
||||
return remoteDataSource.getImageQuality(uploadItem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,28 +3,17 @@ package fr.free.nrw.commons.settings;
|
|||
import android.Manifest;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.MultiSelectListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.karumi.dexter.Dexter;
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse;
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
|
|
@ -33,10 +22,15 @@ import fr.free.nrw.commons.logging.CommonsLogSender;
|
|||
import fr.free.nrw.commons.upload.Language;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import static fr.free.nrw.commons.utils.SystemThemeUtils.THEME_MODE_DEFAULT;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragment {
|
||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
|
|
@ -49,29 +43,19 @@ public class SettingsFragment extends PreferenceFragment {
|
|||
private ListPreference langListPreference;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
ApplicationlessInjection
|
||||
.getInstance(getActivity().getApplicationContext())
|
||||
.getCommonsApplicationComponent()
|
||||
.inject(this);
|
||||
.getInstance(getActivity().getApplicationContext())
|
||||
.getCommonsApplicationComponent()
|
||||
.inject(this);
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
// Set the preferences from an XML resource
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey);
|
||||
|
||||
themeListPreference = (ListPreference) findPreference(Prefs.KEY_THEME_VALUE);
|
||||
themeListPreference = findPreference(Prefs.KEY_THEME_VALUE);
|
||||
prepareTheme();
|
||||
|
||||
//Check if the Author Name switch is enabled and appropriately handle the author name usage
|
||||
SwitchPreference useAuthorName = (SwitchPreference) findPreference("useAuthorName");
|
||||
EditTextPreference authorName = (EditTextPreference) findPreference("authorName");
|
||||
authorName.setEnabled(defaultKvStore.getBoolean("useAuthorName", false));
|
||||
useAuthorName.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
authorName.setEnabled((Boolean)newValue);
|
||||
return true;
|
||||
});
|
||||
|
||||
MultiSelectListPreference multiSelectListPref = (MultiSelectListPreference) findPreference(Prefs.MANAGED_EXIF_TAGS);
|
||||
MultiSelectListPreference multiSelectListPref = findPreference(Prefs.MANAGED_EXIF_TAGS);
|
||||
if (multiSelectListPref != null) {
|
||||
multiSelectListPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof HashSet && !((HashSet) newValue).contains(getString(R.string.exif_tag_location))) {
|
||||
|
|
@ -81,42 +65,44 @@ public class SettingsFragment extends PreferenceFragment {
|
|||
});
|
||||
}
|
||||
|
||||
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
|
||||
final EditTextPreference uploadLimit = findPreference("uploads");
|
||||
int currentUploadLimit = defaultKvStore.getInt(Prefs.UPLOADS_SHOWING, 100);
|
||||
uploadLimit.setText(Integer.toString(currentUploadLimit));
|
||||
uploadLimit.setSummary(Integer.toString(currentUploadLimit));
|
||||
uploadLimit.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
uploadLimit.setText(String.valueOf(currentUploadLimit));
|
||||
|
||||
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
|
||||
if (newValue.toString().length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (s.length() == 0) return;
|
||||
|
||||
int value = Integer.parseInt(s.toString());
|
||||
|
||||
if (value > 500) {
|
||||
uploadLimit.getEditText().setError(getString(R.string.maximum_limit_alert));
|
||||
value = 500;
|
||||
} else if (value == 0) {
|
||||
uploadLimit.getEditText().setError(getString(R.string.cannot_be_zero));
|
||||
value = 100;
|
||||
}
|
||||
|
||||
defaultKvStore.putInt(Prefs.UPLOADS_SHOWING, value);
|
||||
defaultKvStore.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, true);
|
||||
uploadLimit.setText(Integer.toString(value));
|
||||
uploadLimit.setSummary(Integer.toString(value));
|
||||
int value = Integer.parseInt(newValue.toString());
|
||||
if (value > 500) {
|
||||
Snackbar error = Snackbar.make(getView(), R.string.maximum_limit_alert, Snackbar.LENGTH_LONG);
|
||||
error.show();
|
||||
return false;
|
||||
} else if (value == 0) {
|
||||
Snackbar error = Snackbar.make(getView(), R.string.cannot_be_zero, Snackbar.LENGTH_LONG);
|
||||
error.show();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
langListPreference = (ListPreference) findPreference("descriptionDefaultLanguagePref");
|
||||
uploadLimit.setOnBindEditTextListener(editText -> {
|
||||
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
editText.selectAll();
|
||||
int maxLength = 3; // set maxLength to 3
|
||||
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(maxLength)});
|
||||
|
||||
int value = Integer.parseInt(editText.getText().toString());
|
||||
|
||||
defaultKvStore.putInt(Prefs.UPLOADS_SHOWING, value);
|
||||
defaultKvStore.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, true);
|
||||
uploadLimit.setText(Integer.toString(value));
|
||||
});
|
||||
|
||||
langListPreference = findPreference("descriptionDefaultLanguagePref");
|
||||
prepareLanguages();
|
||||
Preference betaTesterPreference = findPreference("becomeBetaTester");
|
||||
betaTesterPreference.setOnPreferenceClickListener(preference -> {
|
||||
|
|
@ -129,42 +115,26 @@ public class SettingsFragment extends PreferenceFragment {
|
|||
return true;
|
||||
});
|
||||
// Disable some settings when not logged in.
|
||||
if (defaultKvStore.getBoolean("login_skipped", false)){
|
||||
SwitchPreference useExternalStorage = (SwitchPreference) findPreference("useExternalStorage");
|
||||
SwitchPreference displayNearbyCardView = (SwitchPreference) findPreference("displayNearbyCardView");
|
||||
SwitchPreference displayLocationPermissionForCardView = (SwitchPreference) findPreference("displayLocationPermissionForCardView");
|
||||
SwitchPreference displayCampaignsCardView = (SwitchPreference) findPreference("displayCampaignsCardView");
|
||||
useExternalStorage.setEnabled(false);
|
||||
if (defaultKvStore.getBoolean("login_skipped", false)) {
|
||||
findPreference("useExternalStorage").setEnabled(false);
|
||||
findPreference("useAuthorName").setEnabled(false);
|
||||
findPreference("displayNearbyCardView").setEnabled(false);
|
||||
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
||||
findPreference("displayCampaignsCardView").setEnabled(false);
|
||||
uploadLimit.setEnabled(false);
|
||||
useAuthorName.setEnabled(false);
|
||||
displayNearbyCardView.setEnabled(false);
|
||||
displayLocationPermissionForCardView.setEnabled(false);
|
||||
displayCampaignsCardView.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses previously saved theme if there is any, if not then uses default.
|
||||
* Sets the theme pref
|
||||
*/
|
||||
private void prepareTheme() {
|
||||
|
||||
themeListPreference.setSummary(getThemeSummary(getCurrentTheme()));
|
||||
|
||||
themeListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private CharSequence getThemeSummary(String value) {
|
||||
int prefIndex = themeListPreference.findIndexOfValue(value);
|
||||
return themeListPreference.getEntries()[prefIndex];
|
||||
}
|
||||
|
||||
private String getCurrentTheme() {
|
||||
return defaultKvStore.getString(Prefs.KEY_THEME_VALUE, THEME_MODE_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares language summary and language codes list and adds them to list preference as pairs.
|
||||
* Uses previously saved language if there is any, if not uses phone local as initial language.
|
||||
|
|
@ -195,19 +165,14 @@ public class SettingsFragment extends PreferenceFragment {
|
|||
String languageCode = getCurrentLanguageCode();
|
||||
if (languageCode.equals("")){
|
||||
// If current language code is empty, means none selected by user yet so use phone local
|
||||
langListPreference.setSummary(Locale.getDefault().getDisplayLanguage());
|
||||
langListPreference.setValue(Locale.getDefault().getLanguage());
|
||||
} else {
|
||||
// If any language is selected by user previously, use it
|
||||
int prefIndex = langListPreference.findIndexOfValue(languageCode);
|
||||
langListPreference.setSummary(langListPreference.getEntries()[prefIndex]);
|
||||
langListPreference.setValue(languageCode);
|
||||
}
|
||||
|
||||
langListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String userSelectedValue = (String) newValue;
|
||||
int prefIndex = langListPreference.findIndexOfValue(userSelectedValue);
|
||||
langListPreference.setSummary(langListPreference.getEntries()[prefIndex]);
|
||||
saveLanguageValue(userSelectedValue);
|
||||
return true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Created by seannemann on 6/27/2018.
|
||||
*/
|
||||
|
||||
public class LongTitleEditTextPreference extends EditTextPreference {
|
||||
public LongTitleEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LongTitleEditTextPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LongTitleEditTextPreference(Context context) {
|
||||
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
|
||||
TextView title= view.findViewById(android.R.id.title);
|
||||
if (title != null) {
|
||||
title.setSingleLine(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.ListPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Created by seannemann on 6/27/2018.
|
||||
*/
|
||||
|
||||
public class LongTitleListPreference extends ListPreference {
|
||||
public LongTitleListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LongTitleListPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
|
||||
TextView title= view.findViewById(android.R.id.title);
|
||||
if (title != null) {
|
||||
title.setSingleLine(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.MultiSelectListPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class LongTitleMultiSelectListPreference extends MultiSelectListPreference {
|
||||
/*
|
||||
public LongTitleMultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public LongTitleMultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
*/
|
||||
public LongTitleMultiSelectListPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LongTitleMultiSelectListPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
|
||||
TextView title= view.findViewById(android.R.id.title);
|
||||
if (title != null) {
|
||||
title.setSingleLine(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Created by seannemann on 6/27/2018.
|
||||
*/
|
||||
|
||||
public class LongTitlePreference extends Preference {
|
||||
public LongTitlePreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LongTitlePreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LongTitlePreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
|
||||
TextView title= view.findViewById(android.R.id.title);
|
||||
if (title != null) {
|
||||
title.setSingleLine(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Created by seannemann on 6/27/2018.
|
||||
*/
|
||||
|
||||
public class LongTitlePreferenceCategory extends PreferenceCategory {
|
||||
public LongTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LongTitlePreferenceCategory(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LongTitlePreferenceCategory(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
|
||||
TextView title= view.findViewById(android.R.id.title);
|
||||
if (title != null) {
|
||||
title.setSingleLine(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package fr.free.nrw.commons.ui.LongTitlePreferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* Created by seannemann on 6/27/2018.
|
||||
*/
|
||||
|
||||
public class LongTitleSwitchPreference extends SwitchPreference {
|
||||
public LongTitleSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public LongTitleSwitchPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public LongTitleSwitchPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindView(View view)
|
||||
{
|
||||
super.onBindView(view);
|
||||
|
||||
TextView title= view.findViewById(android.R.id.title);
|
||||
if (title != null) {
|
||||
title.setSingleLine(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,22 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.ImageUtilsWrapper;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_CAPTION;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||
|
||||
/**
|
||||
* Methods for pre-processing images to be uploaded
|
||||
*/
|
||||
|
|
@ -43,38 +40,34 @@ public class ImageProcessingService {
|
|||
this.mediaClient = mediaClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check image quality before upload
|
||||
* - checks duplicate image
|
||||
* - checks dark image
|
||||
* - checks geolocation for image
|
||||
* - check for valid caption
|
||||
*/
|
||||
Single<Integer> validateImage(UploadModel.UploadItem uploadItem, boolean checkTitle) {
|
||||
int currentImageQuality = uploadItem.getImageQuality();
|
||||
Timber.d("Current image quality is %d", currentImageQuality);
|
||||
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
Timber.d("Checking the validity of image");
|
||||
String filePath = uploadItem.getMediaUri().getPath();
|
||||
Single<Integer> duplicateImage = checkDuplicateImage(filePath);
|
||||
Single<Integer> wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath);
|
||||
Single<Integer> darkImage = checkDarkImage(filePath);
|
||||
Single<Integer> itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK);
|
||||
Single<Integer> checkFBMD = checkFBMD(filePath);
|
||||
Single<Integer> checkEXIF = checkEXIF(filePath);
|
||||
|
||||
Single<Integer> zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle,
|
||||
(duplicate, wrongGeo, dark, title) -> {
|
||||
Timber.d("Result for duplicate: %d, geo: %d, dark: %d, title: %d", duplicate, wrongGeo, dark, title);
|
||||
return duplicate | wrongGeo | dark | title;
|
||||
});
|
||||
return Single.zip(zipResult, checkFBMD , checkEXIF , (zip , fbmd , exif)->{
|
||||
Timber.d("zip:" + zip + "fbmd:" + fbmd + "exif:" + exif);
|
||||
return zip | fbmd | exif;
|
||||
});
|
||||
/**
|
||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||
* geolocation for image - check for valid title
|
||||
*/
|
||||
Single<Integer> validateImage(UploadModel.UploadItem uploadItem) {
|
||||
int currentImageQuality = uploadItem.getImageQuality();
|
||||
Timber.d("Current image quality is %d", currentImageQuality);
|
||||
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
Timber.d("Checking the validity of image");
|
||||
String filePath = uploadItem.getMediaUri().getPath();
|
||||
|
||||
return Single.zip(
|
||||
checkDuplicateImage(filePath),
|
||||
checkImageGeoLocation(uploadItem.getPlace(), filePath),
|
||||
checkDarkImage(filePath),
|
||||
validateItemTitle(uploadItem),
|
||||
checkFBMD(filePath),
|
||||
checkEXIF(filePath),
|
||||
(duplicateImage, wrongGeoLocation, darkImage, itemTitle, fbmd, exif) -> {
|
||||
Timber.d("duplicate: %d, geo: %d, dark: %d, title: %d" + "fbmd:" + fbmd + "exif:" + exif,
|
||||
duplicateImage, wrongGeoLocation, darkImage, itemTitle);
|
||||
return duplicateImage | wrongGeoLocation | darkImage | itemTitle | fbmd | exif;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to discourage users from uploading images to Commons that were taken from Facebook.
|
||||
|
|
@ -115,7 +108,8 @@ public class ImageProcessingService {
|
|||
.map(doesFileExist -> {
|
||||
Timber.d("Result for valid title is %s", doesFileExist);
|
||||
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
||||
});
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -126,14 +120,14 @@ public class ImageProcessingService {
|
|||
*/
|
||||
private Single<Integer> checkDuplicateImage(String filePath) {
|
||||
Timber.d("Checking for duplicate image %s", filePath);
|
||||
return Single.fromCallable(() ->
|
||||
fileUtilsWrapper.getFileInputStream(filePath))
|
||||
return Single.fromCallable(() -> fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(fileUtilsWrapper::getSHA1)
|
||||
.flatMap(mediaClient::checkFileExistsUsingSha)
|
||||
.map(b -> {
|
||||
Timber.d("Result for duplicate image %s", b);
|
||||
return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
|
||||
});
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,7 +160,8 @@ public class ImageProcessingService {
|
|||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
return imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
|
||||
});
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import io.reactivex.Single;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* We want to discourage users from uploading images to Commons that were taken from Facebook.
|
||||
* This attempts to detect whether an image was downloaded from Facebook by heuristically
|
||||
* searching for metadata that is specific to images that come from Facebook.
|
||||
* We want to discourage users from uploading images to Commons that were taken from Facebook. This
|
||||
* attempts to detect whether an image was downloaded from Facebook by heuristically searching for
|
||||
* metadata that is specific to images that come from Facebook.
|
||||
*/
|
||||
@Singleton
|
||||
public class ReadFBMD {
|
||||
|
||||
@Inject
|
||||
public ReadFBMD() {
|
||||
}
|
||||
@Inject
|
||||
public ReadFBMD() {
|
||||
}
|
||||
|
||||
public Single<Integer> processMetadata(String path) {
|
||||
try {
|
||||
int psBlockOffset;
|
||||
int fbmdOffset;
|
||||
public Single<Integer> processMetadata(String path) {
|
||||
return Single.fromCallable(() -> {
|
||||
try {
|
||||
int psBlockOffset;
|
||||
int fbmdOffset;
|
||||
|
||||
try (FileInputStream fs = new FileInputStream(path)) {
|
||||
byte[] bytes = new byte[4096];
|
||||
fs.read(bytes);
|
||||
fs.close();
|
||||
String fileStr = new String(bytes);
|
||||
psBlockOffset = fileStr.indexOf("8BIM");
|
||||
fbmdOffset = fileStr.indexOf("FBMD");
|
||||
}
|
||||
|
||||
if (psBlockOffset > 0 && fbmdOffset > 0
|
||||
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
|
||||
return Single.just(ImageUtils.FILE_FBMD);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
try (FileInputStream fs = new FileInputStream(path)) {
|
||||
byte[] bytes = new byte[4096];
|
||||
fs.read(bytes);
|
||||
fs.close();
|
||||
String fileStr = new String(bytes);
|
||||
psBlockOffset = fileStr.indexOf("8BIM");
|
||||
fbmdOffset = fileStr.indexOf("FBMD");
|
||||
}
|
||||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
|
||||
if (psBlockOffset > 0 && fbmdOffset > 0
|
||||
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
|
||||
return ImageUtils.FILE_FBMD;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ImageUtils.IMAGE_OK;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.utils.BiMap;
|
||||
import fr.free.nrw.commons.utils.LangCodeUtils;
|
||||
|
||||
|
||||
public class SpinnerLanguagesAdapter extends ArrayAdapter {
|
||||
|
||||
private final int resource;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private List<String> languageNamesList;
|
||||
private List<String> languageCodesList;
|
||||
private final BiMap<AdapterView, String> selectedLanguages;
|
||||
public String selectedLangCode="";
|
||||
private Context context;
|
||||
private boolean dropDownClicked;
|
||||
private String savedLanguageValue;
|
||||
|
||||
|
||||
|
||||
public SpinnerLanguagesAdapter(@NonNull Context context,
|
||||
int resource,
|
||||
BiMap<AdapterView, String> selectedLanguages,
|
||||
String savedLanguageValue) {
|
||||
super(context, resource);
|
||||
this.resource = resource;
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
languageNamesList = new ArrayList<>();
|
||||
languageCodesList = new ArrayList<>();
|
||||
prepareLanguages();
|
||||
this.selectedLanguages = selectedLanguages;
|
||||
this.context = context;
|
||||
this.dropDownClicked = false;
|
||||
this.savedLanguageValue = savedLanguageValue;
|
||||
}
|
||||
|
||||
private void prepareLanguages() {
|
||||
List<Language> languages = getLocaleSupportedByDevice();
|
||||
|
||||
for(Language language: languages) {
|
||||
if(!languageCodesList.contains(language.getLocale().getLanguage())) {
|
||||
languageNamesList.add(language.getLocale().getDisplayName());
|
||||
languageCodesList.add(language.getLocale().getLanguage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Language> getLocaleSupportedByDevice() {
|
||||
List<Language> languages = new ArrayList<>();
|
||||
Locale[] localesArray = Locale.getAvailableLocales();
|
||||
for (Locale locale : localesArray) {
|
||||
languages.add(new Language(locale));
|
||||
}
|
||||
|
||||
Collections.sort(languages, (language, t1) -> language.getLocale().getDisplayName()
|
||||
.compareTo(t1.getLocale().getDisplayName()));
|
||||
return languages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return !languageCodesList.get(position).isEmpty()&&
|
||||
(!selectedLanguages.containsKey(languageCodesList.get(position)) ||
|
||||
languageCodesList.get(position).equals(selectedLangCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return languageNamesList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView,
|
||||
@NonNull ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = layoutInflater.inflate(resource, parent, false);
|
||||
}
|
||||
ViewHolder holder = new ViewHolder(convertView);
|
||||
holder.init(position, true, savedLanguageValue);
|
||||
|
||||
dropDownClicked = true;
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = layoutInflater.inflate(resource, parent, false);
|
||||
holder = new ViewHolder(convertView);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
holder.init(position, false, savedLanguageValue);
|
||||
return convertView;
|
||||
}
|
||||
|
||||
|
||||
public class ViewHolder {
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.tv_language)
|
||||
TextView tvLanguage;
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.view)
|
||||
View view;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
ButterKnife.bind(this, itemView);
|
||||
}
|
||||
|
||||
public void init(int position, boolean isDropDownView, String savedLanguageValue) {
|
||||
String languageCode = LangCodeUtils.fixLanguageCode(languageCodesList.get(position));
|
||||
final String languageName = StringUtils.capitalize(languageNamesList.get(position));
|
||||
|
||||
if(TextUtils.isEmpty(savedLanguageValue)){
|
||||
savedLanguageValue = Locale.getDefault().getLanguage();
|
||||
}
|
||||
|
||||
if (!isDropDownView) {
|
||||
if( !dropDownClicked && savedLanguageValue !=null){
|
||||
languageCode = LangCodeUtils.fixLanguageCode(savedLanguageValue);
|
||||
}
|
||||
if (view != null) {
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
if (languageCode.length() > 2)
|
||||
tvLanguage.setText(languageCode.substring(0, 2));
|
||||
else
|
||||
tvLanguage.setText(languageCode);
|
||||
} else {
|
||||
view.setVisibility(View.VISIBLE);
|
||||
if (languageCodesList.get(position).isEmpty()) {
|
||||
tvLanguage.setText(languageName);
|
||||
tvLanguage.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
} else {
|
||||
tvLanguage.setText(
|
||||
String.format("%s [%s]", languageName, languageCode));
|
||||
if (selectedLanguages.containsKey(languageCodesList.get(position)) &&
|
||||
!languageCodesList.get(position).equals(selectedLangCode)) {
|
||||
tvLanguage.setTextColor(Color.GRAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getLanguageCode(int position) {
|
||||
return languageCodesList.get(position);
|
||||
}
|
||||
|
||||
int getIndexOfUserDefaultLocale(Context context) {
|
||||
return languageCodesList.indexOf(context.getResources().getConfiguration().locale.getLanguage());
|
||||
}
|
||||
|
||||
int getIndexOfLanguageCode(String languageCode) {
|
||||
return languageCodesList.indexOf(languageCode);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.utils.BiMap
|
||||
import fr.free.nrw.commons.utils.LangCodeUtils
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.row_item_languages_spinner.*
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This class handles the display of language spinners and their dropdown views for UploadMediaDetailFragment
|
||||
*
|
||||
* @property selectedLanguages - controls the enabled state of dropdown views
|
||||
*
|
||||
* @param context - required by super constructor
|
||||
*/
|
||||
class SpinnerLanguagesAdapter constructor(
|
||||
context: Context,
|
||||
private val selectedLanguages: BiMap<*, String>
|
||||
) : ArrayAdapter<Any?>(context, -1) {
|
||||
|
||||
private val languageNamesList: List<String>
|
||||
private val languageCodesList: List<String>
|
||||
|
||||
init {
|
||||
val sortedLanguages = Locale.getAvailableLocales()
|
||||
.map(::Language)
|
||||
.sortedBy { it.locale.displayName }
|
||||
languageNamesList = sortedLanguages.map { it.locale.displayName }
|
||||
languageCodesList = sortedLanguages.map { it.locale.language }
|
||||
}
|
||||
|
||||
var selectedLangCode = ""
|
||||
|
||||
override fun isEnabled(position: Int) = languageCodesList[position].let {
|
||||
it.isNotEmpty() && !selectedLanguages.containsKey(it) && it != selectedLangCode
|
||||
}
|
||||
|
||||
override fun getCount() = languageNamesList.size
|
||||
|
||||
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup) =
|
||||
(convertView ?: parent.inflate(R.layout.row_item_languages_spinner).also {
|
||||
it.tag = DropDownViewHolder(it)
|
||||
}).apply {
|
||||
(tag as DropDownViewHolder).init(
|
||||
languageCodesList[position],
|
||||
languageNamesList[position],
|
||||
isEnabled(position)
|
||||
)
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup) =
|
||||
(convertView ?: parent.inflate(R.layout.row_item_languages_spinner).also {
|
||||
it.tag = SpinnerViewHolder(it)
|
||||
}).apply { (tag as SpinnerViewHolder).init(languageCodesList[position]) }
|
||||
|
||||
class SpinnerViewHolder(override val containerView: View) : LayoutContainer {
|
||||
fun init(languageCode: String) {
|
||||
LangCodeUtils.fixLanguageCode(languageCode).let {
|
||||
tv_language.text = if (it.length > 2) it.take(2) else it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DropDownViewHolder(override val containerView: View) : LayoutContainer {
|
||||
fun init(languageCode: String, languageName: String, enabled: Boolean) {
|
||||
tv_language.isEnabled = enabled
|
||||
if (languageCode.isEmpty()) {
|
||||
tv_language.text = StringUtils.capitalize(languageName)
|
||||
tv_language.textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||
} else {
|
||||
tv_language.text =
|
||||
"${StringUtils.capitalize(languageName)}" +
|
||||
" [${LangCodeUtils.fixLanguageCode(languageCode)}]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getLanguageCode(position: Int): String {
|
||||
return languageCodesList[position]
|
||||
}
|
||||
|
||||
fun getIndexOfUserDefaultLocale(context: Context): Int {
|
||||
return languageCodesList.indexOf(context.locale.language)
|
||||
}
|
||||
|
||||
fun getIndexOfLanguageCode(languageCode: String): Int {
|
||||
return languageCodesList.indexOf(languageCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ViewGroup.inflate(@LayoutRes resId: Int) =
|
||||
LayoutInflater.from(context).inflate(resId, this, false)
|
||||
|
||||
private val Context.locale: Locale
|
||||
get() = ConfigurationCompat.getLocales(resources.configuration)[0]
|
||||
|
|
@ -202,8 +202,8 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
|
|||
private void initLanguageSpinner(int position, UploadMediaDetail description) {
|
||||
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(
|
||||
spinnerDescriptionLanguages.getContext(),
|
||||
R.layout.row_item_languages_spinner, selectedLanguages,
|
||||
savedLanguageValue);
|
||||
selectedLanguages
|
||||
);
|
||||
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
|
||||
|
||||
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
|
@ -217,7 +217,7 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
|
|||
selectedLanguages.remove(adapterView);
|
||||
selectedLanguages.put(adapterView, languageCode);
|
||||
((SpinnerLanguagesAdapter) adapterView
|
||||
.getAdapter()).selectedLangCode = languageCode;
|
||||
.getAdapter()).setSelectedLangCode(languageCode);
|
||||
spinnerDescriptionLanguages.setSelection(position);
|
||||
Timber.d("Description language code is: "+languageCode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,31 +3,8 @@ package fr.free.nrw.commons.upload;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.filepicker.MimeTypeMapWrapper;
|
||||
|
|
@ -40,6 +17,17 @@ import io.reactivex.Observable;
|
|||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import timber.log.Timber;
|
||||
|
||||
@Singleton
|
||||
|
|
@ -131,8 +119,8 @@ public class UploadModel {
|
|||
return Observable.just(getUploadItem(uploadableFile, place, source, similarImageInterface));
|
||||
}
|
||||
|
||||
public Single<Integer> getImageQuality(UploadItem uploadItem, boolean checkTitle) {
|
||||
return imageProcessingService.validateImage(uploadItem, checkTitle);
|
||||
public Single<Integer> getImageQuality(UploadItem uploadItem) {
|
||||
return imageProcessingService.validateImage(uploadItem);
|
||||
}
|
||||
|
||||
private UploadItem getUploadItem(UploadableFile uploadableFile,
|
||||
|
|
@ -213,6 +201,8 @@ public class UploadModel {
|
|||
CommonsApplication.DEFAULT_EDIT_SUMMARY, selectedDepictions, item.gpsCoords.getCoords());
|
||||
if (item.place != null) {
|
||||
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
|
||||
// If item already has an image, we need to know it. We don't want to override existing image later
|
||||
contribution.setP18Value(item.place.pic);
|
||||
}
|
||||
if (null == selectedCategories) {//Just a fail safe, this should never be null
|
||||
selectedCategories = new ArrayList<>();
|
||||
|
|
|
|||
|
|
@ -289,12 +289,15 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
Timber.d("Contribution upload success. Initiating Wikidata edit for entity id %s",
|
||||
contribution.getWikiDataEntityId());
|
||||
// to perform upload of depictions we pass on depiction entityId of the selected depictions to the wikidataEditService
|
||||
final String p18Value = contribution.getP18Value();
|
||||
if (contribution.getDepictionsEntityIds() != null) {
|
||||
for (String s : contribution.getDepictionsEntityIds()) {
|
||||
wikidataEditService.createClaimWithLogging(s, canonicalFilename);
|
||||
wikidataEditService.createClaimWithLogging(s, canonicalFilename,
|
||||
p18Value);
|
||||
}
|
||||
}
|
||||
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), canonicalFilename);
|
||||
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), canonicalFilename,
|
||||
p18Value);
|
||||
wikidataEditService.createLabelforWikidataEntity(contribution.getWikiDataEntityId(), canonicalFilename,
|
||||
(Map) contribution.getCaptions());
|
||||
contribution.setFilename(canonicalFilename);
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
@OnClick(R.id.btn_next)
|
||||
public void onNextButtonClicked() {
|
||||
uploadItem.setMediaDetails(uploadMediaDetailAdapter.getUploadMediaDetails());
|
||||
presenter.verifyImageQuality(uploadItem, true);
|
||||
presenter.verifyImageQuality(uploadItem);
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_previous)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public interface UploadMediaDetailsContract {
|
|||
void receiveImage(UploadableFile uploadableFile, @Contribution.FileSource String source,
|
||||
Place place);
|
||||
|
||||
void verifyImageQuality(UploadItem uploadItem, boolean validateTitle);
|
||||
void verifyImageQuality(UploadItem uploadItem);
|
||||
|
||||
void setUploadItem(int index, UploadItem uploadItem);
|
||||
|
||||
|
|
|
|||
|
|
@ -111,14 +111,14 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
|||
* asks the repository to verify image quality
|
||||
*
|
||||
* @param uploadItem
|
||||
* @param validateTitle
|
||||
*/
|
||||
@Override
|
||||
public void verifyImageQuality(UploadItem uploadItem, boolean validateTitle) {
|
||||
public void verifyImageQuality(UploadItem uploadItem) {
|
||||
view.showProgress(true);
|
||||
Disposable imageQualityDisposable = repository
|
||||
.getImageQuality(uploadItem, true)
|
||||
.subscribeOn(ioScheduler)
|
||||
|
||||
compositeDisposable.add(
|
||||
repository
|
||||
.getImageQuality(uploadItem)
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribe(imageResult -> {
|
||||
view.showProgress(false);
|
||||
|
|
@ -129,9 +129,8 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
|||
view.showMessage("" + throwable.getLocalizedMessage(),
|
||||
R.color.color_error);
|
||||
Timber.e(throwable, "Error occurred while handling image");
|
||||
});
|
||||
|
||||
compositeDisposable.add(imageQualityDisposable);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class ImageUtilsWrapper {
|
||||
|
||||
@Inject
|
||||
public ImageUtilsWrapper() {
|
||||
@Inject
|
||||
public ImageUtilsWrapper() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
||||
return Single.just(ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(Schedulers.computation());
|
||||
}
|
||||
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
||||
return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
||||
.subscribeOn(Schedulers.computation());
|
||||
}
|
||||
|
||||
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString, LatLng latLng) {
|
||||
boolean isImageGeoLocationDifferent = ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng);
|
||||
return Single.just(isImageGeoLocationDifferent)
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(Schedulers.computation())
|
||||
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT : ImageUtils.IMAGE_OK);
|
||||
}
|
||||
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString,
|
||||
LatLng latLng) {
|
||||
return Single.fromCallable(
|
||||
() -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
|
||||
: ImageUtils.IMAGE_OK);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,9 @@ package fr.free.nrw.commons.wikidata;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import org.wikipedia.csrf.CsrfTokenClient;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
|
|
@ -29,6 +16,15 @@ import io.reactivex.Observable;
|
|||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.wikipedia.csrf.CsrfTokenClient;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -75,11 +71,17 @@ public class WikidataEditService {
|
|||
|
||||
/**
|
||||
* Create a P18 claim and log the edit with custom tag
|
||||
<<<<<<< HEAD
|
||||
*
|
||||
* @param wikidataEntityId
|
||||
* @param fileName
|
||||
=======
|
||||
* @param wikidataEntityId a unique id of each Wikidata items
|
||||
* @param fileName name of the file we will upload
|
||||
* @param p18Value pic attribute of Wikidata item
|
||||
>>>>>>> origin/master
|
||||
*/
|
||||
public void createClaimWithLogging(String wikidataEntityId, String fileName) {
|
||||
public void createClaimWithLogging(String wikidataEntityId, String fileName, @NonNull String p18Value) {
|
||||
if (wikidataEntityId == null) {
|
||||
Timber.d("Skipping creation of claim as Wikidata entity ID is null");
|
||||
return;
|
||||
|
|
@ -95,6 +97,11 @@ public class WikidataEditService {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!p18Value.trim().isEmpty()) {
|
||||
Timber.d("Skipping creation of claim as p18Value is not empty, we won't override existing image");
|
||||
return;
|
||||
}
|
||||
|
||||
editWikidataProperty(wikidataEntityId, fileName);
|
||||
//editWikiBaseDepictsProperty(wikidataEntityId, fileName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true"
|
||||
android:drawable="@color/opak_middle_grey" />
|
||||
<item android:drawable="@color/black" />
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true"
|
||||
android:drawable="@color/divider_grey" />
|
||||
<item android:drawable="@color/white" />
|
||||
</selector>
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/search_list_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/tiny_margin">
|
||||
android:padding="@dimen/tiny_margin"
|
||||
android:background="@drawable/linearlayout_color_selector">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/place_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/place_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"></TextView>
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
|
@ -4,16 +4,17 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/tiny_margin"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/linearlayout_color_dark_selector">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/place_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/place_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"></TextView>
|
||||
android:textColor="@color/white"/>
|
||||
</LinearLayout>
|
||||
|
|
@ -22,9 +22,4 @@
|
|||
tools:text="en"
|
||||
/>
|
||||
|
||||
<View
|
||||
android:id="@+id/view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/tiny_height"
|
||||
android:background="@color/item_white_background" />
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<string name="gps_disabled">GPS is uitgesit in uw apparaat. Wil u die aansit?</string>
|
||||
<string name="enable_gps">GPS aansit</string>
|
||||
<string name="contributions_subtitle_zero">Nog geen uploads</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -369,7 +369,6 @@
|
|||
<string name="preference_author_name_toggle">Gebruik die gepaste outeur se naam</string>
|
||||
<string name="preference_author_name_toggle_summary">Gebruik \'n pasgemaakte outeurnaam in plaas van u gebruikersnaam terwyl u foto\'s oplaai</string>
|
||||
<string name="preference_author_name">Pasgemaakte outeurnaam</string>
|
||||
<string name="preference_author_name_summary">Die pasgemaakte outeur se naam om te gebruik in plaas van u gebruikersnaam in oplaaie</string>
|
||||
<string name="contributions_fragment">bydraes</string>
|
||||
<string name="nearby_fragment">Naby</string>
|
||||
<string name="notifications">Kennisgewings</string>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
* Mido
|
||||
* Monrokhoury
|
||||
* Mr. Ibrahem
|
||||
* NEHAOUA
|
||||
* OsamaK
|
||||
* ترجمان05
|
||||
* ديفيد
|
||||
|
|
@ -49,7 +50,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">اكتمال رفع %1$s</string>
|
||||
<string name="upload_failed_notification_title">فشل رفع %1$s</string>
|
||||
<string name="upload_failed_notification_subtitle">انقر لتشاهد</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">%1$d رفع ملف</item>
|
||||
<item quantity="other">%1$d رفع ملفات</item>
|
||||
</plurals>
|
||||
|
|
@ -85,16 +86,16 @@
|
|||
<string name="gps_disabled">عطل نظام الملاحة العالمي GPS بجهازك. أترغب في التنشيط؟</string>
|
||||
<string name="enable_gps">تفعيل GPS</string>
|
||||
<string name="contributions_subtitle_zero">لا مرفوعات بعد</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
</plurals>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<plurals name="starting_multiple_uploads" fuzzy="true">
|
||||
<item quantity="one">جارٍ بدء %1$d رفع</item>
|
||||
<item quantity="other">جارٍ بدء %1$d مرفوعات</item>
|
||||
</plurals>
|
||||
<string name="multiple_uploads_title">{{جمع|واحد=%1$d رفع|%1$d رفع}}</string>
|
||||
<string name="multiple_uploads_title" fuzzy="true">{{جمع|واحد=%1$d رفع|%1$d رفع}}</string>
|
||||
<string name="categories_not_found">لا توجد تصنيفات تطابق %1$s</string>
|
||||
<string name="categories_skip_explanation">أضف تصنيفات لجعل صورك أكثر قابلية للاكتشاف على ويكيميديا كومنز.\n\nابدأ الكتابة لإضافة تصنيفات.</string>
|
||||
<string name="categories_activity_title">تصنيفات</string>
|
||||
|
|
@ -106,8 +107,8 @@
|
|||
<string name="menu_about">حول</string>
|
||||
<string name="about_license">تطبيق ويكيميديا كومنز تطبيق مفتوح المصدر تم إنشاؤه وصيانته من قبل المستفيدين والمتطوعين من مجتمع ويكيميديا، لا تشارك مؤسسة ويكيميديا في إنشاء التطبيق أو تطويره أو صيانته.</string>
|
||||
<string name="about_improve">أنشئ <a href=\"%1$s\">مشكلة غيت هب</a> جديدة لتقارير الأخطاء والاقتراحات.</string>
|
||||
<string name="about_privacy_policy" fuzzy="true"><u>Pسياسة الخصوصية</u></string>
|
||||
<string name="about_credits" fuzzy="true"><u>الإحالات</u></string>
|
||||
<string name="about_privacy_policy">سياسة الخصوصية</string>
|
||||
<string name="about_credits">الإحالات</string>
|
||||
<string name="title_activity_about">حول</string>
|
||||
<string name="menu_feedback">إرسال ملاحظات (عبر البريد الإلكتروني)</string>
|
||||
<string name="no_email_client">عميل البريد الإلكتروني غير مثبت</string>
|
||||
|
|
@ -116,7 +117,7 @@
|
|||
<string name="no_uploads_yet">لم ترفع بعد أية صور.</string>
|
||||
<string name="menu_retry_upload">إعادة المحاولة</string>
|
||||
<string name="menu_cancel_upload">إلغاء</string>
|
||||
<plurals name="share_license_summary">
|
||||
<plurals name="share_license_summary" fuzzy="true">
|
||||
<item quantity="one">سيتم ترخيص هذه الصورة تحت %1$s</item>
|
||||
<item quantity="other">سيتم ترخيص هذه الصور تحت %1$s</item>
|
||||
</plurals>
|
||||
|
|
@ -126,7 +127,7 @@
|
|||
<string name="use_previous">استخدم العنوان والوصف السابق</string>
|
||||
<string name="allow_gps">الحصول تلقائيا على الموقع الحالي</string>
|
||||
<string name="allow_gps_summary">يسترد الموقع الحالي إذا لم يتم وسم الصورة جغرافيا، وكان هناك وسم جغرافي عليها، تحذير: سيكشف هذا عن موقعك الحالي.</string>
|
||||
<string name="preference_theme" fuzzy="true">الوضع الليلي</string>
|
||||
<string name="preference_theme">الوضع الليلي</string>
|
||||
<string name="license_name_cc_by_sa_four">النسبة-الترخيص بالمثل 4.0</string>
|
||||
<string name="license_name_cc_by_four">الإحالة 4.0</string>
|
||||
<string name="license_name_cc_by_sa">النسبة-الترخيص بالمثل 3.0</string>
|
||||
|
|
@ -167,7 +168,7 @@
|
|||
<string name="welcome_copyright_subtext">تجنب المواد محفوظة الحقوق الموجودة على الإنترنت وصور الملصقات وأغلفة الكتب، إلخ.</string>
|
||||
<string name="welcome_final_text">هل تظن أنك فهمت؟</string>
|
||||
<string name="welcome_final_button_text">نعم!</string>
|
||||
<string name="welcome_help_button_text" fuzzy="true"><u>المزيد من المعلومات</u></string>
|
||||
<string name="welcome_help_button_text">مزيد من المعلومات</string>
|
||||
<string name="detail_panel_cats_label">تصنيفات</string>
|
||||
<string name="detail_panel_cats_loading">جارٍ التحميل…</string>
|
||||
<string name="detail_panel_cats_none">لا شيء محدد</string>
|
||||
|
|
@ -269,7 +270,7 @@
|
|||
<string name="null_url">خطأ! المسار غير موجود</string>
|
||||
<string name="nominate_deletion">ترشيح للحذف</string>
|
||||
<string name="nominated_for_deletion">الصورة تم ترشيحها للحذف.</string>
|
||||
<string name="nominated_see_more" fuzzy="true"><u>انظر صفحة الويب للتفاصيل</u></string>
|
||||
<string name="nominated_see_more">انظر صفحة الويب للتفاصيل</string>
|
||||
<string name="nominating_file_for_deletion">جارٍ ترشيح %1$s للحذف.</string>
|
||||
<string name="nominating_for_deletion_status">جارٍ ترشيح الملف للحذف: %1$s</string>
|
||||
<string name="view_browser">عرض في المتصفح</string>
|
||||
|
|
@ -281,7 +282,7 @@
|
|||
<string name="copy_wikicode">انسخ نص الويكي إلى الكليب بورد</string>
|
||||
<string name="wikicode_copied">نص الويكي تم نسخه إلى الكليب بورد</string>
|
||||
<string name="nearby_location_has_not_changed">الموقع لم يتغير.</string>
|
||||
<string name="nearby_location_not_available" fuzzy="true">الموقع غير متوفر.</string>
|
||||
<string name="nearby_location_not_available">قد لا تعمل الأجهزة المجاورة بشكل صحيح، الموقع غير متوفر.</string>
|
||||
<string name="location_permission_rationale_nearby">صلاحية مطلوبة لعرض قائمة بالأماكن القريبة</string>
|
||||
<string name="get_directions">الحصول على الاتجاهات</string>
|
||||
<string name="read_article">قراءة المقالة</string>
|
||||
|
|
@ -294,8 +295,8 @@
|
|||
<string name="nearby_wikidata">ويكي بيانات</string>
|
||||
<string name="nearby_wikipedia">ويكيبيديا</string>
|
||||
<string name="nearby_commons">كومنز</string>
|
||||
<string name="about_rate_us" fuzzy="true"><u>قيِّمنا</u></string>
|
||||
<string name="about_faq" fuzzy="true"><u>الأسئلة المتكررة</u></string>
|
||||
<string name="about_rate_us">قيِّمنا</string>
|
||||
<string name="about_faq">الأسئلة المتكررة</string>
|
||||
<string name="welcome_skip_button">تخطي البرنامج التعليمي</string>
|
||||
<string name="no_internet">الإنترنت غير متوفر</string>
|
||||
<string name="internet_established">الإنترنت متاح</string>
|
||||
|
|
@ -303,7 +304,7 @@
|
|||
<string name="error_review">خطأ في جلب الصورة للمراجعة، اضغط على تحديث للمحاولة مرة أخرى.</string>
|
||||
<string name="error_review_categories">خطأ في جلب تصنيات الصورة للمراجعة، اضغط على تحديث للمحاولة مرة أخرى.</string>
|
||||
<string name="no_notifications">لم يتم العثور على إشعارات</string>
|
||||
<string name="about_translate" fuzzy="true"><u>ترجمة</u></string>
|
||||
<string name="about_translate">ترجمة</string>
|
||||
<string name="about_translate_title">اللغات</string>
|
||||
<string name="about_translate_message">اختر اللغة التي ترغب في تقديم ترجمات لها</string>
|
||||
<string name="about_translate_proceed">تقدم</string>
|
||||
|
|
@ -386,7 +387,6 @@
|
|||
<string name="preference_author_name_toggle">استخدم اسم مؤلف مخصصا</string>
|
||||
<string name="preference_author_name_toggle_summary">استخدم اسم مؤلف مخصصا بدلا من اسم المستخدم الخاص بك أثناء رفع الصور</string>
|
||||
<string name="preference_author_name">اسم المؤلف المخصص</string>
|
||||
<string name="preference_author_name_summary">اسم المؤلف المخصص لاستخدامه بدلا من اسم المستخدم الخاص بك في المرفوعات</string>
|
||||
<string name="contributions_fragment">مساهمات</string>
|
||||
<string name="nearby_fragment">مجاور</string>
|
||||
<string name="notifications">الإشعارات</string>
|
||||
|
|
@ -405,7 +405,7 @@
|
|||
<string name="submit">إرسال</string>
|
||||
<string name="upload_title_duplicate">يوجد ملف باسم الملف %1$s، هل أنت متأكد أنك تريد المتابعة؟</string>
|
||||
<string name="map_application_missing">لا يمكن العثور على تطبيق خرائط متوافق على جهازك; الرجاء تثبيت تطبيق خرائط لاستخدام هذه الميزة.</string>
|
||||
<plurals name="upload_count_title">
|
||||
<plurals name="upload_count_title" fuzzy="true">
|
||||
<item quantity="one">%1$d رفع</item>
|
||||
<item quantity="other">%1$d مرفوعات</item>
|
||||
</plurals>
|
||||
|
|
@ -445,6 +445,7 @@
|
|||
<string name="display_location_permission_title">عرض إذن الموقع</string>
|
||||
<string name="display_location_permission_explanation">اطلب إذن الموقع عند الحاجة إلى ميزة عرض بطاقة التنبيه القريبة.</string>
|
||||
<string name="achievements_fetch_failed">حدث خطأ ما; لم نتمكن من جلب إنجازاتك</string>
|
||||
<string name="achievements_fetch_failed_ultimate_achievement">لقد قدمت الكثير من المساهمات لا يستطيع نظامنا لحساب الإنجازات التعامل معها. هذا هو الإنجاز النهائي.</string>
|
||||
<string name="ends_on">ينتهي في:</string>
|
||||
<string name="display_campaigns">عرض الحملات</string>
|
||||
<string name="display_campaigns_explanation">انظر الحملات الجارية</string>
|
||||
|
|
@ -495,7 +496,7 @@
|
|||
<string name="review_thanks_no_button_text">الصورة التالية</string>
|
||||
<string name="skip_image_explanation">النقر فوق هذا الزر سيمنحك صورة أخرى مرفوعة مؤخرا من ويكيميديا كومنز</string>
|
||||
<string name="review_image_explanation">يمكنك مراجعة الصور وتحسين جودة ويكيميديا كومنز.\n وسائط اللمراجعة الأربعة هي:\n - هل هذه الصورة في النطاق؟\n - هل تتبع هذه الصورة قواعد حقوق النشر؟\n - هل هذه الصورة مصنفة بشكل صحيح؟\n - إذا سارت الأمور على ما يرام يمكنك أيضا شكر المساهم.</string>
|
||||
<plurals name="receiving_shared_content">
|
||||
<plurals name="receiving_shared_content" fuzzy="true">
|
||||
<item quantity="one">جارٍ تلقي محتوى مشترك، قد تستغرق معالجة الصورة بعض الوقت بناءً على حجم الصور وجهازك</item>
|
||||
<item quantity="other">جارٍ تلقي محتوى مشترك، قد تستغرق معالجة الصور بعض الوقت بناءً على حجم الصور وجهازك</item>
|
||||
</plurals>
|
||||
|
|
@ -564,5 +565,17 @@
|
|||
<string name="place_type">نوع المكان:</string>
|
||||
<string name="nearby_search_hint">جسر، متحف، فندق، إلخ</string>
|
||||
<string name="you_must_reset_your_passsword">حدث خطأ ما عند الدخول، يجب عليك إعادة تعيين كلمة المرور!!</string>
|
||||
<string name="upload_nearby_place_found_title">تم العثور على مكان قريب</string>
|
||||
<string name="upload_nearby_place_found_description">هل هذه صورة للمكان %1$؟</string>
|
||||
<string name="title_app_shortcut_explore">استكشف</string>
|
||||
<string name="title_app_shortcut_bookmark">العلامات</string>
|
||||
<string name="title_app_shortcut_setting">الإعدادات</string>
|
||||
<string name="remove_bookmark">أزل من العلامات</string>
|
||||
<string name="add_bookmark">اضف إلى العلامات</string>
|
||||
<string name="wallpaper_set_unsuccessfully">هناك خطأ ما. تعذر تعيين الخلفية</string>
|
||||
<string name="setting_wallpaper_dialog_title">اجعلها خلفية</string>
|
||||
<string name="setting_wallpaper_dialog_message">إعداد الخلفية. أرجو الإنتظار…</string>
|
||||
<string name="theme_default_name">افتراضي</string>
|
||||
<string name="theme_dark_name">غامق</string>
|
||||
<string name="theme_light_name">فاتح</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
<string name="gps_disabled">El GPS ta desactiváu nel preséu. ¿Quiés activalu?</string>
|
||||
<string name="enable_gps">Activar GPS</string>
|
||||
<string name="contributions_subtitle_zero">Inda nun hai xubes</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -376,7 +376,6 @@
|
|||
<string name="preference_author_name_toggle">Usar nome d\'autor personalizáu</string>
|
||||
<string name="preference_author_name_toggle_summary">Usar un nome d\'autor personalizáu en cuenta del to nome d\'usuariu al xubir semeyes</string>
|
||||
<string name="preference_author_name">Nome d\'autor personalizáu</string>
|
||||
<string name="preference_author_name_summary">El nome d\'autor personalizáu pa usar nes xubíes en cuenta del to nome d\'usuariu</string>
|
||||
<string name="contributions_fragment">Collaboraciones</string>
|
||||
<string name="nearby_fragment">Cercanu</string>
|
||||
<string name="notifications">Avisos</string>
|
||||
|
|
|
|||
|
|
@ -214,7 +214,6 @@
|
|||
<string name="preference_author_name_toggle">Използване на персонализирано авторско име</string>
|
||||
<string name="preference_author_name_toggle_summary">При качването използвайте персонализирано авторско име вместо потребителското си име</string>
|
||||
<string name="preference_author_name">Персонализирано авторско име</string>
|
||||
<string name="preference_author_name_summary">Персонализираното авторско име, което ще се използва вместо потребителското ви име при качване</string>
|
||||
<string name="archived_notifications">Известия (архивирани)</string>
|
||||
<string name="list_sheet">Списък</string>
|
||||
<string name="next">Следваща</string>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@
|
|||
<string name="gps_disabled">GPS আপনার ডিভাইসে অক্ষম করা আছে। আপনি কি এটি সক্ষম করতে চান?</string>
|
||||
<string name="enable_gps">GPS সক্রিয় করুন</string>
|
||||
<string name="contributions_subtitle_zero">এখনো কোন আপলোড নেই</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">Oc\'h echuiñ enporzhiadenn %1$s</string>
|
||||
<string name="upload_failed_notification_title">C\'hwitet en deus enporzhiañ %1$s</string>
|
||||
<string name="upload_failed_notification_subtitle">Pouezit evit diskwel</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">%1$d restr o vezañ karget</item>
|
||||
<item quantity="other">%1$d restr o vezañ karget</item>
|
||||
</plurals>
|
||||
|
|
@ -74,16 +74,16 @@
|
|||
<string name="gps_disabled">Diwerededkaet eo ar GPS war hoc\'h ardivink.\nHa c\'hoant ho peus da weredekaat anezhañ ?</string>
|
||||
<string name="enable_gps">Gweredekaat ar GPS</string>
|
||||
<string name="contributions_subtitle_zero">Enporzhiadenn ebet c\'hoazh !</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
</plurals>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<plurals name="starting_multiple_uploads" fuzzy="true">
|
||||
<item quantity="one">%1$d bellgargadenn loc\'het</item>
|
||||
<item quantity="other">%1$d pellgargadennoù loc\'het</item>
|
||||
</plurals>
|
||||
<plurals name="multiple_uploads_title">
|
||||
<plurals name="multiple_uploads_title" fuzzy="true">
|
||||
<item quantity="one">%1$d bellgargadenn</item>
|
||||
<item quantity="other">%1$d pellgargadennoù</item>
|
||||
</plurals>
|
||||
|
|
@ -107,7 +107,7 @@
|
|||
<string name="no_uploads_yet">N\'ho peus ket enporzhiet skeudennoù c\'hoazh.</string>
|
||||
<string name="menu_retry_upload">Klask en-dro</string>
|
||||
<string name="menu_cancel_upload">Nullañ</string>
|
||||
<plurals name="share_license_summary">
|
||||
<plurals name="share_license_summary" fuzzy="true">
|
||||
<item quantity="one">gant an aotre-implijout %1$s e vo ar skeudenn-mañ</item>
|
||||
<item quantity="other">gant an aotreoù-implijout %1$s e vo ar skeudenn-mañ</item>
|
||||
</plurals>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">Postavljanje datoteke %1$s završeno</string>
|
||||
<string name="upload_failed_notification_title">Postavljanje datoteke %1$s nije uspjelo</string>
|
||||
<string name="upload_failed_notification_subtitle">Dodirnite da biste vidjeli</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">postavlja se %1$d datoteka</item>
|
||||
<item quantity="other">postavlja se %1$d datoteka</item>
|
||||
</plurals>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
<string name="gps_disabled">El vostre dispositiu no té el GPS habilitat. El voleu habilitar?</string>
|
||||
<string name="enable_gps">Habilita el GPS</string>
|
||||
<string name="contributions_subtitle_zero">Encara no hi ha cap càrrega</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -353,7 +353,6 @@
|
|||
<string name="preference_author_name_toggle">Utilitza un nom d\'autor personalitzat</string>
|
||||
<string name="preference_author_name_toggle_summary">Utilitza un nom d\'autor personalitzat en comptes del vostre nom d\'usuari a l\'hora de pujar fotos</string>
|
||||
<string name="preference_author_name">Nom de l\'autor personalitzat</string>
|
||||
<string name="preference_author_name_summary">El nom d\'autor personalitzat que s\'utilitzarà en comptes del vostre nom d\'usuari en les pujades</string>
|
||||
<string name="contributions_fragment">Contribucions</string>
|
||||
<string name="nearby_fragment">A prop</string>
|
||||
<string name="notifications">Notificacions</string>
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">Dokončení nahrávání souboru %1$s</string>
|
||||
<string name="upload_failed_notification_title">Načítání souboru %1$s se nezdařilo</string>
|
||||
<string name="upload_failed_notification_subtitle">Klepnutím zobrazíte</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">%1$d soubor se nahrává</item>
|
||||
<item quantity="other">%1$d souborů se nahrává</item>
|
||||
</plurals>
|
||||
|
|
@ -88,16 +88,16 @@
|
|||
<string name="gps_disabled">GPS ve vašem zařízení není povoleno. Chtěli byste ho spustit?</string>
|
||||
<string name="enable_gps">Spustit GPS</string>
|
||||
<string name="contributions_subtitle_zero">Žádné nahrané soubory</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
</plurals>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<plurals name="starting_multiple_uploads" fuzzy="true">
|
||||
<item quantity="one">Spouští se nahrávání %1$d souboru</item>
|
||||
<item quantity="other">Spouští se nahrávání %1$d souborů</item>
|
||||
</plurals>
|
||||
<plurals name="multiple_uploads_title">
|
||||
<plurals name="multiple_uploads_title" fuzzy="true">
|
||||
<item quantity="one">%1$d nahrávání</item>
|
||||
<item quantity="other">%1$d nahrávání</item>
|
||||
</plurals>
|
||||
|
|
@ -122,7 +122,7 @@
|
|||
<string name="no_uploads_yet">Zatím jste nenahrál žádné fotky.</string>
|
||||
<string name="menu_retry_upload">Opakovat</string>
|
||||
<string name="menu_cancel_upload">Zrušit</string>
|
||||
<plurals name="share_license_summary">
|
||||
<plurals name="share_license_summary" fuzzy="true">
|
||||
<item quantity="one">Tento obrázek bude zveřejněn pod licencí %1$s</item>
|
||||
<item quantity="other">Tyto obrázky budou zveřejněny pod licencí %1$s</item>
|
||||
</plurals>
|
||||
|
|
@ -132,7 +132,7 @@
|
|||
<string name="use_previous">Použít předchozí název a popis</string>
|
||||
<string name="allow_gps">Automaticky získat aktuální polohu</string>
|
||||
<string name="allow_gps_summary">Nabídnout kategorie na základě aktuální polohy (pokud není obrázek opatřen souřadnicemi). Varování: Tímto krokem poskytujete svou současnou polohu.</string>
|
||||
<string name="preference_theme" fuzzy="true">Noční režim</string>
|
||||
<string name="preference_theme">Vzhled</string>
|
||||
<string name="license_name_cc_by_sa_four">Uveďte autora-Zachovejte licenci 4.0</string>
|
||||
<string name="license_name_cc_by_four">Uveďte autora 4.0</string>
|
||||
<string name="license_name_cc_by_sa"> Uveďte autora-Zachovejte licenci 3.0</string>
|
||||
|
|
@ -392,7 +392,6 @@
|
|||
<string name="preference_author_name_toggle">Použít vlastní název autora</string>
|
||||
<string name="preference_author_name_toggle_summary">Při nahrávání fotografií používejte vlastní jméno autora namísto uživatelského jména</string>
|
||||
<string name="preference_author_name">Vlastní název autora</string>
|
||||
<string name="preference_author_name_summary">Vlastní název autora, který chcete používat místo uživatelského jména v nahrávání</string>
|
||||
<string name="contributions_fragment">Příspěvky</string>
|
||||
<string name="nearby_fragment">Poblíž</string>
|
||||
<string name="notifications">Upozornění</string>
|
||||
|
|
@ -411,7 +410,7 @@
|
|||
<string name="submit">Odeslat</string>
|
||||
<string name="upload_title_duplicate">Soubor s názvem %1$s již existuje. Opravdu chcete pokračovat?</string>
|
||||
<string name="map_application_missing">Na vašem zařízení nebyla nalezena žádná kompatibilní aplikace poskytující mapy. Pro použití této funkce nainstalujte aplikaci poskytující mapy.</string>
|
||||
<plurals name="upload_count_title">
|
||||
<plurals name="upload_count_title" fuzzy="true">
|
||||
<item quantity="one">%1$d nahrání</item>
|
||||
<item quantity="other">%1$d nahrání</item>
|
||||
</plurals>
|
||||
|
|
@ -501,7 +500,7 @@
|
|||
<string name="review_thanks_no_button_text">Další soubor</string>
|
||||
<string name="skip_image_explanation">Kliknutím na toto tlačítko se zobrazí další nedávno nahraný obrázek z Wikimedia Commons</string>
|
||||
<string name="review_image_explanation">Můžete kontrolovat obrázky a zlepšit kvalitu Wikimedia Commons.\n\nTři aspekty, které se posuzují:\n - Je tento obrázek v pořádku?\n - Dodržuje tento obrázok pravidla autorských práv?\n - Je tento obrázek správně kategorizován?\n\nPokud je vše v pořádku, autorovi můžete poděkovat.</string>
|
||||
<plurals name="receiving_shared_content">
|
||||
<plurals name="receiving_shared_content" fuzzy="true">
|
||||
<item quantity="one">Probíhá příjem sdíleného obsahu. Zpracování obrázku může chvíli trvat v závislosti na velikosti obrázku a vašem zařízení</item>
|
||||
<item quantity="other">Probíhá příjem sdíleného obsahu. Zpracování obrázků může chvíli trvat v závislosti na velikosti obrázků a vašem zařízení</item>
|
||||
</plurals>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">Yn gorffen uwchlwytho %1$s</string>
|
||||
<string name="upload_failed_notification_title">Methwyd uwchlwytho %1$s</string>
|
||||
<string name="upload_failed_notification_subtitle">Tapiwch i weld</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">%1$d ffeil yn uwchlwytho</item>
|
||||
<item quantity="other">%1$d ffeil yn uwchlwytho</item>
|
||||
</plurals>
|
||||
|
|
@ -72,16 +72,16 @@
|
|||
<string name="gps_disabled">Ataliwyd GPS ar eich dyfais. Ydych chi am ei droi\'n weithredol?</string>
|
||||
<string name="enable_gps">Gweithredu\'r GPS</string>
|
||||
<string name="contributions_subtitle_zero">Heb uwchlwytho eto</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
</plurals>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<plurals name="starting_multiple_uploads" fuzzy="true">
|
||||
<item quantity="one">Cychwyn %1$d uwchlwythiad</item>
|
||||
<item quantity="other">Cychwyn uwchlwytho %1$d ffeil</item>
|
||||
</plurals>
|
||||
<plurals name="multiple_uploads_title">
|
||||
<plurals name="multiple_uploads_title" fuzzy="true">
|
||||
<item quantity="one">%1$d uwchlwythiad</item>
|
||||
<item quantity="other">%1$d uwchlwythiad</item>
|
||||
</plurals>
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
<string name="no_uploads_yet">Nid ydych wedi uwchlwytho ffotograffau eto.</string>
|
||||
<string name="menu_retry_upload">Ailgynnig</string>
|
||||
<string name="menu_cancel_upload">Diddymer</string>
|
||||
<plurals name="share_license_summary">
|
||||
<plurals name="share_license_summary" fuzzy="true">
|
||||
<item quantity="one">Caiff y ddelwedd hon ei thrwyddedu yn ôl termau\'r drwydded %1$s</item>
|
||||
<item quantity="other">Caiff y delweddau hyn eu trwyddedu dan %1$s</item>
|
||||
</plurals>
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@
|
|||
<string name="use_previous">Brug forrige titel og beskrivelse</string>
|
||||
<string name="allow_gps">Hent automatisk nuværende placering</string>
|
||||
<string name="allow_gps_summary">Henter den nuværende placering for at tilbyde kategoriforslag hvis billedet ikke er geografisk mærket og geomærker billedet. Advarsel: Dette vil afsløre din nuværende placering.</string>
|
||||
<string name="preference_theme" fuzzy="true">Nat-tilstand</string>
|
||||
<string name="preference_theme">Tema</string>
|
||||
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>
|
||||
<string name="license_name_cc_by_four"> Attribution 4.0</string>
|
||||
<string name="license_name_cc_by_sa"> Attribution-ShareAlike 3.0</string>
|
||||
|
|
@ -385,4 +385,8 @@
|
|||
<string name="place_state_exists">Findes</string>
|
||||
<string name="place_state_needs_photo">Mangler et billede</string>
|
||||
<string name="nearby_search_hint">Bro, museum, hotel, osv.</string>
|
||||
<string name="title_app_shortcut_bookmark">Bogmærker</string>
|
||||
<string name="theme_default_name">Standard</string>
|
||||
<string name="theme_dark_name">Mørkt</string>
|
||||
<string name="theme_light_name">Lyst</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
<string name="gps_disabled">GPS ist auf deinem Gerät deaktiviert. Möchtest du es aktivieren?</string>
|
||||
<string name="enable_gps">GPS aktivieren</string>
|
||||
<string name="contributions_subtitle_zero">Noch keine hochgeladenen Dateien</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">Eine hochgeladene Datei</item>
|
||||
<item quantity="other">%1$d hochgeladene Dateien</item>
|
||||
|
|
@ -385,7 +385,6 @@
|
|||
<string name="preference_author_name_toggle">Benutzerdefinierten Namen des Autors verwenden</string>
|
||||
<string name="preference_author_name_toggle_summary">Einen benutzerdefinierten Namen des Autors anstelle deines Benutzernamens beim Hochladen von Fotos verwenden</string>
|
||||
<string name="preference_author_name">Benutzerdefinierter Name des Autors</string>
|
||||
<string name="preference_author_name_summary">Der benutzerdefinierte Name des Autors, der anstelle deines Benutzernamens in den hochgeladenen Dateien erscheinen soll.</string>
|
||||
<string name="contributions_fragment">Beiträge</string>
|
||||
<string name="nearby_fragment">In der Nähe</string>
|
||||
<string name="notifications">Benachrichtigungen</string>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
<string name="gps_disabled">Το GPS στην συσκευή είναι απενεργοποιημένο. Θέλετε να το ενεργοποιήσετε;</string>
|
||||
<string name="enable_gps"> Ενεργοποιήσετε το GPS</string>
|
||||
<string name="contributions_subtitle_zero">Δεν έχουν ακόμη επιφορτωθεί αρχεία</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">Δεν υπάρχουν φορτώσεις ακόμη</item>
|
||||
<item quantity="one">%1$d φόρτωση</item>
|
||||
<item quantity="other">%1$d φορτώσεις</item>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
<string name="gps_disabled">GPS estas malŝaltita en via aparato. Ĉu vi volas ŝalti ĝin?</string>
|
||||
<string name="enable_gps">Ŝalti la GPS</string>
|
||||
<string name="contributions_subtitle_zero">Neniuj alŝutaĵoj ĝis nun!</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -378,7 +378,6 @@
|
|||
<string name="preference_author_name_toggle">Uzi laŭmendan aŭtoran nomon</string>
|
||||
<string name="preference_author_name_toggle_summary">Uzi laŭmendan aŭtoran nomon anstataŭ via uzantnomon por alŝutado de fotoj</string>
|
||||
<string name="preference_author_name">Laŭmenda aŭtora nomo</string>
|
||||
<string name="preference_author_name_summary">La laŭmenda aŭtora nomo uzota anstataŭ via uzantnomo por alŝutoj</string>
|
||||
<string name="contributions_fragment">Kontribuoj</string>
|
||||
<string name="nearby_fragment">Apude</string>
|
||||
<string name="notifications">Sciigoj</string>
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@
|
|||
<string name="gps_disabled">El GPS está desactivado en tu dispositivo. ¿Quieres activarlo?</string>
|
||||
<string name="enable_gps">Activar GPS</string>
|
||||
<string name="contributions_subtitle_zero">No hay subidas aún</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -401,7 +401,6 @@
|
|||
<string name="preference_author_name_toggle">Usar nombre de autor personalizado</string>
|
||||
<string name="preference_author_name_toggle_summary">Usar un nombre de autor personalizado en vez de tu nombre de usuario al subir fotos</string>
|
||||
<string name="preference_author_name">Nombre de autor personalizado</string>
|
||||
<string name="preference_author_name_summary">El nombre de autor personalizado para usar en las subidas en vez de tu nombre de usuario</string>
|
||||
<string name="contributions_fragment">Contribuciones</string>
|
||||
<string name="nearby_fragment">Cercanos</string>
|
||||
<string name="notifications">Notificaciones</string>
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@
|
|||
<string name="gps_disabled">GPSa desgaituta dago gailu honetan. Gaitu nahi duzu?</string>
|
||||
<string name="enable_gps">GPSa gaitu</string>
|
||||
<string name="contributions_subtitle_zero">Oraindik ez da ezer igo</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">igoera (%1$d)</item>
|
||||
<item quantity="other">(%1$d) igoera</item>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
<string name="gps_disabled">GPS ei ole käytössä. Haluatko ottaa sen käyttöön?</string>
|
||||
<string name="enable_gps">Ota GPS käyttöön</string>
|
||||
<string name="contributions_subtitle_zero">Ei tallennuksia vielä</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -287,14 +287,14 @@
|
|||
<string name="nearby_wikidata">Wikidata</string>
|
||||
<string name="nearby_wikipedia">Wikipedia</string>
|
||||
<string name="nearby_commons">Commons</string>
|
||||
<string name="about_rate_us" fuzzy="true"><u>Arvostele meidät</u></string>
|
||||
<string name="about_faq" fuzzy="true"><u>UKK</u></string>
|
||||
<string name="about_rate_us">Arvostele meidät</string>
|
||||
<string name="about_faq">UKK</string>
|
||||
<string name="welcome_skip_button">Ohita opetus</string>
|
||||
<string name="no_internet">Internet ei saatavissa</string>
|
||||
<string name="internet_established">Internet saatavana</string>
|
||||
<string name="error_notifications">Virhe ilmoitusten haussa</string>
|
||||
<string name="no_notifications">Ilmoituksia ei löytynyt</string>
|
||||
<string name="about_translate" fuzzy="true"><u>Käännä</u></string>
|
||||
<string name="about_translate">Käännä</string>
|
||||
<string name="about_translate_title">Kielet</string>
|
||||
<string name="about_translate_message">Valitse kieli, joksi haluaisit kääntää</string>
|
||||
<string name="about_translate_proceed">Jatka</string>
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@
|
|||
<string name="gps_disabled">Le GPS est désactivé sur votre appareil. Voulez-vous l’activer ?</string>
|
||||
<string name="enable_gps">Activer le GPS</string>
|
||||
<string name="contributions_subtitle_zero">Encore aucun téléversement</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -400,7 +400,6 @@
|
|||
<string name="preference_author_name_toggle">Utilisez un nom d\'auteur personnalisé</string>
|
||||
<string name="preference_author_name_toggle_summary">Utilisez un nom d\'auteur personnalisé au lieu de votre nom d\'utilisateur lorsque vous téléversez des photos</string>
|
||||
<string name="preference_author_name">Nom d\'auteur personnalisé</string>
|
||||
<string name="preference_author_name_summary">Le nom d\'auteur personnalisé à utiliser au lieu du nom d\'utilisateur dans vos téléversements</string>
|
||||
<string name="contributions_fragment">Contributions</string>
|
||||
<string name="nearby_fragment">Proche</string>
|
||||
<string name="notifications">Notifications</string>
|
||||
|
|
@ -459,6 +458,7 @@
|
|||
<string name="display_location_permission_title">Afficher l\'autorisation de géolocalisation</string>
|
||||
<string name="display_location_permission_explanation">Demander le droit de géolocaliser quand cela est nécessaire à la fonction de notification de proximité.</string>
|
||||
<string name="achievements_fetch_failed">Un problème est survenu, nous n\'avons pas pu récupérer vos accomplissements</string>
|
||||
<string name="achievements_fetch_failed_ultimate_achievement">Vous avez effectué tellement de contributions que notre système de calcul des accomplissements est débordé. Il s\'agit là de l\'accomplissement ultime.</string>
|
||||
<string name="ends_on">Se termine le:</string>
|
||||
<string name="display_campaigns">Campagnes d\'affichage</string>
|
||||
<string name="display_campaigns_explanation">Voir les campagnes en cours</string>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
<string name="gps_disabled">O GPS está desactivado no seu dispositivo. Quere activalo?</string>
|
||||
<string name="enable_gps">Activar GPS</string>
|
||||
<string name="contributions_subtitle_zero">Aínda non hai subas</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -382,7 +382,6 @@
|
|||
<string name="preference_author_name_toggle">Usar nome personalizado de autor</string>
|
||||
<string name="preference_author_name_toggle_summary">Usar un nome personalizado de autor en lugar do seu nome de usuario á hora de cargar imaxes</string>
|
||||
<string name="preference_author_name">Nome personalizado de autor</string>
|
||||
<string name="preference_author_name_summary">O nome personalizado de autor a utilizar nas cargas en lugar do seu nome de usuario</string>
|
||||
<string name="contributions_fragment">Contribucións</string>
|
||||
<string name="nearby_fragment">Preto</string>
|
||||
<string name="notifications">Notificacións</string>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
<string name="gps_disabled">आपके डिवाइस में जीपीएस अक्षम है। क्या आप इसे सक्षम करना चाहेंगे?</string>
|
||||
<string name="enable_gps">जीपीएस सक्षम करें</string>
|
||||
<string name="contributions_subtitle_zero">अभी तक कोई अपलोड नहीं</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">Završeno postavljanje %1$s</string>
|
||||
<string name="upload_failed_notification_title">Postavljanje %1$s neuspješno</string>
|
||||
<string name="upload_failed_notification_subtitle">Dodirnite da biste vidjeli</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">Postavlja se %1$d datoteka</item>
|
||||
<item quantity="other">Postavljaju se %1$d datoteke</item>
|
||||
</plurals>
|
||||
|
|
@ -75,11 +75,11 @@
|
|||
<item quantity="one">%1$d postavljena datoteka</item>
|
||||
<item quantity="other">%1$d postavljene datoteke</item>
|
||||
</plurals>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<plurals name="starting_multiple_uploads" fuzzy="true">
|
||||
<item quantity="one">Započeto %1$d postavljanje</item>
|
||||
<item quantity="other">Započeta %1$d postavljanja</item>
|
||||
</plurals>
|
||||
<plurals name="multiple_uploads_title">
|
||||
<plurals name="multiple_uploads_title" fuzzy="true">
|
||||
<item quantity="one">%1$d postavljanje</item>
|
||||
<item quantity="other">%1$d postavljanja</item>
|
||||
</plurals>
|
||||
|
|
@ -280,10 +280,19 @@
|
|||
<string name="level">Razina</string>
|
||||
<string name="error_occurred">Došlo je do pogrješke!</string>
|
||||
<string name="notifications_channel_name_all">Obavijesti Zajedničkoga poslužitelja</string>
|
||||
<string name="achievements_fetch_failed_ultimate_achievement">Toliko ste pridonijeli projektu da se naš sustav za računanje postignuća ne može nositi s time. To je vrhunsko postignuće.</string>
|
||||
<string name="error_processing_image">Došlo je do pogrješke tijekom obradbe slike. Molimo Vas, pokušajte ponovo!</string>
|
||||
<string name="please_wait">Molimo Vas, pričekajte ...</string>
|
||||
<string name="skip_image">Preskoči ovu sliku</string>
|
||||
<string name="default_description_language">Zadani jezik za opis</string>
|
||||
<string name="title_app_shortcut_explore">Istraži</string>
|
||||
<string name="title_app_shortcut_bookmark">Oznake</string>
|
||||
<string name="title_app_shortcut_setting">Postavke</string>
|
||||
<string name="remove_bookmark">Uklonjeno iz oznaka</string>
|
||||
<string name="add_bookmark">Dodano u oznake</string>
|
||||
<string name="wallpaper_set_unsuccessfully">Nešto je pošlo po zlu. Ne možemo postaviti pozadinu</string>
|
||||
<string name="setting_wallpaper_dialog_title">Postavi kao pozadinu</string>
|
||||
<string name="setting_wallpaper_dialog_message">Postavljanje pozadine. Molimo, pričekajte...</string>
|
||||
<string name="theme_default_name">Zadano</string>
|
||||
<string name="theme_dark_name">Tamno</string>
|
||||
<string name="theme_light_name">Svijetlo</string>
|
||||
|
|
|
|||
|
|
@ -374,7 +374,6 @@
|
|||
<string name="preference_author_name_toggle">Gunakan nama lain</string>
|
||||
<string name="preference_author_name_toggle_summary">Gunakan nama lain, bukan nama pengguna Anda saat mengunggah foto</string>
|
||||
<string name="preference_author_name">Nama lain</string>
|
||||
<string name="preference_author_name_summary">Nama lain untuk digunakan sebagai ganti nama pengguna Anda pada unggahan</string>
|
||||
<string name="contributions_fragment">Kontribusi</string>
|
||||
<string name="nearby_fragment">Sekitar</string>
|
||||
<string name="notifications">Pemberitahuan</string>
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
<string name="gps_disabled">GPS er óvirkt í tækinu þínu. Viltu virkja það?</string>
|
||||
<string name="enable_gps">Virkja GPS</string>
|
||||
<string name="contributions_subtitle_zero">Engar innsendingar ennþá</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -369,7 +369,6 @@
|
|||
<string name="preference_author_name_toggle">Nota sérsniðið nafn höfundar</string>
|
||||
<string name="preference_author_name_toggle_summary">Nota sérsniðið nafn höfundar í stað notandanafnsins þíns þegar ljósmyndir eru sendar inn</string>
|
||||
<string name="preference_author_name">Sérsniðið nafn höfundar</string>
|
||||
<string name="preference_author_name_summary">Sérsniðið nafn höfundar sem nota á í stað notandanafnsins þíns þegar myndir eru sendar inn</string>
|
||||
<string name="contributions_fragment">Framlög</string>
|
||||
<string name="nearby_fragment">Nálægt</string>
|
||||
<string name="notifications">Tilkynningar</string>
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@
|
|||
<string name="gps_disabled">Il GPS è disabilitato nel dispositivo. Vuoi attivarlo?</string>
|
||||
<string name="enable_gps">Attiva GPS</string>
|
||||
<string name="contributions_subtitle_zero">Non è stato ancora caricato niente</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -381,7 +381,6 @@
|
|||
<string name="preference_author_name_toggle">Usa il nome dell\'autore personalizzato</string>
|
||||
<string name="preference_author_name_toggle_summary">Utilizza un nome dell\'autore personalizzato al posto del tuo nome utente durante il caricamento delle foto</string>
|
||||
<string name="preference_author_name">Nome dell\'autore personalizzato</string>
|
||||
<string name="preference_author_name_summary">Il nome dell\'autore personalizzato da usare al posto del tuo nome utente nei caricamenti</string>
|
||||
<string name="contributions_fragment">Contributi</string>
|
||||
<string name="nearby_fragment">Nelle vicinanze</string>
|
||||
<string name="notifications">Notifiche</string>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
<string name="upload_progress_notification_title_finishing">העלאת %1$s מסתיימת</string>
|
||||
<string name="upload_failed_notification_title">העלאת %1$s נכשלה</string>
|
||||
<string name="upload_failed_notification_subtitle">לחץ כדי להציג</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<plurals name="uploads_pending_notification_indicator" fuzzy="true">
|
||||
<item quantity="one">מועלה קובץ אחד</item>
|
||||
<item quantity="other">מועלים %1$d קבצים</item>
|
||||
</plurals>
|
||||
|
|
@ -86,16 +86,16 @@
|
|||
<string name="gps_disabled">ה־GPS במכשיר שלך אינו מופעל. האם להפעיל אותו?</string>
|
||||
<string name="enable_gps">הפעלת GPS</string>
|
||||
<string name="contributions_subtitle_zero">לא הועלה עדיין שום דבר</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
</plurals>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<plurals name="starting_multiple_uploads" fuzzy="true">
|
||||
<item quantity="one">התחלת העלאה</item>
|
||||
<item quantity="other">התחלת 1$d% העלאות</item>
|
||||
</plurals>
|
||||
<plurals name="multiple_uploads_title">
|
||||
<plurals name="multiple_uploads_title" fuzzy="true">
|
||||
<item quantity="one">העלאה אחת</item>
|
||||
<item quantity="other">%1$d העלאות</item>
|
||||
</plurals>
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
<string name="no_uploads_yet">עדיין לא העלית תמונות.</string>
|
||||
<string name="menu_retry_upload">לנסות שוב</string>
|
||||
<string name="menu_cancel_upload">ביטול</string>
|
||||
<plurals name="share_license_summary">
|
||||
<plurals name="share_license_summary" fuzzy="true">
|
||||
<item quantity="one">התמונה הזאת תפורסם ברישיון %1$s</item>
|
||||
<item quantity="other">התמונות האלה תפורסמנה ברישיון %1$s</item>
|
||||
</plurals>
|
||||
|
|
@ -390,7 +390,6 @@
|
|||
<string name="preference_author_name_toggle">שימוש בשם יוצר מותאם אישית</string>
|
||||
<string name="preference_author_name_toggle_summary">שימוש בשם יוצר בהתאמה אישית במקום בשם המשתמש שלך בעת העלאת תמונות</string>
|
||||
<string name="preference_author_name">שם יוצר מותאם אישית</string>
|
||||
<string name="preference_author_name_summary">שם יוצר מותאם אישית לשימוש במקום שם המשתמש שלך להעלאות</string>
|
||||
<string name="contributions_fragment">תרומות</string>
|
||||
<string name="nearby_fragment">בסביבה</string>
|
||||
<string name="notifications">התראות</string>
|
||||
|
|
@ -409,7 +408,7 @@
|
|||
<string name="submit">שליחה</string>
|
||||
<string name="upload_title_duplicate">כבר קיים קובץ בשם %1$s. להמשיך?</string>
|
||||
<string name="map_application_missing">לא נמצא יישום מפה תואם במכשיר שלך. נא להתקין יישום מפה כדי להשתמש בתכונה זו.</string>
|
||||
<plurals name="upload_count_title">
|
||||
<plurals name="upload_count_title" fuzzy="true">
|
||||
<item quantity="one">העלאה אחת</item>
|
||||
<item quantity="other">%1$d העלאות</item>
|
||||
</plurals>
|
||||
|
|
@ -500,7 +499,7 @@
|
|||
<string name="review_thanks_no_button_text">התמונה הבאה</string>
|
||||
<string name="skip_image_explanation">לחיצה על הכפתור הזה תציג בפניך תמונה נוספת שנוספה לאחרונה לוויקישיתוף</string>
|
||||
<string name="review_image_explanation">ניתן לסקור את התמונות ולשפר את האיכות של ויקישיתוף.\nארבע יסודות הסקירה הם:\n- האם התמונה קשורה לנושא?\n- האם התמונה תואמת לכללי זכויות היוצרים?\n- האם התמונה נמצאת בקטגוריה הנכונה?\n- אם הכול בסדר, ניתן גם להודות למי שתרם אותה.</string>
|
||||
<plurals name="receiving_shared_content">
|
||||
<plurals name="receiving_shared_content" fuzzy="true">
|
||||
<item quantity="one">מתקבל תוכן שיתופי. עיבוד התמונה עשוי לארוך זמן מה כתלות בגודל התמונה והמכשיר שלך</item>
|
||||
<item quantity="other">מתקבל תוכן שיתופי. עיבוד התמונות עשוי לארוך זמן מה כתלות בגודל התמונות והמכשיר שלך</item>
|
||||
</plurals>
|
||||
|
|
@ -582,4 +581,7 @@
|
|||
<string name="theme_default_name">ברירת מחדל</string>
|
||||
<string name="theme_dark_name">כהה</string>
|
||||
<string name="theme_light_name">בהירה</string>
|
||||
<string name="cannot_open_location_settings">פתיחת הגדרות המיקום נכשלה. נא להפעיל את איתור המיקום ידנית</string>
|
||||
<string name="recommend_high_accuracy_mode">לתוצאות המיטביות יש להשתמש במצב דיוק גבוה.</string>
|
||||
<string name="ask_to_turn_location_on">להפעיל מיקום?</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -386,7 +386,6 @@
|
|||
<string name="preference_author_name_toggle">専用の投稿者名を使う</string>
|
||||
<string name="preference_author_name_toggle_summary">画像の投稿にはユーザー名ではなく専用の投稿者名を使う</string>
|
||||
<string name="preference_author_name">投稿専用の名前</string>
|
||||
<string name="preference_author_name_summary">ユーザー名ではなく、投稿専用の投稿者名</string>
|
||||
<string name="contributions_fragment">投稿記録</string>
|
||||
<string name="nearby_fragment">付近</string>
|
||||
<string name="notifications">お知らせ</string>
|
||||
|
|
|
|||
|
|
@ -371,7 +371,6 @@
|
|||
<string name="preference_author_name_toggle">사용자 지정 저자 이름을 사용합니다</string>
|
||||
<string name="preference_author_name_toggle_summary">사진을 업로드하는 중에 사용자 이름 대신 사용자 지정 저자 이름을 사용합니다</string>
|
||||
<string name="preference_author_name">저자 이름 사용자 지정</string>
|
||||
<string name="preference_author_name_summary">업로드할 때 사용자 이름 대신 사용할 사용자 지정 저자 이름</string>
|
||||
<string name="contributions_fragment">기여</string>
|
||||
<string name="nearby_fragment">근처</string>
|
||||
<string name="notifications">알림</string>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
<string name="gps_disabled">GPS ass op Ärem Apparat ausgeschalt. Wëllt Dir en aktivéieren?</string>
|
||||
<string name="enable_gps">GPS aktivéieren</string>
|
||||
<string name="contributions_subtitle_zero">Nach keng eropgeluede Fichieren</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">N@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -134,7 +134,7 @@
|
|||
<string name="license_name_cc_zero">CC Zero</string>
|
||||
<string name="tutorial_1_text">Wikimedia Commons späichert déi meescht Biller, déi op Wikipedia benotzt ginn.</string>
|
||||
<string name="tutorial_1_subtext">Är Biller hëllefe Leit op der ganzer Welt ze forméieren!</string>
|
||||
<string name="tutorial_2_text">Lued wgl. Biller erop déi komplett vun Iech opgeholl oder gemaacht goufen:</string>
|
||||
<string name="tutorial_2_text">Luet wgl. nëmme Biller erop déi komplett vun Iech opgeholl oder gemaach goufen:</string>
|
||||
<string name="tutorial_2_subtext_1">Natierlech Objeten (Blummen, Déieren, Bierger)</string>
|
||||
<string name="tutorial_2_subtext_2">Nëtzlech Objeten (Vëloen, Garen)</string>
|
||||
<string name="tutorial_2_subtext_3">Berühmt Leit (Äre Buergermeeschter, Olympioniken deenen Dir begéint sidd)</string>
|
||||
|
|
@ -222,7 +222,7 @@
|
|||
<string name="upload_problem_image_duplicate">Bild ass schonn op Commons.</string>
|
||||
<string name="upload_problem_different_geolocation">Dëst Bild gouf op enger anerer Plaz gemaach.</string>
|
||||
<string name="upload_problem_do_you_continue">Wëllt Dir dëst Bild nach ëmmer eroplueden?</string>
|
||||
<string name="internet_downloaded">Lued wgl. nëmme Biller erop déi Dir selwer opgeholl hutt. Lued keng Biller erop déi Dir aus dem Internet erofgelueden hutt.</string>
|
||||
<string name="internet_downloaded">Luet wgl. nëmme Biller erop déi Dir selwer opgeholl hutt. Luet keng Biller erop déi Dir aus dem Internet erofgelueden hutt.</string>
|
||||
<string name="give_permission">Autorisatioun ginn</string>
|
||||
<string name="use_external_storage">Externe Späicher benotzen</string>
|
||||
<string name="use_external_storage_summary">Biller späicheren déi mat der in-app Kamera vun Ärem Apparat gemaach goufen</string>
|
||||
|
|
@ -322,6 +322,7 @@
|
|||
<string name="yes_submit">Jo, schécken</string>
|
||||
<string name="no_go_back">Neen, zréck goen</string>
|
||||
<string name="never_ask_again">Dëst ni méi froen</string>
|
||||
<string name="achievements_fetch_failed_ultimate_achievement">Dir hutt esou vill Kontributioune gemaach datt eise Berechnungssystem iwwerfuerdert ass. Dëst ass déi bescht Leeschtung.</string>
|
||||
<string name="nominate_for_deletion_done">Fäerdeg</string>
|
||||
<string name="notsure">Net sécher</string>
|
||||
<string name="send_thank_send">Merci schécken</string>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
<string name="gps_disabled">GPS е исклучен на вашиот уред. Дали сакате да го вклучите?</string>
|
||||
<string name="enable_gps">Вклучи GPS</string>
|
||||
<string name="contributions_subtitle_zero">Сè уште нема подигања</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -377,7 +377,6 @@
|
|||
<string name="preference_author_name_toggle">Употреби прилагодено авторско име</string>
|
||||
<string name="preference_author_name_toggle_summary">При подигањето користете прилагодено авторско име наместо вашето корисничко име</string>
|
||||
<string name="preference_author_name">Прилагодено авторско име</string>
|
||||
<string name="preference_author_name_summary">Прилагоденото авторско име што ќе се користи место вашето корисничко име при подигања</string>
|
||||
<string name="contributions_fragment">Придонеси</string>
|
||||
<string name="nearby_fragment">Во близина</string>
|
||||
<string name="notifications">Известувања</string>
|
||||
|
|
@ -569,4 +568,8 @@
|
|||
<string name="theme_default_name">По основно</string>
|
||||
<string name="theme_dark_name">Темен</string>
|
||||
<string name="theme_light_name">Светол</string>
|
||||
<string name="cannot_open_location_settings">Не можев да ги отворам поставките за местоположба. Вклучете ја местоположбата рачно.</string>
|
||||
<string name="recommend_high_accuracy_mode">За да добиете најдобар исход, изберете го режимот на висока уточнетост.</string>
|
||||
<string name="ask_to_turn_location_on">Да ја вклучам местоположбата?</string>
|
||||
<string name="nearby_needs_location">„Во близина“ бара местоположба за да работи</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
<string name="gps_disabled">GPS er slått av på denne enheten. Ønsker du å slå den på?</string>
|
||||
<string name="enable_gps">Slå på GPS</string>
|
||||
<string name="contributions_subtitle_zero">Ingen opplastinger ennå</string>
|
||||
<plurals name="contributions_subtitle">
|
||||
<plurals name="contributions_subtitle" fuzzy="true">
|
||||
<item quantity="zero">\@string/contributions_subtitle_zero</item>
|
||||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
|
|
@ -384,7 +384,6 @@
|
|||
<string name="preference_author_name_toggle">Bruk egendefinert opphavspersonnavn</string>
|
||||
<string name="preference_author_name_toggle_summary">Bruk et egendefinert opphavspersonnavn i stedet for brukernavnet ditt når bilder lastes opp</string>
|
||||
<string name="preference_author_name">Egendefinert opphavspersonnavn</string>
|
||||
<string name="preference_author_name_summary">Det egendefinerte opphavspersonnavnet som skal brukes i stedet for brukernavnet ditt i opplastinger</string>
|
||||
<string name="contributions_fragment">Bidrag</string>
|
||||
<string name="nearby_fragment">I nærheten</string>
|
||||
<string name="notifications">Varsler</string>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue