diff --git a/.travis.yml b/.travis.yml index 20c5bfaee..5e76e09d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,12 +19,13 @@ android: components: - tools - platform-tools - - build-tools-26.0.2 + - build-tools-27.0.0 - extra-google-m2repository - extra-android-m2repository - ${ANDROID_TARGET} - android-25 - android-26 + - android-27 - sys-img-${ANDROID_ABI}-${ANDROID_TARGET} licenses: - 'android-sdk-license-.+' diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 9d7150008..dffa9d28d 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,7 @@ +## Title (required) + +Fixes #{GitHub issue number and title (Please do not forget adding title) } + ## Description (required) Fixes #{GitHub issue number and title} diff --git a/app/build.gradle b/app/build.gradle index 5d37f8f54..d2b9463c1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,6 +69,10 @@ dependencies { testImplementation 'com.nhaarman:mockito-kotlin:1.5.0' testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1' + implementation 'com.caverock:androidsvg:1.2.1' + implementation 'com.github.bumptech.glide:glide:4.7.1' + kapt 'com.github.bumptech.glide:compiler:4.7.1' + androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1' androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION" @@ -117,7 +121,7 @@ android { buildTypes { release { minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient. - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt' } debug { applicationIdSuffix ".debug" diff --git a/app/proguard-glide.txt b/app/proguard-glide.txt new file mode 100644 index 000000000..ef3437660 --- /dev/null +++ b/app/proguard-glide.txt @@ -0,0 +1,9 @@ +-keep public class * implements com.bumptech.glide.module.GlideModule +-keep public class * extends com.bumptech.glide.module.AppGlideModule +-keep public enum com.bumptech.glide.load.ImageHeaderParser$** { + **[] $VALUES; + public *; +} + +# for DexGuard only +-keepresourcexmlelements manifest/application/meta-data@value=GlideModule \ No newline at end of file diff --git a/app/quality.gradle b/app/quality.gradle index 7ea20916a..1afdf0d68 100644 --- a/app/quality.gradle +++ b/app/quality.gradle @@ -18,7 +18,7 @@ task checkstyle(type: Checkstyle) { reports { html { enabled true - destination "${project.buildDir}/reports/checkstyle/checkstyle.html" + destination file("${project.buildDir}/reports/checkstyle/checkstyle.html") } } } @@ -36,10 +36,10 @@ task pmd(type: Pmd) { xml.enabled = false html.enabled = true xml { - destination "${project.buildDir}/reports/pmd/pmd.xml" + destination file("${project.buildDir}/reports/pmd/pmd.xml") } html { - destination "${project.buildDir}/reports/pmd/pmd.html" + destination file("${project.buildDir}/reports/pmd/pmd.html") } } } diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java index d0fb628e3..b19d70a4e 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java @@ -271,11 +271,11 @@ public class LoginActivity extends AccountAuthenticatorActivity { showMessageAndCancelDialog(R.string.login_failed_network); } else if (result.toLowerCase(Locale.getDefault()).contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) { // Matches nosuchuser, nosuchusershort, noname - showMessageAndCancelDialog(R.string.login_failed_username); + showMessageAndCancelDialog(R.string.login_failed_wrong_credentials); emptySensitiveEditFields(); } else if (result.toLowerCase(Locale.getDefault()).contains("wrongpassword".toLowerCase())) { // Matches wrongpassword, wrongpasswordempty - showMessageAndCancelDialog(R.string.login_failed_password); + showMessageAndCancelDialog(R.string.login_failed_wrong_credentials); emptySensitiveEditFields(); } else if (result.toLowerCase(Locale.getDefault()).contains("throttle".toLowerCase())) { // Matches unknown throttle error codes diff --git a/app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java b/app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java new file mode 100644 index 000000000..9087f9501 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java @@ -0,0 +1,36 @@ +package fr.free.nrw.commons.glide; + +import android.support.annotation.NonNull; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.ResourceDecoder; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.SimpleResource; +import com.caverock.androidsvg.SVG; +import com.caverock.androidsvg.SVGParseException; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Decodes an SVG internal representation from an {@link InputStream}. + */ +public class SvgDecoder implements ResourceDecoder { + + @Override + public boolean handles(@NonNull InputStream source, @NonNull Options options) { + // TODO: Can we tell? + return true; + } + + public Resource decode(@NonNull InputStream source, int width, int height, + @NonNull Options options) + throws IOException { + try { + SVG svg = SVG.getFromInputStream(source); + return new SimpleResource<>(svg); + } catch (SVGParseException ex) { + throw new IOException("Cannot load SVG from stream", ex); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/glide/SvgDrawableTranscoder.java b/app/src/main/java/fr/free/nrw/commons/glide/SvgDrawableTranscoder.java new file mode 100644 index 000000000..89910c8fb --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/glide/SvgDrawableTranscoder.java @@ -0,0 +1,28 @@ +package fr.free.nrw.commons.glide; + +import android.graphics.Picture; +import android.graphics.drawable.PictureDrawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.SimpleResource; +import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; +import com.caverock.androidsvg.SVG; + +/** + * Convert the {@link SVG}'s internal representation to an Android-compatible one + * ({@link Picture}). + */ +public class SvgDrawableTranscoder implements ResourceTranscoder { + @Nullable + @Override + public Resource transcode(@NonNull Resource toTranscode, + @NonNull Options options) { + SVG svg = toTranscode.get(); + Picture picture = svg.renderToPicture(); + PictureDrawable drawable = new PictureDrawable(picture); + return new SimpleResource<>(drawable); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/glide/SvgSoftwareLayerSetter.java b/app/src/main/java/fr/free/nrw/commons/glide/SvgSoftwareLayerSetter.java new file mode 100644 index 000000000..66a3bd6bf --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/glide/SvgSoftwareLayerSetter.java @@ -0,0 +1,51 @@ +package fr.free.nrw.commons.glide; + +import android.graphics.drawable.PictureDrawable; +import android.widget.ImageView; + +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.ImageViewTarget; +import com.bumptech.glide.request.target.Target; + +/** + * Listener which updates the {@link ImageView} to be software rendered, because + * {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on + * a hardware backed {@link android.graphics.Canvas Canvas}. + */ +public class SvgSoftwareLayerSetter implements RequestListener { + + /** + * Sets the layer type to none if the load fails + * @param e + * @param model + * @param target + * @param isFirstResource + * @return + */ + @Override + public boolean onLoadFailed(GlideException e, Object model, Target target, + boolean isFirstResource) { + ImageView view = ((ImageViewTarget) target).getView(); + view.setLayerType(ImageView.LAYER_TYPE_NONE, null); + return false; + } + + /** + * Sets the layer type to software when the resource is ready + * @param resource + * @param model + * @param target + * @param dataSource + * @param isFirstResource + * @return + */ + @Override + public boolean onResourceReady(PictureDrawable resource, Object model, + Target target, DataSource dataSource, boolean isFirstResource) { + ImageView view = ((ImageViewTarget) target).getView(); + view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null); + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 9614c4f00..fcfb1f4d9 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -23,9 +23,6 @@ import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -35,6 +32,9 @@ import java.util.Locale; import javax.inject.Inject; import javax.inject.Provider; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import fr.free.nrw.commons.License; import fr.free.nrw.commons.LicenseList; import fr.free.nrw.commons.Media; @@ -56,16 +56,16 @@ import static android.widget.Toast.LENGTH_SHORT; public class MediaDetailFragment extends CommonsDaggerSupportFragment { private boolean editable; - private boolean isFeaturedMedia; + private boolean isCategoryImage; private MediaDetailPagerFragment.MediaDetailProvider detailProvider; private int index; - public static MediaDetailFragment forMedia(int index, boolean editable, boolean isFeaturedMedia) { + public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage) { MediaDetailFragment mf = new MediaDetailFragment(); Bundle state = new Bundle(); state.putBoolean("editable", editable); - state.putBoolean("isFeaturedMedia", isFeaturedMedia); + state.putBoolean("isCategoryImage", isCategoryImage); state.putInt("index", index); state.putInt("listIndex", 0); state.putInt("listTop", 0); @@ -128,7 +128,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { super.onSaveInstanceState(outState); outState.putInt("index", index); outState.putBoolean("editable", editable); - outState.putBoolean("isFeaturedMedia", isFeaturedMedia); + outState.putBoolean("isCategoryImage", isCategoryImage); getScrollPosition(); outState.putInt("listTop", initialListTop); @@ -144,12 +144,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { if (savedInstanceState != null) { editable = savedInstanceState.getBoolean("editable"); - isFeaturedMedia = savedInstanceState.getBoolean("isFeaturedMedia"); + isCategoryImage = savedInstanceState.getBoolean("isCategoryImage"); index = savedInstanceState.getInt("index"); initialListTop = savedInstanceState.getInt("listTop"); } else { editable = getArguments().getBoolean("editable"); - isFeaturedMedia = getArguments().getBoolean("isFeaturedMedia"); + isCategoryImage = getArguments().getBoolean("isCategoryImage"); index = getArguments().getInt("index"); initialListTop = 0; } @@ -161,7 +161,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { ButterKnife.bind(this,view); - if (isFeaturedMedia){ + if (isCategoryImage){ authorLayout.setVisibility(VISIBLE); } else { authorLayout.setVisibility(GONE); @@ -328,7 +328,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { if (!TextUtils.isEmpty(licenseLink(media))) { openWebBrowser(licenseLink(media)); } else { - if(isFeaturedMedia) { + if(isCategoryImage) { Timber.d("Unable to fetch license URL for %s", media.getLicense()); } else { Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT); @@ -503,8 +503,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { if (media.getRequestedDeletion()){ delete.setVisibility(GONE); nominatedForDeletion.setVisibility(VISIBLE); - } - else{ + } else if (!isCategoryImage) { delete.setVisibility(VISIBLE); nominatedForDeletion.setVisibility(GONE); } diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java index 6629d0933..e962bdaf3 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java @@ -444,8 +444,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { .param("notprop", "list") .param("format", "xml") .param("meta", "notifications") -// .param("meta", "notifications") .param("notformat", "model") + .param("notwikis", "wikidatawiki|commonswiki|enwiki") .get() .getNode("/api/query/notifications/list"); } catch (IOException e) { diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java index 17a318e74..6dcfca35d 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java @@ -1,6 +1,7 @@ package fr.free.nrw.commons.notification; -import android.util.Log; +import android.graphics.drawable.PictureDrawable; +import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -8,17 +9,23 @@ import android.widget.ImageView; import android.widget.TextView; import com.borjabravo.readmoretextview.ReadMoreTextView; +import com.bumptech.glide.RequestBuilder; import com.pedrogomez.renderers.Renderer; import butterknife.BindView; import butterknife.ButterKnife; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.glide.SvgSoftwareLayerSetter; + +import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; /** * Created by root on 19.12.2017. */ public class NotificationRenderer extends Renderer { + private RequestBuilder requestBuilder; + @BindView(R.id.title) ReadMoreTextView title; @BindView(R.id.time) TextView time; @BindView(R.id.icon) ImageView icon; @@ -41,23 +48,32 @@ public class NotificationRenderer extends Renderer { protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) { View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false); ButterKnife.bind(this, inflatedView); + requestBuilder = GlideApp.with(inflatedView.getContext()) + .as(PictureDrawable.class) + .error(R.drawable.round_icon_unknown) + .transition(withCrossFade()) + .listener(new SvgSoftwareLayerSetter()); return inflatedView; } @Override public void render() { Notification notification = getContent(); - String str = notification.notificationText.trim(); - str = str.concat(" "); - title.setText(str); + setTitle(notification.notificationText); time.setText(notification.date); - switch (notification.notificationType) { - case THANK_YOU_EDIT: - icon.setImageResource(R.drawable.ic_edit_black_24dp); - break; - default: - icon.setImageResource(R.drawable.round_icon_unknown); - } + requestBuilder.load(notification.iconUrl).into(icon); + } + + /** + * Cleans up the notification text and sets it as the title + * Clean up is required to fix escaped HTML string and extra white spaces at the beginning of the notification + * @param notificationText + */ + private void setTitle(String notificationText) { + notificationText = notificationText.trim().replaceAll("(^\\h*)|(\\h*$)", ""); + notificationText = Html.fromHtml(notificationText).toString(); + notificationText = notificationText.concat(" "); + title.setText(notificationText); } public interface NotificationClicked{ diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java index 68c3add1c..e7c87d3f4 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationUtils.java @@ -16,12 +16,13 @@ import javax.annotation.Nullable; import fr.free.nrw.commons.BuildConfig; import fr.free.nrw.commons.R; -import static fr.free.nrw.commons.notification.NotificationType.THANK_YOU_EDIT; import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN; public class NotificationUtils { private static final String COMMONS_WIKI = "commonswiki"; + private static final String WIKIDATA_WIKI = "wikidatawiki"; + private static final String WIKIPEDIA_WIKI = "enwiki"; public static boolean isCommonsNotification(Node document) { if (document == null || !document.hasAttributes()) { @@ -31,6 +32,32 @@ public class NotificationUtils { return COMMONS_WIKI.equals(element.getAttribute("wiki")); } + /** + * Returns true if the wiki attribute corresponds to wikidatawiki + * @param document + * @return + */ + public static boolean isWikidataNotification(Node document) { + if (document == null || !document.hasAttributes()) { + return false; + } + Element element = (Element) document; + return WIKIDATA_WIKI.equals(element.getAttribute("wiki")); + } + + /** + * Returns true if the wiki attribute corresponds to enwiki + * @param document + * @return + */ + public static boolean isWikipediaNotification(Node document) { + if (document == null || !document.hasAttributes()) { + return false; + } + Element element = (Element) document; + return WIKIPEDIA_WIKI.equals(element.getAttribute("wiki")); + } + public static NotificationType getNotificationType(Node document) { Element element = (Element) document; String type = element.getAttribute("type"); @@ -68,10 +95,17 @@ public class NotificationUtils { return notifications; } + /** + * Currently the app is interested in showing notifications just from the following three wikis: commons, wikidata, wikipedia + * This function returns true only if the notification belongs to any of the above wikis and is of a known notification type + * @param node + * @return + */ private static boolean isUsefulNotification(Node node) { - return isCommonsNotification(node) - && !getNotificationType(node).equals(UNKNOWN) - && !getNotificationType(node).equals(THANK_YOU_EDIT); + return (isCommonsNotification(node) + || isWikidataNotification(node) + || isWikipediaNotification(node)) + && !getNotificationType(node).equals(UNKNOWN); } public static boolean isBundledNotification(Node document) { @@ -97,7 +131,7 @@ public class NotificationUtils { switch (type) { case THANK_YOU_EDIT: - notificationText = context.getString(R.string.notifications_thank_you_edit); + notificationText = getThankYouEditDescription(document); break; case EDIT_USER_TALK: notificationText = getNotificationText(document); @@ -146,6 +180,16 @@ public class NotificationUtils { return body != null ? body.getTextContent() : ""; } + /** + * Gets the header node returned in the XML document to form the description for thank you edits + * @param document + * @return + */ + private static String getThankYouEditDescription(Node document) { + Node body = getNode(getModel(document), "header"); + return body != null ? body.getTextContent() : ""; + } + private static String getNotificationIconUrl(Node document) { String format = "%s%s"; Node iconUrl = getNode(getModel(document), "iconUrl"); diff --git a/app/src/main/java/fr/free/nrw/commons/notification/SvgModule.java b/app/src/main/java/fr/free/nrw/commons/notification/SvgModule.java new file mode 100644 index 000000000..5a1e8ae63 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/notification/SvgModule.java @@ -0,0 +1,35 @@ +package fr.free.nrw.commons.notification; + +import android.content.Context; +import android.graphics.drawable.PictureDrawable; +import android.support.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Registry; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.module.AppGlideModule; +import com.caverock.androidsvg.SVG; + +import java.io.InputStream; + +import fr.free.nrw.commons.glide.SvgDecoder; +import fr.free.nrw.commons.glide.SvgDrawableTranscoder; + +/** + * Module for the SVG sample app. + */ +@GlideModule +public class SvgModule extends AppGlideModule { + @Override + public void registerComponents(@NonNull Context context, @NonNull Glide glide, + @NonNull Registry registry) { + registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder()) + .append(InputStream.class, SVG.class, new SvgDecoder()); + } + + // Disable manifest parsing to avoid adding similar modules twice. + @Override + public boolean isManifestParsingEnabled() { + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java index 4390bcef4..089f1ac46 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/MultipleUploadListFragment.java @@ -26,6 +26,8 @@ import android.widget.GridView; import android.widget.RelativeLayout; import android.widget.TextView; +import butterknife.BindView; +import butterknife.ButterKnife; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.view.SimpleDraweeView; @@ -41,9 +43,13 @@ public class MultipleUploadListFragment extends Fragment { void OnMultipleUploadInitiated(); } - private GridView photosGrid; + @BindView(R.id.multipleShareBackground) + GridView photosGrid; + + @BindView(R.id.multipleBaseTitle) + EditText baseTitle; + private PhotoDisplayAdapter photosAdapter; - private EditText baseTitle; private TitleTextWatcher textWatcher = new TitleTextWatcher(); private Point photoSize; @@ -166,9 +172,7 @@ public class MultipleUploadListFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false); - photosGrid = view.findViewById(R.id.multipleShareBackground); - baseTitle = view.findViewById(R.id.multipleBaseTitle); - + ButterKnife.bind(this,view); photosAdapter = new PhotoDisplayAdapter(); photosGrid.setAdapter(photosAdapter); photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity()); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java index 198792314..e512cb889 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java @@ -8,7 +8,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.ContentResolver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -32,16 +31,16 @@ import android.support.v4.app.ActivityCompat; import android.support.v4.app.FragmentManager; import android.support.v4.content.ContextCompat; import android.support.v4.graphics.BitmapCompat; -import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.MenuItem; import android.view.View; -import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; -import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import butterknife.BindView; +import butterknife.OnClick; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.view.SimpleDraweeView; import com.github.chrisbanes.photoview.PhotoView; @@ -69,22 +68,18 @@ import fr.free.nrw.commons.caching.CacheController; import fr.free.nrw.commons.category.CategorizationFragment; import fr.free.nrw.commons.category.OnCategoriesSaveHandler; import fr.free.nrw.commons.contributions.Contribution; -import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.modifications.CategoryModifier; import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModifierSequence; import fr.free.nrw.commons.modifications.ModifierSequenceDao; import fr.free.nrw.commons.modifications.TemplateRemoveModifier; -import fr.free.nrw.commons.utils.ImageUtils; import fr.free.nrw.commons.mwapi.MediaWikiApi; import fr.free.nrw.commons.utils.ViewUtil; import timber.log.Timber; -import android.support.design.widget.FloatingActionButton; import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED; import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE; -import static java.lang.Long.min; /** * Activity for the title/desc screen after image is selected. Also starts processing image @@ -95,6 +90,22 @@ public class ShareActivity implements SingleUploadFragment.OnUploadActionInitiated, OnCategoriesSaveHandler,SimilarImageDialogFragment.onResponse { + @BindView(R.id.container) + FrameLayout flContainer; + @BindView(R.id.backgroundImage) + SimpleDraweeView backgroundImageView; + @BindView(R.id.media_map) + FloatingActionButton mapsFragment; //Lets stick to camelCase + @BindView(R.id.media_upload_zoom_in) + FloatingActionButton zoomInButton; + @BindView(R.id.media_upload_zoom_out) + FloatingActionButton zoomOutButton; + @BindView(R.id.main_fab) + FloatingActionButton mainFab; + @BindView(R.id.expanded_image) + PhotoView expandedImageView; + + private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1; private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2; private static final int REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION = 3; @@ -120,9 +131,6 @@ public class ShareActivity private Uri mediaUri; private Contribution contribution; - private SimpleDraweeView backgroundImageView; - private FloatingActionButton maps_fragment; - private boolean cacheFound; private GPSExtractor imageObj; @@ -143,11 +151,14 @@ public class ShareActivity private Animator CurrentAnimator; private long ShortAnimationDuration; - private FloatingActionButton zoomInButton; - private FloatingActionButton zoomOutButton; - private FloatingActionButton mainFab; private boolean isFABOpen = false; + //Had to make them class variables, to extract out the click listeners, also I see no harm in this + final Rect startBounds = new Rect(); + final Rect finalBounds = new Rect(); + final Point globalOffset = new Point(); + private float startScaleFinal; + /** * Called when user taps the submit button. * Requests Storage permission, if needed. @@ -264,7 +275,6 @@ public class ShareActivity setContentView(R.layout.activity_share); ButterKnife.bind(this); initBack(); - backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage); backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder .newInstance(getResources()) .setPlaceholderImage(VectorDrawableCompat.create(getResources(), @@ -337,6 +347,7 @@ public class ShareActivity if (mediaUri != null) { backgroundImageView.setImageURI(mediaUri); } +<<<<<<< HEAD } /** @@ -388,18 +399,94 @@ public class ShareActivity }); } - /** + +======= + if (savedInstanceState != null) { + contribution = savedInstanceState.getParcelable("contribution"); + } + + requestAuthToken(); + + Timber.d("Uri: %s", mediaUri.toString()); + Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory()); + + useNewPermissions = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + useNewPermissions = true; + + if (!needsToRequestStoragePermission()) { + storagePermitted = true; + } + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + locationPermitted = true; + } + } + + // Check storage permissions if marshmallow or newer + if (useNewPermissions && (!storagePermitted || !locationPermitted)) { + if (!storagePermitted && !locationPermitted) { + String permissionRationales = + getResources().getString(R.string.read_storage_permission_rationale) + "\n" + + getResources().getString(R.string.location_permission_rationale); + snackbar = requestPermissionUsingSnackBar( + permissionRationales, + new String[]{ + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.ACCESS_FINE_LOCATION}, + REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION); + View snackbarView = snackbar.getView(); + TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text); + textView.setMaxLines(3); + } else if (!storagePermitted) { + requestPermissionUsingSnackBar( + getString(R.string.read_storage_permission_rationale), + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + REQUEST_PERM_ON_CREATE_STORAGE); + } else if (!locationPermitted) { + requestPermissionUsingSnackBar( + getString(R.string.location_permission_rationale), + new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, + REQUEST_PERM_ON_CREATE_LOCATION); + } + } + performPreUploadProcessingOfFile(); + + + SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView"); + categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization"); + if (shareView == null && categorizationFragment == null) { + shareView = new SingleUploadFragment(); + getSupportFragmentManager() + .beginTransaction() + .add(R.id.single_upload_fragment_container, shareView, "shareView") + .commitAllowingStateLoss(); + } + uploadController.prepareService(); + mapsFragment.setVisibility(View.VISIBLE); + if( imageObj == null || imageObj.imageCoordsExists != true){ + mapsFragment.setVisibility(View.INVISIBLE); + } + + } + + /* +>>>>>>> refs/remotes/commons-app/master * Function to display the zoom and map FAB */ private void showFABMenu() { isFABOpen=true; +<<<<<<< HEAD if( imageObj != null && imageObj.imageCoordsExists) maps_fragment.setVisibility(View.VISIBLE); +======= + if( imageObj != null && imageObj.imageCoordsExists == true) + mapsFragment.setVisibility(View.VISIBLE); +>>>>>>> refs/remotes/commons-app/master zoomInButton.setVisibility(View.VISIBLE); mainFab.animate().rotationBy(180); - maps_fragment.animate().translationY(-getResources().getDimension(R.dimen.second_fab)); + mapsFragment.animate().translationY(-getResources().getDimension(R.dimen.second_fab)); zoomInButton.animate().translationY(-getResources().getDimension(R.dimen.first_fab)); } @@ -409,7 +496,7 @@ public class ShareActivity private void closeFABMenu(){ isFABOpen=false; mainFab.animate().rotationBy(-180); - maps_fragment.animate().translationY(0); + mapsFragment.animate().translationY(0); zoomInButton.animate().translationY(0).setListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { @@ -418,7 +505,7 @@ public class ShareActivity @Override public void onAnimationEnd(Animator animator) { if(!isFABOpen){ - maps_fragment.setVisibility(View.GONE); + mapsFragment.setVisibility(View.GONE); zoomInButton.setVisibility(View.GONE); } } @@ -671,8 +758,9 @@ public class ShareActivity return; } + //I might not be supposed to change it, but still, I saw it @Override - public void onPostiveResponse() { + public void onPositiveResponse() { imageObj = tempImageObj; decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data Timber.d("EXIF from tempImageObj"); @@ -838,26 +926,19 @@ public class ShareActivity scaled = bitmap; } // Load the high-resolution "zoomed-in" image. - PhotoView expandedImageView = (PhotoView) findViewById( - R.id.expanded_image); expandedImageView.setImageBitmap(scaled); // Calculate the starting and ending bounds for the zoomed-in image. // This step involves lots of math. Yay, math. - final Rect startBounds = new Rect(); - final Rect finalBounds = new Rect(); - final Point globalOffset = new Point(); - // The start bounds are the global visible rectangle of the thumbnail, // and the final bounds are the global visible rectangle of the container // view. Also set the container view's offset as the origin for the // bounds, since that's the origin for the positioning animation // properties (X, Y). thumbView.getGlobalVisibleRect(startBounds); - findViewById(R.id.container) - .getGlobalVisibleRect(finalBounds, globalOffset); + flContainer.getGlobalVisibleRect(finalBounds, globalOffset); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y); @@ -928,53 +1009,86 @@ public class ShareActivity // Upon clicking the zoomed-in image, it should zoom back down // to the original bounds and show the thumbnail instead of // the expanded image. - final float startScaleFinal = startScale; - zoomOutButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (CurrentAnimator != null) { - CurrentAnimator.cancel(); - } - zoomOutButton.setVisibility(View.GONE); - mainFab.setVisibility(View.VISIBLE); + startScaleFinal = startScale; - // Animate the four positioning/sizing properties in parallel, - // back to their original values. - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator - .ofFloat(expandedImageView, View.X, startBounds.left)) - .with(ObjectAnimator - .ofFloat(expandedImageView, - View.Y,startBounds.top)) - .with(ObjectAnimator - .ofFloat(expandedImageView, - View.SCALE_X, startScaleFinal)) - .with(ObjectAnimator - .ofFloat(expandedImageView, - View.SCALE_Y, startScaleFinal)); - set.setDuration(ShortAnimationDuration); - set.setInterpolator(new DecelerateInterpolator()); - set.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - thumbView.setAlpha(1f); - expandedImageView.setVisibility(View.GONE); - CurrentAnimator = null; - } - - @Override - public void onAnimationCancel(Animator animation) { - thumbView.setAlpha(1f); - expandedImageView.setVisibility(View.GONE); - CurrentAnimator = null; - } - }); - set.start(); - CurrentAnimator = set; - - } - - }); } + /* + * called when upper arrow floating button + */ + @OnClick(R.id.main_fab) + public void onMainFabClicked() { + if (!isFABOpen) { + showFABMenu(); + } else { + closeFABMenu(); + } + } + + @OnClick(R.id.media_upload_zoom_in) + public void onZoomInFabClicked() { + //This try catch block was originally holding the entire click listener on the fab button, I did not wanted to risk exceptions + try { + zoomImageFromThumb(backgroundImageView, mediaUri); + } catch (Exception e) { + Log.i("exception", e.toString()); + } + } + + @OnClick(R.id.media_upload_zoom_out) + public void onZoomOutFabClicked() { + if (CurrentAnimator != null) { + CurrentAnimator.cancel(); + } + zoomOutButton.setVisibility(View.GONE); + mainFab.setVisibility(View.VISIBLE); + + // Animate the four positioning/sizing properties in parallel, + // back to their original values. + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator + .ofFloat(expandedImageView, View.X, startBounds.left)) + .with(ObjectAnimator + .ofFloat(expandedImageView, + View.Y, startBounds.top)) + .with(ObjectAnimator + .ofFloat(expandedImageView, + View.SCALE_X, startScaleFinal)) + .with(ObjectAnimator + .ofFloat(expandedImageView, + View.SCALE_Y, startScaleFinal)); + set.setDuration(ShortAnimationDuration); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + //background image view is thumbView + backgroundImageView.setAlpha(1f); + expandedImageView.setVisibility(View.GONE); + CurrentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + //background image view is thumbView + backgroundImageView.setAlpha(1f); + expandedImageView.setVisibility(View.GONE); + CurrentAnimator = null; + } + }); + set.start(); + CurrentAnimator = set; + } + + @OnClick(R.id.media_map) + public void onFabShowMapsClicked() { + if (imageObj != null && imageObj.imageCoordsExists == true) { + Uri gmmIntentUri = Uri + .parse("google.streetview:cbll=" + imageObj.getDecLatitude() + "," + imageObj + .getDecLongitude()); + Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri); + mapIntent.setPackage("com.google.android.apps.maps"); + startActivity(mapIntent); + } + } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java index a8f336927..59b8a1223 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/SimilarImageDialogFragment.java @@ -13,6 +13,9 @@ import android.view.ViewGroup; import android.view.Window; import android.widget.Button; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.imagepipeline.listener.RequestListener; @@ -29,29 +32,33 @@ import fr.free.nrw.commons.R; */ public class SimilarImageDialogFragment extends DialogFragment { + + @BindView(R.id.orginalImage) SimpleDraweeView originalImage; + @BindView(R.id.possibleImage) SimpleDraweeView possibleImage; + @BindView(R.id.postive_button) Button positiveButton; + @BindView(R.id.negative_button) Button negativeButton; onResponse mOnResponse;//Implemented interface from shareActivity Boolean gotResponse = false; + public SimilarImageDialogFragment() { } public interface onResponse{ - public void onPostiveResponse(); + public void onPositiveResponse(); + public void onNegativeResponse(); } + @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false); + ButterKnife.bind(this,view); Set requestListeners = new HashSet<>(); requestListeners.add(new RequestLoggingListener()); - originalImage =(SimpleDraweeView) view.findViewById(R.id.orginalImage); - possibleImage =(SimpleDraweeView) view.findViewById(R.id.possibleImage); - positiveButton = (Button) view.findViewById(R.id.postive_button); - negativeButton = (Button) view.findViewById(R.id.negative_button); - originalImage.setHierarchy(GenericDraweeHierarchyBuilder .newInstance(getResources()) .setPlaceholderImage(VectorDrawableCompat.create(getResources(), @@ -70,22 +77,6 @@ public class SimilarImageDialogFragment extends DialogFragment { originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath")))); possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath")))); - negativeButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mOnResponse.onNegativeResponse(); - gotResponse = true; - dismiss(); - } - }); - positiveButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - mOnResponse.onPostiveResponse(); - gotResponse = true; - dismiss(); - } - }); return view; } @@ -105,8 +96,23 @@ public class SimilarImageDialogFragment extends DialogFragment { @Override public void onDismiss(DialogInterface dialog) { // I user dismisses dialog by pressing outside the dialog. - if(!gotResponse) + if (!gotResponse) { mOnResponse.onNegativeResponse(); + } super.onDismiss(dialog); } + + @OnClick(R.id.negative_button) + public void onNegativeButtonClicked() { + mOnResponse.onNegativeResponse(); + gotResponse = true; + dismiss(); + } + + @OnClick(R.id.postive_button) + public void onPositiveButtonClicked() { + mOnResponse.onPositiveResponse(); + gotResponse = true; + dismiss(); + } } diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 532a4897e..947bc441a 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -84,6 +84,7 @@ Categoríes Configuración Date d\'alta + Imáxenes destacaes Tocante a La app de Wikimedia Commons ye software de códigu abiertu, creáu y calteníu por becaos y voluntarios de la comunidá de Wikimedia. La Fundación Wikimedia nun participa na creación, desendolcu nin caltenimientu de la app. Crea una nueva <a href=\"https://github.com/commons-app/apps-android-commons/issues\">incidencia en GitHub</a> pa informar de problemes y suxerencies. @@ -169,6 +170,8 @@ Títulu del mediu Descripción Equí va la descripción del mediu. Esto pué ser llargo enforma, y necesitará espardese per delles llinies. Sicasí, esperamos que se vea bien. + Autor + El nome d\'usuariu del autor de la imaxe destacada va equí. Data d\'unviu Llicencia Coordenaes @@ -211,6 +214,7 @@ Salir Tutorial Avisos + Destacada Los sitios cercanos nun pueden amosase ensin los permisos d\'allugamientu nun s\'atoparon descripciones Páxina del ficheru en Commons @@ -259,4 +263,14 @@ Siguir Encaboxar Retentar + Entendílo + Estos son sitios cercanos a ti que precisen imaxes para ilustrar los sos artículos de Wikipedia + Tocando esti botón amuésase la llista d\'esos llugares + Puedes xubir una imaxe pa cualquier sitiu dende la galería o la cámara + Nun s\'alcontró nenguna imaxe + Asocedió un error al cargar les imáxenes. + Xubida por: %1$s + Compartir app + Nun s\'especificaron les coordenaes al escoyer la imaxe + Error al llograr los llugares cercanos. diff --git a/app/src/main/res/values-diq/strings.xml b/app/src/main/res/values-diq/strings.xml index 1f1d69ea3..53bd88bac 100644 --- a/app/src/main/res/values-diq/strings.xml +++ b/app/src/main/res/values-diq/strings.xml @@ -13,6 +13,7 @@ Bıngeh Lokasyon Commons + Eyari Namey karberi Parola @@ -84,7 +85,8 @@ Qeyd be Heq te cı Qandê yew <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub-cıkewtış</a>ê neweyi rê rapor û teklifan bıaferne. - <a href=\"https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\">Politikay nımıtışi</a> + <u>Politikaya nımıtışi</u> + <u>İştırakkerdoği</u> Heq te cı Peyd rışten bırış (E-posta ra) E-posta eyar nêbi @@ -92,7 +94,7 @@ Anciya bıcerrebne Bıtexelne Ron - Lisans + Lisanso hesebiyaye Attribution-ShareAlike 3.0 Attribution 3.0 CC0 @@ -127,6 +129,8 @@ E Sername + Şınasnayış + Nuştekar Lisans Koordinati Korbıze diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 81e655b95..d9580dca1 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -162,8 +162,8 @@ Nincs leírás Ismeretlen licenc Frissítés - Szükséges engedély: Külső tárhely olvasása. Az alkalmazás nem működik enélkül. - Szükséges engedély: Külső tárhely írása. Az alkalmazás nem működik enélkül. + Szükséges engedély: Külső tárhely olvasása. Az alkalmazás nem működik enélkül. + Szükséges engedély: Külső tárhely írása. Az alkalmazás nem tudja használni a kamerát enélkül. Lehetséges engedély: Jelenlegi hely megszerzése, a kategóriajavaslatok lehetőségéért. OK Közeli helyek @@ -242,6 +242,7 @@ A hely nem változott. A hely nem érhető el. Közeli helyek listájának megtekintéséhez engedély szükséges + SZÓCIKK OLVASÁSA Üdvözlünk a Wikimedia Commonson, %1$s! Örülünk, hogy itt vagy. %1$s üzenetet hagyott a vitalapodon Köszönjük a szerkesztésedet! @@ -258,5 +259,12 @@ Folytatás Mégse Újra + Ezek a helyek vannak a közeledben, amikről van Wikipédia szócikk és nincs bennük kép. + A gombra koppintva bejön egy lista, ami ezeket a helyeket mutatja. + Bármelyik helyhez feltölthetsz képet a galériádból vagy készíthetsz újat a kamerával. + Nem található kép! + Képbetöltés közben hiba történt Alkalmazás megosztása + A koordináták nem lettek megadva a kép kiválasztásakor. + Hiba a közeli helyek elérésekor. diff --git a/app/src/main/res/values-is/strings.xml b/app/src/main/res/values-is/strings.xml index ba5bdfe42..f1b020965 100644 --- a/app/src/main/res/values-is/strings.xml +++ b/app/src/main/res/values-is/strings.xml @@ -263,4 +263,14 @@ Halda áfram Hætta við Reyna aftur + Náði því! + Þetta eru þeir staðir í næsta nágrenni við þig sem vantar myndir til að skýra með Wikipedia-greinar + Ef ýtt er á þennan hnapp birtist listi yfir þessa staði + Þú getur sent inn mynd úr myndasafninu þínu eða myndavélinni + Engir myndir fundust! + Villa kom upp við að hlaða inn myndum. + Sent inn af: %1$s + Deila forriti + Hnit voru ekki tilgreind við val myndar + Villa við að sækja nálæga staði. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ef45a5d3a..6ff03ee0d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -40,7 +40,8 @@ %1$s のアップロードに失敗しました 閲覧するにはタップしてください - %1$d 件のファイルをアップロード中 + %1$d file uploading + %1$d件のファイルをアップロード中 自分の最近のアップロードファイル 順番待ち中 @@ -59,9 +60,9 @@ ログインできません - ネットワークのエラーです ログインできません - 利用者名を確認してください ログインできません - パスワードを確認してください - 失敗した回数が多すぎます。数分でもう一度お試しください。 + 失敗した回数が多すぎます。数分待ってからもう一度お試しください。 申し訳ありませんが、この利用者はコモンズでブロックされています。 - 2 要素認証コードを提供する必要があります。 + 2要素認証コードを提供する必要があります。 ログイン失敗 アップロード このセットに名前をつけてください @@ -76,22 +77,24 @@ まだ何もアップロードされていません。 \@string/contributions_subtitle_zero - %1$d 件のアップロード + pne=%1$d upload - %1$d 件のアップロードを開始中 + Starting %1$d upload + %1$d件のアップロードを開始中 + %1$d upload %1$d 件のアップロード %1$s に一致するカテゴリが見つかりません - あなたの画像をウィキメディア・コモンズで見つけやすくするためにカテゴリを追加してください。\n\nカテゴリ名の入力を開始してください。\nこの手順をスキップするにはこのメッセージをタップしてください(または戻るボタン)。 + あなたの画像をウィキメディア・コモンズで見つけやすくするためにカテゴリを追加してください。\nカテゴリ名の入力を開始してください。 カテゴリ 設定 利用者登録 秀逸な画像 このアプリについて - ウィキメディア・コモンズ・アプリはウィキメディア・コミュニティの助成金受給者とボランティアによって製作・メンテナンスされているオープンソースソフトウェアです。ウィキメディア財団はこのアプリの製作・開発・メンテナンスに関与していません。 + ウィキメディア・コモンズ・アプリはウィキメディア・コミュニティの助成金受給者とボランティアが製作・管理しているオープンソースソフトウェアです。ウィキメディア財団はこのアプリの製作・開発・メンテナンスに関与していません。 バグとアイディアは <a href=\"https://github.com/commons-app/apps-android-commons/issues\">Github</a> へ。 <u>プライバシー・ポリシー</u> <u>クレジット</u> @@ -103,7 +106,7 @@ まだ写真をアップロードしていません。 再試行 キャンセル - この画像が %1$s ライセンスでアップロードされます。 + この画像は%1$sライセンスのもとにアップロードされます。 この画像の投稿に当たり、私はこれが自分自身の作品であり、著作権のあるコンテンツや自撮りは含まれていないと宣言します。 ダウンロード 既定のライセンス @@ -148,6 +151,7 @@ - 題名: シドニー・オペラハウス\n- 説明: 湾の向こうから見たシドニー・オペラハウス\n- カテゴリ: 西側から見たシドニー・オペラハウス、遠くから見たシドニー・オペラハウス 題名: シドニーのオペラハウス 説明: シドニーのオペラハウス。湾を挟んで撮影。 + カテゴリ: シドニーオペラハウスの西面、シドニーオペラ遠景 画像を投稿してください。ウィキペディアの記事に彩りを! ウィキペディアの画像はウィキメディア・コモンズに保管されています。 あなたの画像は世界中の人々が学習する助けになります @@ -167,7 +171,7 @@ 近くの場所 付近の場所が見つかりません 警告 - このファイルが既にコモンズにあります。本当にアップロードしますか? + このファイルは既にコモンズにあります。本当にアップロードしますか? はい いいえ タイトル @@ -185,7 +189,7 @@ 2FA コード 最近のアップロードファイルに表示する最大件数 最大限 - 500 以上の項目を表示できません + 表示できるのは500件以下です 最近のアップロードファイルに表示する最大件数 2段階認証は現在サポートされていません。 ログアウトしてもよろしいですか? @@ -226,7 +230,7 @@ ウィキペディアの記事 画像をキャッシュする際のエラー ファイル固有の説明的な表題。ファイル名として使われます。平易な言葉を使い、空白を入れることができます。拡張子は含めないでください。 - 可能な限りメディアを説明してください:どこで撮られましたか?それは何を示していますか?文脈とは何ですか?物や人を説明してください。容易に推測できない情報、例えば風景の場合の時刻を明らかにする。メディアに珍しいことがある場合は、何が珍しいのかを説明してください。 + 可能な限りメディアを説明してください: 撮影地はどこですか? それは何を示していますか? どんな文脈がありますか? 被写体の物や人を説明してください。容易に推測できない情報、例えば風景であれば時刻を明示します。特筆すべき物事が映っている場合は、何が珍しいのかを説明してください。 この画像は暗すぎますがアップロードしますか? ウィキメディア・コモンズは百科事典に適した画像のみ受け付けます。 ピントが合っていませんが、アップロードしますか? ウィキメディア・コモンズは百科事典に適した画像のみ受け付けます。 権限を付与 @@ -255,7 +259,7 @@ ウィキペディア コモンズ <u>評価する</u> - <u>FAQ</u> + <u>よくある質問</u> チュートリアルをスキップする インターネットに接続していません インターネットに接続しました @@ -276,4 +280,5 @@ アップロードした人: %1$ アプリをシェアする 画像の選択中に位置情報を特定できませんでした + 付近の場所を取得しようとしてエラーが発生しました。 diff --git a/app/src/main/res/values-kum/strings.xml b/app/src/main/res/values-kum/strings.xml new file mode 100644 index 000000000..1630600cd --- /dev/null +++ b/app/src/main/res/values-kum/strings.xml @@ -0,0 +1,96 @@ + + + + Гёрюнюш + Ортакъ + + Кюйлевлер + Къоллавчу аты + Чечил + Гирмек + Чечилни унутдунгму? + Къайытланмакъ + %1$s юкленген! + %1$s юкленип тура + Къарамакъ учун бас + Гезикде + Юклев + Сурат алмакъ + Ювукъда + Юклевлерим + Пайлашмакъ + Ат + Тасвир + Юклев + Юклев + Сакъламакъ + Янгыртмакъ + Тизме + GPS ишлетмек + Юклевлер ёкъ гьали де + Категориялар + Кюйлевлер + Къайытланмакъ + Тасвири + <u>Баракаллалар</u> + Тасвири + Такрарламакъ + Гери алмакъ + Юклемек + CC0 + CC BY-SA 3.0 + CC BY-SA 3.0 (Алмания) + CC BY-SA 3.0 (Эстония) + CC BY 3.0 + CC BY-SA 4.0 + CC BY 4.0 + CC Zero + Юклев уьлгю: + Дюр! + Категориялар + Юклев... + Бир зат сайланмагъан + Тасвири ёкъ + Янгыртмакъ + OK + Буварыв + Дюр + Ёкъ + Ат + Тасвир + Ясавчу + Юклев тархы + 2FA код + Лале + Оьзсуратсыз + Хош гел Википедиягъа + Гери алмакъ + Ачмакъ + Япмакъ + Баш + Юклемек + Ювукъда + Тасвир + Кюйлевлер + Чыкъмакъ + Билдиривлер + Сайламлы + тасвири табылмады + Википедия макъала + Янгылыш! Байланыв табылмады + ЯКЪЛАР + ВИКИПЕДИЯ + <u>Къыйматла бизин</u> + Интернет гиришсиз + Интернет гиришли + Билдиривлер ёкъ + <u>Таржума этмек</u> + Тиллер + Гери алыв + Такрарламакъ + Тюшюндюм! + Гьеч сурат табылмады! + Уьстевню пайлашмакъ + diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml index be0d2f69f..db9d8d3fd 100644 --- a/app/src/main/res/values-ne/strings.xml +++ b/app/src/main/res/values-ne/strings.xml @@ -55,7 +55,7 @@ श्रेणीहरू सेटिङ्गहरू बारेमा - <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">अपाचे लाइसेन्स संस्करण२</a> को अाधारमा खुला स्रोत सफ्टवेयर जारी + विकिमिडिया कमन्स याप एक स्वतन्त्र स्रोत याप हो। यो याप विकिमिडिया समुदायका अनुदानप्राप्तकर्ताहरू र स्वयंसेवकहरू द्वारा निर्मित एवं प्रबन्धित छ। विकिमीडिया फाउण्डेसन यस यापको निर्माण, विकास र प्रबन्धनमा कुनै पनि प्रकारले संलग्न छैन। <a href=\"https://github.com/commons-app/apps-android-commons\">गिटहब</a> मा स्रोत। <a href=\" https://github.com/commons-app/apps-android-commons/issues\">बगजिल्ला</a> मा बग छ। <a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">गोपनीयता नीति</a> बारेमा diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ce71af94b..0d5a0ee3e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -46,11 +46,7 @@ Завершение загрузки %1$s Загрузка %1$s не удалась Нажмите для просмотра - - %1$d файл загружается - %1$d файла загружается - %1$d файлов загружается - + %1$d {{PLURAL:%1$d|one=файл загружается|few=файла загружается|файлов загружается}} Мои недавние загрузки В очереди Ошибка загрузки. @@ -115,7 +111,7 @@ Почтовый клиент не установлен Недавно использованные категории Ожидание первой синхронизации… - Вы ещё не загрузили ни одной фотографии. + Вы ещё не загрузили ни одного изображения. Повторить Отмена Это изображение будет лицензировано под %1$s @@ -123,7 +119,7 @@ Скачать Лицензия по умолчанию Использовать предыдущие название/описание - Автоматически получить текущее местоположение + Анализ местоположения Получить текущее местоположение, чтобы были предложены категории, если изображение не содержит геотегов Ночной режим Использовать тёмную тему @@ -148,7 +144,7 @@ CC BY 4.0 CC Zero Викисклад содержит бо́льшую часть изображений, которые используются в Википедии. - Ваши изображения помогают образованию людей во всём мире! + Ваши изображения могут помочь образованию людей во всём мире! Пожалуйста, загрузите фотографии, которые были сняты или созданы исключительно вами: Природные объекты (например, цветы, животные, горы)\n• Полезные предметы (например, велосипеды, вокзалы)\n• Известные люди (например, ваш мэр, спортсмены-олимпийцы, которых вы встретили) Природные объекты (например, цветы, животные, горы) @@ -166,7 +162,7 @@ Категории: Sydney Opera House from the west, Sydney Opera House remote views Загрузите свои изображения. Помогите Википедии оживить статьи! Изображения в Википедии хранятся на Викискладе. - Ваши изображения помогают образованию людей во всём мире. + Ваши изображения могут помочь образованию людей во всём мире. Избегайте материалов, защищённых авторским правом, например, найденных в Интернете, изображений плакатов, книжных обложек и т.п. Вам это понятно? Да! @@ -211,7 +207,7 @@ Facebook-страница Commons Исходные коды Commons на гитхабе Фоновое изображение - Ошибка медиаизображения + Ошибка медиафайла Изображение не найдено Загрузить изображение Гора Зао @@ -246,7 +242,7 @@ Пожалуйста, подробно опишите загружаемый файл: где он был снят? что на нём изображено? каков его контекст? Пожалуйста опишите изображённых персон или объекты. Добавьте информацию, о которой нельзя легко догадаться, например, время суток, когда снимался файл. Если снято что-то необычное, постарайтесь пояснить, что именно в этом необычного. Это изображение слишком тёмное. Вы уверены, что хотите его загрузить? Викисклад подходит только для фотографий, имеющих энциклопедическую ценность. Это изображение размыто. Вы уверены, что хотите его загрузить? Викисклад подходит только для фотографий, имеющих энциклопедическую ценность. - Дать разрешение + Разрешить Использовать внешнее хранилище Сохранять изображения, сделанные с помощью встроенной камеры на устройстве Войдите в свою учётную запись @@ -294,4 +290,5 @@ Загружено участником %1$s Поделиться приложением Во время выбора изображения не были указаны координаты + Ошибка получения мест поблизости diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 17a324a97..1d2fc868e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -280,4 +280,5 @@ Uppladdad av: %1$s Dela app Koordinater specificerades inte vid bildvalet + Fel uppstod när platser i närheten hämtades. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index dadd40917..c9dd60b41 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -280,4 +280,5 @@ Yükleyen: %1$s Uygulamayı Paylaş Koordinatlar görüntü seçimi sırasında belirlenmedi + Yakındaki yerler alınırken hata oluştu. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index a4ed20320..0e83e6be0 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -150,7 +150,7 @@ Корисні об\'єкти (велосипеди, залізничні станції) Відомі люди (ваш мер, спортсмен-олімпієць, якого ви зустріли) Будь ласка, НЕ завантажуйте: - u2022 Селфі або фото своїх друзів \nu2022 Зображення, які Ви завантажили з інтернету \nu2022 Скріншоти патентованих програм + - Селфі або фото своїх друзів \n- Зображення, які Ви завантажили з інтернету \n- Скріншоти патентованих програм Селфі чи фото ваших друзів Зображення, які ви завантажили з інтернету Знімки екрану пропрієтарних програм @@ -165,6 +165,7 @@ Уникайте захищених авторським правом матеріалів, знайдених в Інтернеті, а також зображень плакатів, обкладинок книг і т. п. Ви це зрозуміли? Так! + Категорії Завантаження… Нічого не обрано @@ -186,7 +187,7 @@ Опис Сюди потрапляє опис медіафайлу. Він потенційно може бути досить довгим і розтягнутися на декілька рядків. Однак ми сподіваємось, що він виглядатиме гарно. Автор - Тут вказується ім\'я автора вибраного зображеня + Тут вказується ім\'я автора вибраного зображення Дата завантаження Ліцензія Координати @@ -202,7 +203,7 @@ Ви справді хочете вийти із системи? Логотип Вікісховища Веб-сайт Commons - Фейсбук сторінка Commons + Facebook-сторінка Commons Програмний код Commons на GitHub Фонове зображення Помилка медіазображення @@ -237,15 +238,15 @@ Стаття Вікіпедії Помилка кешування зображень Унікальна описова назва файлу. Ви можете використовувати простий текст з пробілами. Не вказуйте розширення файлу - Будь ласка, докладно опишіть файл: де його було зроблено? що на ньому зображено? який контекст? Будь ласка, опишіть об\'єкти чи осіб. Додайте інформацію, яку не можна легко здогадатися, наприклад, пору доби для фотографії пейзажу. Якщо зображено щось незвичайне, постарайтеся пояснити, що робить його незвичайним. + Будь ласка, докладно опишіть файл: де його було зроблено? що на ньому зображено? який контекст? Будь ласка, опишіть об\'єкти чи осіб. Додайте інформацію, яку не можна легко здогадатися, наприклад, пору доби для фотографії пейзажу. Якщо зображено щось незвичайне, спробуйте пояснити, що робить його незвичайним. Це зображення надто темне. Ви упевнені, що хочете його завантажити? Вікісховище призначене лише для зображень, що мають енциклопедичну цінність. Це зображення розмите. Ви упевнені, що хочете його завантажити? Вікісховище призначене лише для зображень, що мають енциклопедичну цінність. Надати дозвіл Використовувати зовнішнє сховище Зберігати зображення, виконані вбудованою камерою Вашого пристрою Увійдіть у свій обліковий запис - Надіслати лог-файл - Надіслати лог-файл розробникам електронною поштою + Надіслати файл журналу + Надіслати файл журналу розробникам електронною поштою Не знайдено браузера, щоб відкрити посилання Помилка! Посилання не знайдено Номінувати на вилучення @@ -257,9 +258,9 @@ Потрібний дозвіл для показу списку місць поблизу Показати на мапі у зовнішній програмі ЧИТАТИ СТАТТЮ - Вітаємо у Wikimedia Commons, %1$s! Раді вас бачити. + Вітаємо у Вікісховищі, %1$s! Раді вас бачити. %1$s залишив повідомлення на вашій сторінці обговорення - Дякуємо за правку + Дякуємо за редагування %1$s згадав вас на %2$s. Перемкнути режим перегляду НАПРЯМКИ @@ -279,4 +280,14 @@ Виконується Скасувати Повторити + Зрозуміло + Це місця поблизу, про які є статті Вікіпедії, але які потребують ілюстрацій + Натискання цієї кнопки згенерує список таких місць + Ви можете завантажити зображення для любого з цих місць, зробивши знімок камерою або вибравши зображення з галереї + Зображень не знайдено! + Сталася помилка при завантаженні зображень. + Завантажено: %1$s + Поділитися програмою + Під час вибору зображення не були вказані координати + Помилка отримання місць поблизу. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e30baa10..ba982e625 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,8 +46,7 @@ Please provide a title for this file Description Unable to login - network failure - Unable to login - please check your username - Unable to login - please check your password + Unable to login - please check your username and password Too many unsuccessful attempts. Please try again in a few minutes. Sorry, this user has been blocked on Commons You must provide your two factor authentication code. diff --git a/app/src/main/resources/queries/nearby_query.rq b/app/src/main/resources/queries/nearby_query.rq index 0453005e8..b11f0985a 100644 --- a/app/src/main/resources/queries/nearby_query.rq +++ b/app/src/main/resources/queries/nearby_query.rq @@ -39,6 +39,11 @@ SELECT # Get emoji OPTIONAL { ?classId wdt:P487 ?emoji0. } OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. } + + OPTIONAL { + ?wikipediaArticle schema:about ?item ; + schema:isPartOf . + } OPTIONAL { ?wikipediaArticle schema:about ?item ; schema:isPartOf . diff --git a/gradle.properties b/gradle.properties index 850003852..05aa34949 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,17 +14,17 @@ # org.gradle.parallel=true #Thu Mar 01 15:28:48 IST 2018 systemProp.http.proxyPort=0 -compileSdkVersion=android-26 +compileSdkVersion=android-27 android.useDeprecatedNdk=true BUTTERKNIFE_VERSION=8.6.0 org.gradle.jvmargs=-Xmx1536M -buildToolsVersion=26.0.2 -targetSdkVersion=25 +buildToolsVersion=27.0.0 +targetSdkVersion=27 #TODO: Temporary disabled. https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html#aapt2 #Refer to PR: https://github.com/commons-app/apps-android-commons/pull/932 android.enableAapt2=false -SUPPORT_LIB_VERSION=26.0.2 +SUPPORT_LIB_VERSION=27.1.1 minSdkVersion=15 systemProp.http.proxyHost= LEAK_CANARY=1.5.4