Merge branch 'master' into dependency-injection

This commit is contained in:
Paul Hawke 2017-09-23 14:24:34 -05:00
commit e33febf506
36 changed files with 706 additions and 446 deletions

View file

@ -93,6 +93,8 @@ android {
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.org/wiki/\""
buildConfigField "String", "EVENTLOG_URL", "\"https://www.wikimedia.org/beacon/event\""
buildConfigField "String", "EVENTLOG_WIKI", "\"commonswiki\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\""
}
beta {
@ -104,6 +106,8 @@ android {
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "EVENTLOG_URL", "\"https://commons.wikimedia.beta.wmflabs.org/beacon/event\""
buildConfigField "String", "EVENTLOG_WIKI", "\"commonswiki\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""
}
}

View file

@ -28,7 +28,6 @@ import fr.free.nrw.commons.di.CommonsApplicationModule;
import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.FileUtils;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -100,7 +99,7 @@ public class CommonsApplication extends DaggerApplication {
return component;
}
public void clearApplicationData(Context context, NavigationBaseActivity.LogoutListener logoutListener) {
public void clearApplicationData(Context context, LogoutListener logoutListener) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
if (applicationDirectory.exists()) {
@ -141,4 +140,8 @@ public class CommonsApplication extends DaggerApplication {
Category.Table.onDelete(db);
Contribution.Table.onDelete(db);
}
public interface LogoutListener {
void onLogoutComplete();
}
}

View file

@ -12,18 +12,16 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class LicenseList {
Map<String, License> licenses = new HashMap<>();
Resources res;
private static String XMLNS_LICENSE = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
private Map<String, License> licenses = new HashMap<>();
private Resources res;
public LicenseList(Activity activity) {
res = activity.getResources();
XmlPullParser parser = res.getXml(R.xml.wikimedia_licenses);
while (xmlFastForward(parser, XMLNS_LICENSE, "license")) {
String namespace = "https://www.mediawiki.org/wiki/Extension:UploadWizard/xmlns/licenses";
while (xmlFastForward(parser, namespace, "license")) {
String id = parser.getAttributeValue(null, "id");
String template = parser.getAttributeValue(null, "template");
String url = parser.getAttributeValue(null, "url");
@ -33,10 +31,6 @@ public class LicenseList {
}
}
public Set<String> keySet() {
return licenses.keySet();
}
public Collection<License> values() {
return licenses.values();
}
@ -46,7 +40,7 @@ public class LicenseList {
}
@Nullable
public License licenseForTemplate(String template) {
License licenseForTemplate(String template) {
String ucTemplate = new PageTitle(template).getDisplayText();
for (License license : values()) {
if (ucTemplate.equals(new PageTitle(license.getTemplate()).getDisplayText())) {
@ -56,27 +50,17 @@ public class LicenseList {
return null;
}
public String nameIdForTemplate(String template) {
private String nameIdForTemplate(String template) {
// hack :D (converts dashes and periods to underscores)
// cc-by-sa-3.0 -> cc_by_sa_3_0
return "license_name_" + template.toLowerCase(Locale.ENGLISH).replace("-", "_").replace(".", "_");
return "license_name_" + template.toLowerCase(Locale.ENGLISH).replace("-",
"_").replace(".", "_");
}
private int stringIdByName(String stringId) {
return res.getIdentifier("fr.free.nrw.commons:string/" + stringId, null, null);
}
public String nameForTemplate(String template) {
//Log.d("Commons", "LicenseList.nameForTemplate: template: " + template);
String stringId = nameIdForTemplate(template);
//Log.d("Commons", "LicenseList.nameForTemplate: stringId: " + stringId);
int nameId = stringIdByName(stringId);
//Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
if(nameId != 0) {
//Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
return res.getString(nameId);
}
return template;
private String nameForTemplate(String template) {
int nameId = res.getIdentifier("fr.free.nrw.commons:string/"
+ nameIdForTemplate(template), null, null);
return (nameId != 0) ? res.getString(nameId) : template;
}
/**

View file

@ -29,12 +29,66 @@ public class Media implements Parcelable {
}
};
private static Pattern displayTitlePattern = Pattern.compile("(.*)(\\.\\w+)", Pattern.CASE_INSENSITIVE);
// Primary metadata fields
protected Uri localUri;
protected String imageUrl;
protected String filename;
protected String description; // monolingual description on input...
protected long dataLength;
protected Date dateCreated;
protected @Nullable Date dateUploaded;
protected int width;
protected int height;
protected String license;
protected String creator;
protected ArrayList<String> categories; // as loaded at runtime?
private Map<String, String> descriptions; // multilingual descriptions as loaded
private HashMap<String, Object> tags = new HashMap<>();
private @Nullable LatLng coordinates;
protected Media() {
this.categories = new ArrayList<>();
this.descriptions = new HashMap<>();
}
private HashMap<String, Object> tags = new HashMap<>();
public Media(String filename) {
this();
this.filename = filename;
}
public Media(Uri localUri, String imageUrl, String filename, String description,
long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
this();
this.localUri = localUri;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
}
@SuppressWarnings("unchecked")
public Media(Parcel in) {
localUri = in.readParcelable(Uri.class.getClassLoader());
imageUrl = in.readString();
filename = in.readString();
description = in.readString();
dataLength = in.readLong();
dateCreated = (Date) in.readSerializable();
dateUploaded = (Date) in.readSerializable();
creator = in.readString();
tags = (HashMap<String, Object>) in.readSerializable();
width = in.readInt();
height = in.readInt();
license = in.readString();
if (categories != null) {
in.readStringList(categories);
}
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
public Object getTag(String key) {
return tags.get(key);
@ -44,15 +98,14 @@ public class Media implements Parcelable {
tags.put(key, value);
}
public static Pattern displayTitlePattern = Pattern.compile("(.*)(\\.\\w+)", Pattern.CASE_INSENSITIVE);
public String getDisplayTitle() {
if(filename == null) {
if (filename == null) {
return "";
}
// FIXME: Gross hack bercause my regex skills suck maybe or I am too lazy who knows
String title = getFilePageTitle().getDisplayText().replaceFirst("^File:", "");
Matcher matcher = displayTitlePattern.matcher(title);
if(matcher.matches()) {
if (matcher.matches()) {
return matcher.group(1);
} else {
return title;
@ -68,7 +121,7 @@ public class Media implements Parcelable {
}
public String getImageUrl() {
if(imageUrl == null) {
if (imageUrl == null) {
imageUrl = Utils.makeThumbBaseUrl(this.getFilename());
}
return imageUrl;
@ -86,6 +139,10 @@ public class Media implements Parcelable {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public long getDataLength() {
return dataLength;
}
@ -102,7 +159,8 @@ public class Media implements Parcelable {
this.dateCreated = date;
}
public @Nullable Date getDateUploaded() {
public @Nullable
Date getDateUploaded() {
return dateUploaded;
}
@ -138,7 +196,8 @@ public class Media implements Parcelable {
this.license = license;
}
public @Nullable LatLng getCoordinates() {
public @Nullable
LatLng getCoordinates() {
return coordinates;
}
@ -146,24 +205,9 @@ public class Media implements Parcelable {
this.coordinates = coordinates;
}
// Primary metadata fields
protected Uri localUri;
protected String imageUrl;
protected String filename;
protected String description; // monolingual description on input...
protected long dataLength;
protected Date dateCreated;
protected @Nullable Date dateUploaded;
protected int width;
protected int height;
protected String license;
private @Nullable LatLng coordinates;
protected String creator;
protected ArrayList<String> categories; // as loaded at runtime?
protected Map<String, String> descriptions; // multilingual descriptions as loaded
@SuppressWarnings("unchecked")
public ArrayList<String> getCategories() {
return (ArrayList<String>)categories.clone(); // feels dirty
return (ArrayList<String>) categories.clone(); // feels dirty
}
public void setCategories(List<String> categories) {
@ -171,7 +215,7 @@ public class Media implements Parcelable {
this.categories.addAll(categories);
}
public void setDescriptions(Map<String,String> descriptions) {
void setDescriptions(Map<String, String> descriptions) {
for (String key : this.descriptions.keySet()) {
this.descriptions.remove(key);
}
@ -196,23 +240,6 @@ public class Media implements Parcelable {
}
}
public Media(String filename) {
this();
this.filename = filename;
}
public Media(Uri localUri, String imageUrl, String filename, String description, long dataLength, Date dateCreated, @Nullable Date dateUploaded, String creator) {
this();
this.localUri = localUri;
this.imageUrl = imageUrl;
this.filename = filename;
this.description = description;
this.dataLength = dataLength;
this.dateCreated = dateCreated;
this.dateUploaded = dateUploaded;
this.creator = creator;
}
@Override
public int describeContents() {
return 0;
@ -235,27 +262,4 @@ public class Media implements Parcelable {
parcel.writeStringList(categories);
parcel.writeMap(descriptions);
}
public Media(Parcel in) {
localUri = in.readParcelable(Uri.class.getClassLoader());
imageUrl = in.readString();
filename = in.readString();
description = in.readString();
dataLength = in.readLong();
dateCreated = (Date) in.readSerializable();
dateUploaded = (Date) in.readSerializable();
creator = in.readString();
tags = (HashMap<String, Object>)in.readSerializable();
width = in.readInt();
height = in.readInt();
license = in.readString();
if (categories != null) {
in.readStringList(categories);
}
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
public void setDescription(String description) {
this.description = description;
}
}

View file

@ -22,13 +22,14 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
private String authCookie;
private void getAuthCookie(Account account, AccountManager accountManager) {
Single.fromCallable(() -> accountManager.blockingGetAuthToken(account, "", false))
.subscribeOn(Schedulers.io())
.doOnError(Timber::e)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::onAuthCookieAcquired,
this:: onAuthCookieAcquired,
throwable -> onAuthFailure());
}
@ -55,13 +56,13 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
}
protected void requestAuthToken() {
if(authCookie != null) {
if (authCookie != null) {
onAuthCookieAcquired(authCookie);
return;
}
AccountManager accountManager = AccountManager.get(this);
Account curAccount = sessionManager.getCurrentAccount();
if(curAccount == null) {
if (curAccount == null) {
addAccount(accountManager);
} else {
getAuthCookie(curAccount, accountManager);
@ -72,7 +73,7 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
if (savedInstanceState != null) {
authCookie = savedInstanceState.getString("authCookie");
}
}
@ -84,5 +85,6 @@ public abstract class AuthenticatedActivity extends NavigationBaseActivity {
}
protected abstract void onAuthCookieAcquired(String authCookie);
protected abstract void onAuthFailure();
}

View file

@ -6,6 +6,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.theme.BaseActivity;
import timber.log.Timber;
@ -26,13 +27,13 @@ public class SignupActivity extends BaseActivity {
//Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can trust Wikimedia's site... right?
webSettings.setJavaScriptEnabled(true);
webView.loadUrl("https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes");
webView.loadUrl(BuildConfig.SIGNUP_LANDING_URL);
}
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.equals("https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes")) {
if (url.equals(BuildConfig.SIGNUP_SUCCESS_REDIRECTION_URL)) {
//Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url);

View file

@ -32,7 +32,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
private final Context context;
private MediaWikiApi mediaWikiApi;
public WikiAccountAuthenticator(Context context, MediaWikiApi mwApi) {
WikiAccountAuthenticator(Context context, MediaWikiApi mwApi) {
super(context);
this.context = context;
this.mediaWikiApi = mwApi;
@ -98,7 +98,8 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
String authTokenType, Bundle options) throws NetworkErrorException {
// Extract the username and password from the Account Manager, and ask
// the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(context);
@ -154,8 +155,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
@Override
public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @Nullable String authTokenType,
@Nullable Bundle options)
throws NetworkErrorException {
@Nullable Bundle options) throws NetworkErrorException {
return unsupportedOperation();
}

View file

@ -31,16 +31,13 @@ class CategoriesRenderer extends Renderer<CategoryItem> {
@Override
protected void hookListeners(View view) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
view.setOnClickListener(v -> {
CategoryItem item = getContent();
item.setSelected(!item.isSelected());
checkedView.setChecked(item.isSelected());
if (listener != null) {
listener.categoryClicked(item);
}
}
});
}

View file

@ -101,6 +101,7 @@ public class CategorizationFragment extends DaggerFragment {
categoriesCache = new HashMap<>();
if (savedInstanceState != null) {
items.addAll(savedInstanceState.getParcelableArrayList("currentCategories"));
//noinspection unchecked
categoriesCache.putAll((HashMap<String, ArrayList<String>>) savedInstanceState
.getSerializable("categoriesCache"));
}

View file

@ -17,18 +17,23 @@ import fr.free.nrw.commons.data.Category;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
import static android.content.UriMatcher.NO_MATCH;
import static fr.free.nrw.commons.data.Category.Table.ALL_FIELDS;
import static fr.free.nrw.commons.data.Category.Table.COLUMN_ID;
import static fr.free.nrw.commons.data.Category.Table.TABLE_NAME;
public class CategoryContentProvider extends ContentProvider {
public static final String AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
// For URI matcher
private static final int CATEGORIES = 1;
private static final int CATEGORIES_ID = 2;
public static final String AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
private static final String BASE_PATH = "categories";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, BASE_PATH, CATEGORIES);
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID);
@ -51,23 +56,23 @@ public class CategoryContentProvider extends ContentProvider {
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Category.Table.TABLE_NAME);
queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch(uriType) {
switch (uriType) {
case CATEGORIES:
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case CATEGORIES_ID:
cursor = queryBuilder.query(db,
Category.Table.ALL_FIELDS,
ALL_FIELDS,
"_id = ?",
new String[] { uri.getLastPathSegment() },
new String[]{uri.getLastPathSegment()},
null,
null,
sortOrder
@ -95,7 +100,7 @@ public class CategoryContentProvider extends ContentProvider {
long id;
switch (uriType) {
case CATEGORIES:
id = sqlDB.insert(Category.Table.TABLE_NAME, null, contentValues);
id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
@ -118,9 +123,9 @@ public class CategoryContentProvider extends ContentProvider {
sqlDB.beginTransaction();
switch (uriType) {
case CATEGORIES:
for(ContentValues value: values) {
for (ContentValues value : values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(Category.Table.TABLE_NAME, null, value);
sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
@ -148,13 +153,12 @@ public class CategoryContentProvider extends ContentProvider {
int rowsUpdated;
switch (uriType) {
case CATEGORIES_ID:
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Category.Table.TABLE_NAME,
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
Category.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
COLUMN_ID + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");

View file

@ -127,7 +127,7 @@ public class Contribution extends Media {
}
public String getPageContents() {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
buffer

View file

@ -1,30 +1,39 @@
package fr.free.nrw.commons.contributions;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.FileProvider;
import java.io.File;
import java.util.Date;
import java.util.List;
import fr.free.nrw.commons.upload.ShareActivity;
import fr.free.nrw.commons.upload.UploadService;
import timber.log.Timber;
public class ContributionController {
import static android.content.Intent.ACTION_GET_CONTENT;
import static android.content.Intent.ACTION_SEND;
import static android.content.Intent.EXTRA_STREAM;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
import static fr.free.nrw.commons.upload.UploadService.EXTRA_SOURCE;
class ContributionController {
private static final int SELECT_FROM_GALLERY = 1;
private static final int SELECT_FROM_CAMERA = 2;
private Fragment fragment;
private Activity activity;
private final static int SELECT_FROM_GALLERY = 1;
private final static int SELECT_FROM_CAMERA = 2;
public ContributionController(Fragment fragment) {
ContributionController(Fragment fragment) {
this.fragment = fragment;
this.activity = fragment.getActivity();
}
// See http://stackoverflow.com/a/5054673/17865 for why this is done
@ -34,43 +43,59 @@ public class ContributionController {
File photoFile = new File(fragment.getContext().getCacheDir() + "/images",
new Date().getTime() + ".jpg");
photoFile.getParentFile().mkdirs();
Context applicationContext = fragment.getActivity().getApplicationContext();
return FileProvider.getUriForFile(
fragment.getContext(),
fragment.getActivity().getApplicationContext().getPackageName() + ".provider",
applicationContext.getPackageName() + ".provider",
photoFile);
}
public void startCameraCapture() {
private static void requestWritePermission(Context context, Intent intent, Uri uri) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
void startCameraCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
lastGeneratedCaptureUri = reGenerateImageCaptureUriInCache();
takePictureIntent.setFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Intent.setFlags doesn't work for API level <20
requestWritePermission(fragment.getContext(), takePictureIntent, lastGeneratedCaptureUri);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureUri);
fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
}
public void startGalleryPick() {
//FIXME: Starts gallery (opens Google Photos)
Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
}
public void handleImagePicked(int requestCode, Intent data) {
void handleImagePicked(int requestCode, Intent data) {
FragmentActivity activity = fragment.getActivity();
Intent shareIntent = new Intent(activity, ShareActivity.class);
shareIntent.setAction(Intent.ACTION_SEND);
switch(requestCode) {
shareIntent.setAction(ACTION_SEND);
switch (requestCode) {
case SELECT_FROM_GALLERY:
//Handles image picked from gallery
Uri imageData = data.getData();
shareIntent.setType(activity.getContentResolver().getType(imageData));
shareIntent.putExtra(Intent.EXTRA_STREAM, imageData);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY);
shareIntent.putExtra(EXTRA_STREAM, imageData);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
break;
case SELECT_FROM_CAMERA:
shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type
shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
break;
}
Timber.i("Image selected");
@ -81,12 +106,14 @@ public class ContributionController {
}
}
public void saveState(Bundle outState) {
void saveState(Bundle outState) {
if (outState != null) {
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureUri);
}
}
public void loadState(Bundle savedInstanceState) {
if(savedInstanceState != null) {
void loadState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
lastGeneratedCaptureUri = savedInstanceState.getParcelable("lastGeneratedCaptureURI");
}
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.contributions;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@ -41,7 +40,13 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class ContributionsActivity extends AuthenticatedActivity
import static android.content.ContentResolver.requestSync;
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.Contribution.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.AUTHORITY;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;public class ContributionsActivity
extends AuthenticatedActivity
implements LoaderManager.LoaderCallbacks<Cursor>,
AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider,
@ -68,14 +73,18 @@ public class ContributionsActivity extends AuthenticatedActivity
This is why Contribution.STATE_COMPLETED is -1.
*/
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")";
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, "
+ Contribution.Table.COLUMN_UPLOADED + " DESC , ("
+ Contribution.Table.COLUMN_TIMESTAMP + " * "
+ Contribution.Table.COLUMN_STATE + ")";
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder).getService();
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
.getService();
isUploadServiceConnected = true;
}
@ -113,13 +122,14 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here!
ContentResolver.requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
requestSync(sessionManager.getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
allContributions = getContentResolver().query(ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
allContributions = getContentResolver().query(BASE_URI, ALL_FIELDS,
CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
getSupportLoaderManager().initLoader(0, null, this);
}
@ -132,12 +142,13 @@ public class ContributionsActivity extends AuthenticatedActivity
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
contributionsList = (ContributionsListFragment) getSupportFragmentManager()
FragmentManager supportFragmentManager = getSupportFragmentManager();
contributionsList = (ContributionsListFragment)supportFragmentManager
.findFragmentById(R.id.contributionsListFragment);
getSupportFragmentManager().addOnBackStackChangedListener(this);
supportFragmentManager.addOnBackStackChangedListener(this);
if (savedInstanceState != null) {
mediaDetails = (MediaDetailPagerFragment) getSupportFragmentManager()
mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
.findFragmentById(R.id.contributionsFragmentContainer);
}
requestAuthToken();
@ -148,23 +159,25 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("mediaDetailsVisible", (mediaDetails != null && mediaDetails.isVisible()));
boolean mediaDetailsVisible = mediaDetails != null && mediaDetails.isVisible();
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
}
/**
* Replace whatever is in the current contributionsFragmentContainer view with mediaDetailPagerFragment,
* / and preserve previous state in back stack.
* / Called when user selects a contribution.
* Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack.
* Called when user selects a contribution.
*/
private void showDetail(int i) {
if (mediaDetails == null || !mediaDetails.isVisible()) {
mediaDetails = new MediaDetailPagerFragment();
this.getSupportFragmentManager()
FragmentManager supportFragmentManager = getSupportFragmentManager();
supportFragmentManager
.beginTransaction()
.replace(R.id.contributionsFragmentContainer, mediaDetails)
.addToBackStack(null)
.commit();
this.getSupportFragmentManager().executePendingTransactions();
supportFragmentManager.executePendingTransactions();
}
mediaDetails.showImage(i);
}
@ -172,7 +185,7 @@ public class ContributionsActivity extends AuthenticatedActivity
public void retryUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = Contribution.fromCursor(allContributions);
if (c.getState() == Contribution.STATE_FAILED) {
if (c.getState() == STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Timber.d("Restarting for %s", c.toContentValues());
} else {
@ -183,9 +196,9 @@ public class ContributionsActivity extends AuthenticatedActivity
public void deleteUpload(int i) {
allContributions.moveToPosition(i);
Contribution c = Contribution.fromCursor(allContributions);
if (c.getState() == Contribution.STATE_FAILED) {
if (c.getState() == STATE_FAILED) {
Timber.d("Deleting failed contrib %s", c.toContentValues());
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(AUTHORITY));
c.delete();
} else {
Timber.d("Skipping deletion for non-failed contrib %s", c.toContentValues());
@ -222,19 +235,18 @@ public class ContributionsActivity extends AuthenticatedActivity
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
SharedPreferences sharedPref =
PreferenceManager.getDefaultSharedPreferences(this);
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
return new CursorLoader(this, ContributionsContentProvider.BASE_URI,
Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null,
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
int uploads = sharedPref.getInt(UPLOADS_SHOWING, 100);
return new CursorLoader(this, BASE_URI,
ALL_FIELDS, CONTRIBUTION_SELECTION, null,
CONTRIBUTION_SORT + "LIMIT " + uploads);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if (contributionsList.getAdapter() == null) {
contributionsList
.setAdapter(new ContributionsListAdapter(getApplicationContext(), cursor, 0));
contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(),
cursor, 0));
} else {
((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor);
}
@ -269,6 +281,7 @@ public class ContributionsActivity extends AuthenticatedActivity
return contributionsList.getAdapter().getCount();
}
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
compositeDisposable.add(
mediaWikiApi
@ -276,12 +289,10 @@ public class ContributionsActivity extends AuthenticatedActivity
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uploadCount ->
getSupportActionBar().setSubtitle(getResources()
uploadCount -> getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
uploadCount,
uploadCount)),
throwable -> Timber.e(throwable, "Fetching upload count failed")
uploadCount, uploadCount)),
t -> Timber.e(t, "Fetching upload count failed")
)
);
}
@ -337,8 +348,7 @@ public class ContributionsActivity extends AuthenticatedActivity
}
public static void startYourself(Context context) {
Intent contributionsIntent = new Intent(context, ContributionsActivity.class);
context.startActivity(contributionsIntent);
context.startActivity(new Intent(context, ContributionsActivity.class));
}
}

View file

@ -16,17 +16,20 @@ import dagger.android.AndroidInjection;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
public class ContributionsContentProvider extends ContentProvider{
import static android.content.UriMatcher.NO_MATCH;
import static fr.free.nrw.commons.contributions.Contribution.Table.ALL_FIELDS;
import static fr.free.nrw.commons.contributions.Contribution.Table.TABLE_NAME;
public class ContributionsContentProvider extends ContentProvider {
private static final int CONTRIBUTIONS = 1;
private static final int CONTRIBUTIONS_ID = 2;
public static final String AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
private static final String BASE_PATH = "contributions";
private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH);
public static final String AUTHORITY = "fr.free.nrw.commons.contributions.contentprovider";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, BASE_PATH, CONTRIBUTIONS);
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CONTRIBUTIONS_ID);
@ -44,25 +47,28 @@ public class ContributionsContentProvider extends ContentProvider{
return true;
}
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Contribution.Table.TABLE_NAME);
queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch(uriType) {
switch (uriType) {
case CONTRIBUTIONS:
cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case CONTRIBUTIONS_ID:
cursor = queryBuilder.query(db,
Contribution.Table.ALL_FIELDS,
ALL_FIELDS,
"_id = ?",
new String[] { uri.getLastPathSegment() },
new String[]{uri.getLastPathSegment()},
null,
null,
sortOrder
@ -82,6 +88,7 @@ public class ContributionsContentProvider extends ContentProvider{
return null;
}
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
@ -89,7 +96,7 @@ public class ContributionsContentProvider extends ContentProvider{
long id = 0;
switch (uriType) {
case CONTRIBUTIONS:
id = sqlDB.insert(Contribution.Table.TABLE_NAME, null, contentValues);
id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
@ -98,19 +105,20 @@ public class ContributionsContentProvider extends ContentProvider{
return Uri.parse(BASE_URI + "/" + id);
}
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int rows = 0;
int rows;
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch(uriType) {
switch (uriType) {
case CONTRIBUTIONS_ID:
Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
rows = db.delete(Contribution.Table.TABLE_NAME,
rows = db.delete(TABLE_NAME,
"_id = ?",
new String[] { uri.getLastPathSegment() }
new String[]{uri.getLastPathSegment()}
);
break;
default:
@ -120,6 +128,7 @@ public class ContributionsContentProvider extends ContentProvider{
return rows;
}
@SuppressWarnings("ConstantConditions")
@Override
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
@ -128,9 +137,9 @@ public class ContributionsContentProvider extends ContentProvider{
sqlDB.beginTransaction();
switch (uriType) {
case CONTRIBUTIONS:
for(ContentValues value: values) {
for (ContentValues value : values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(Contribution.Table.TABLE_NAME, null, value);
sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
@ -142,6 +151,7 @@ public class ContributionsContentProvider extends ContentProvider{
return values.length;
}
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
/*
@ -156,21 +166,19 @@ public class ContributionsContentProvider extends ContentProvider{
int rowsUpdated = 0;
switch (uriType) {
case CONTRIBUTIONS:
rowsUpdated = sqlDB.update(Contribution.Table.TABLE_NAME,
contentValues,
selection,
selectionArgs);
rowsUpdated = sqlDB.update(TABLE_NAME, contentValues, selection, selectionArgs);
break;
case CONTRIBUTIONS_ID:
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Contribution.Table.TABLE_NAME,
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
Contribution.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID");
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
break;
default:

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.contributions;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@ -29,17 +28,15 @@ import timber.log.Timber;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.app.Activity.RESULT_OK;
import static android.content.Context.MODE_PRIVATE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.View.GONE;
public class ContributionsListFragment extends DaggerFragment {
public interface SourceRefresher {
void refreshSource();
}
@BindView(R.id.contributionsList) GridView contributionsList;
@BindView(R.id.waitingMessage) TextView waitingMessage;
@BindView(R.id.emptyMessage) TextView emptyMessage;
private ContributionController controller;
@Override
@ -47,21 +44,21 @@ public class ContributionsListFragment extends DaggerFragment {
View v = inflater.inflate(R.layout.fragment_contributions, container, false);
ButterKnife.bind(this, v);
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
if(savedInstanceState != null) {
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
if (savedInstanceState != null) {
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
}
//TODO: Should this be in onResume?
SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE);
SharedPreferences prefs = getActivity().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Timber.d("Last Sync Timestamp: %s", lastModified);
if (lastModified.equals("")) {
waitingMessage.setVisibility(View.VISIBLE);
} else {
waitingMessage.setVisibility(View.GONE);
waitingMessage.setVisibility(GONE);
}
return v;
@ -90,7 +87,7 @@ public class ContributionsListFragment extends DaggerFragment {
//FIXME: must get the file data for Google Photos when receive the intent answer, in the onActivityResult method
super.onActivityResult(requestCode, resultCode, data);
if ( resultCode == RESULT_OK ) {
if (resultCode == RESULT_OK) {
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data);
@ -102,7 +99,7 @@ public class ContributionsListFragment extends DaggerFragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.menu_from_gallery:
//Gallery crashes before reach ShareActivity screen so must implement permissions check here
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@ -110,7 +107,7 @@ public class ContributionsListFragment extends DaggerFragment {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(getActivity(),
READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
!= PERMISSION_GRANTED) {
// Should we show an explanation?
if (shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE)) {
@ -160,14 +157,15 @@ public class ContributionsListFragment extends DaggerFragment {
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = " + permissions + " grant =" + grantResults);
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult: req code = " + " perm = "
+ permissions + " grant =" + grantResults);
switch (requestCode) {
// 1 = Storage allowed when gallery selected
case 1: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Call controller.startGalleryPick()");
controller.startGalleryPick();
}
@ -175,7 +173,7 @@ public class ContributionsListFragment extends DaggerFragment {
break;
// 2 = Location allowed when 'nearby places' selected
case 2: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
Timber.d("Location permission granted");
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
startActivity(nearbyIntent);
@ -220,6 +218,10 @@ public class ContributionsListFragment extends DaggerFragment {
}
protected void clearSyncMessage() {
waitingMessage.setVisibility(View.GONE);
waitingMessage.setVisibility(GONE);
}
public interface SourceRefresher {
void refreshSource();
}
}

View file

@ -28,9 +28,19 @@ import fr.free.nrw.commons.mwapi.LogEventResult;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
import static android.content.Context.MODE_PRIVATE;
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
import static fr.free.nrw.commons.contributions.Contribution.Table.COLUMN_FILENAME;
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String[] existsQuery = {COLUMN_FILENAME};
private static final String existsSelection = COLUMN_FILENAME + " = ?";
private static final ContentValues[] EMPTY = {};
private static int COMMIT_THRESHOLD = 10;
@SuppressWarnings("WeakerAccess")
@Inject MediaWikiApi mwApi;
public ContributionsSyncAdapter(Context context, boolean autoInitialize) {
@ -44,19 +54,16 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
return limit; // FIXME: Parameterize!
}
private static final String[] existsQuery = {Contribution.Table.COLUMN_FILENAME};
private static final String existsSelection = Contribution.Table.COLUMN_FILENAME + " = ?";
private boolean fileExists(ContentProviderClient client, String filename) {
Cursor cursor = null;
try {
cursor = client.query(ContributionsContentProvider.BASE_URI,
cursor = client.query(BASE_URI,
existsQuery,
existsSelection,
new String[]{filename},
""
);
return cursor.getCount() != 0;
return cursor != null && cursor.getCount() != 0;
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
@ -67,12 +74,12 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
}
@Override
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
public void onPerformSync(Account account, Bundle bundle, String authority,
ContentProviderClient contentProviderClient, SyncResult syncResult) {
((CommonsApplication) getContext().getApplicationContext()).injector().inject(this);
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
String user = account.name;
SharedPreferences prefs = getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
SharedPreferences prefs = getContext().getSharedPreferences("prefs", MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", "");
Date curTime = new Date();
LogEventResult result;
@ -106,13 +113,15 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
}
String thumbUrl = Utils.makeThumbBaseUrl(filename);
Date dateUpdated = image.getDateUpdated();
Contribution contrib = new Contribution(null, thumbUrl, filename, "", -1, dateUpdated, dateUpdated, user, "", "");
contrib.setState(Contribution.STATE_COMPLETED);
Contribution contrib = new Contribution(null, thumbUrl, filename,
"", -1, dateUpdated, dateUpdated, user,
"", "");
contrib.setState(STATE_COMPLETED);
imageValues.add(contrib.toContentValues());
if (imageValues.size() % COMMIT_THRESHOLD == 0) {
try {
contentProviderClient.bulkInsert(ContributionsContentProvider.BASE_URI, imageValues.toArray(new ContentValues[]{}));
contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@ -122,7 +131,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
if (imageValues.size() != 0) {
try {
contentProviderClient.bulkInsert(ContributionsContentProvider.BASE_URI, imageValues.toArray(new ContentValues[]{}));
contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY));
} catch (RemoteException e) {
throw new RuntimeException(e);
}

View file

@ -280,13 +280,13 @@ public class MediaDetailFragment extends DaggerFragment {
categoryContainer.removeAllViews();
// @fixme add the category items
for (String cat : categoryNames) {
View catLabel = buildCatLabel(cat);
View catLabel = buildCatLabel(cat, categoryContainer);
categoryContainer.addView(catLabel);
}
}
private View buildCatLabel(final String catName) {
final View item = getLayoutInflater(null).inflate(R.layout.detail_category_item, null, false);
private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
final CompatTextView textView = (CompatTextView)item.findViewById(R.id.mediaDetailCategoryItemText);
textView.setText(catName);

View file

@ -1,11 +1,8 @@
package fr.free.nrw.commons.media;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.DownloadManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
@ -39,20 +36,14 @@ import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.mwapi.EventLog;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.Context.DOWNLOAD_SERVICE;
import static android.content.Intent.ACTION_VIEW;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static fr.free.nrw.commons.CommonsApplication.EVENT_SHARE_ATTEMPT;
public class MediaDetailPagerFragment extends DaggerFragment implements ViewPager.OnPageChangeListener {
public interface MediaDetailProvider {
Media getMediaAtPosition(int i);
int getTotalMediaCount();
void notifyDatasetChanged();
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
}
@Inject MediaWikiApi mwApi;
@Inject SessionManager sessionManager;
@ -68,30 +59,10 @@ public class MediaDetailPagerFragment extends DaggerFragment implements ViewPage
this.editable = editable;
}
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
private class MediaDetailAdapter extends FragmentStatePagerAdapter {
public MediaDetailAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
if (i == 0) {
// See bug https://code.google.com/p/android/issues/detail?id=27526
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
}
return MediaDetailFragment.forMedia(i, editable);
}
@Override
public int getCount() {
return ((MediaDetailProvider) getActivity()).getTotalMediaCount();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false);
pager = (ViewPager) view.findViewById(R.id.mediaDetailsPager);
pager.addOnPageChangeListener(this);
@ -137,7 +108,8 @@ public class MediaDetailPagerFragment extends DaggerFragment implements ViewPage
switch (item.getItemId()) {
case R.id.menu_share_current_image:
// Share - this is just logs it, intent set in onCreateOptionsMenu, around line 252
EventLog.schema(CommonsApplication.EVENT_SHARE_ATTEMPT, getContext().getApplicationContext(), mwApi)
CommonsApplication app = (CommonsApplication) getActivity().getApplication();
EventLog.schema(EVENT_SHARE_ATTEMPT, getContext().getApplicationContext(), mwApi)
.param("username", sessionManager.getCurrentAccount().name)
.param("filename", m.getFilename())
.log();
@ -145,7 +117,7 @@ public class MediaDetailPagerFragment extends DaggerFragment implements ViewPage
case R.id.menu_browser_current_image:
// View in browser
Intent viewIntent = new Intent();
viewIntent.setAction(Intent.ACTION_VIEW);
viewIntent.setAction(ACTION_VIEW);
viewIntent.setData(m.getFilePageTitle().getMobileUri());
startActivity(viewIntent);
return true;
@ -191,17 +163,13 @@ public class MediaDetailPagerFragment extends DaggerFragment implements ViewPage
req.allowScanningByMediaScanner();
req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
!(ContextCompat.checkSelfPermission(getContext(),
Manifest.permission.READ_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED)) {
Snackbar.make(getView(), R.string.storage_permission_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, view -> ActivityCompat.requestPermissions(getActivity(),
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1)).show();
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
view -> ActivityCompat.requestPermissions(getActivity(),
new String[]{READ_EXTERNAL_STORAGE}, 1)).show();
} else {
final DownloadManager manager = (DownloadManager) getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(req);
((DownloadManager) getActivity().getSystemService(DOWNLOAD_SERVICE)).enqueue(req);
}
}
@ -276,6 +244,39 @@ public class MediaDetailPagerFragment extends DaggerFragment implements ViewPage
@Override
public void onPageScrollStateChanged(int i) {
}
public interface MediaDetailProvider {
Media getMediaAtPosition(int i);
int getTotalMediaCount();
void notifyDatasetChanged();
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
}
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
private class MediaDetailAdapter extends FragmentStatePagerAdapter {
public MediaDetailAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int i) {
if (i == 0) {
// See bug https://code.google.com/p/android/issues/detail?id=27526
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
}
return MediaDetailFragment.forMedia(i, editable);
}
@Override
public int getCount() {
return ((MediaDetailProvider) getActivity()).getTotalMediaCount();
}
}
}

View file

@ -33,7 +33,7 @@ public class CategoryModifier extends PageModifier {
JSONArray categories;
categories = params.optJSONArray(PARAM_CATEGORIES);
StringBuffer categoriesString = new StringBuffer();
StringBuilder categoriesString = new StringBuilder();
for(int i=0; i < categories.length(); i++) {
String category = categories.optString(i);
categoriesString.append("\n[[Category:").append(category).append("]]");

View file

@ -48,7 +48,7 @@ public class ModifierSequence {
}
public String getEditSummary() {
StringBuffer editSummary = new StringBuffer();
StringBuilder editSummary = new StringBuilder();
for(PageModifier modifier: modifiers) {
editSummary.append(modifier.getEditSumary()).append(" ");
}

View file

@ -32,6 +32,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.PageTitle;
@ -236,8 +237,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
return categories;
})
.flatMapObservable(list -> Observable.fromIterable(list));
}).flatMapObservable(Observable::fromIterable);
}
@Override
@ -266,15 +266,14 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
return categories;
})
.flatMapObservable(list -> Observable.fromIterable(list));
}).flatMapObservable(Observable::fromIterable);
}
@Override
@NonNull
public Observable<String> searchTitles(String title, int searchCatsLimit) {
return Single.fromCallable(() -> {
ArrayList<ApiResult> categoryNodes = null;
return Single.fromCallable((Callable<List<String>>) () -> {
ArrayList<ApiResult> categoryNodes;
try {
categoryNodes = api.action("query")
@ -288,7 +287,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.getNodes("/api/query/search/p/@title");
} catch (IOException e) {
Timber.e("Failed to obtain searchTitles", e);
return new ArrayList();
return Collections.emptyList();
}
if (categoryNodes == null) {
@ -303,8 +302,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
}
return titleCategories;
})
.flatMapObservable(list -> Observable.fromIterable(list));
}).flatMapObservable(Observable::fromIterable);
}
@Override

View file

@ -63,10 +63,10 @@ public class NearbyInfoDialog extends OverlayDialog {
overflowButton.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
overflowButton.setOnClickListener(this::popupMenuListener);
overflowButton.setOnClickListener(v -> popupMenuListener());
}
private void popupMenuListener(View v) {
private void popupMenuListener() {
PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton);
popupMenu.inflate(R.menu.nearby_info_dialog_options);

View file

@ -24,15 +24,13 @@ import fr.free.nrw.commons.nearby.NearbyActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import timber.log.Timber;
public class NavigationBaseActivity extends BaseActivity
public abstract class NavigationBaseActivity extends BaseActivity
implements NavigationView.OnNavigationItemSelectedListener {
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.navigation_view)
NavigationView navigationView;
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
@ -120,17 +118,11 @@ public class NavigationBaseActivity extends BaseActivity
new AlertDialog.Builder(this)
.setMessage(R.string.logout_verification)
.setCancelable(false)
.setPositiveButton(R.string.yes, (dialog, which) ->
((CommonsApplication) getApplicationContext())
.clearApplicationData(NavigationBaseActivity.this, () -> {
Timber.d("Logout complete callback received.");
Intent nearbyIntent = new Intent(
NavigationBaseActivity.this, LoginActivity.class);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(nearbyIntent);
finish();
}))
.setPositiveButton(R.string.yes, (dialog, which) -> {
BaseLogoutListener logoutListener = new BaseLogoutListener();
CommonsApplication app = (CommonsApplication) getApplication();
app.clearApplicationData(this, logoutListener);
})
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.show();
return true;
@ -139,7 +131,16 @@ public class NavigationBaseActivity extends BaseActivity
}
}
public interface LogoutListener {
void onLogoutComplete();
private class BaseLogoutListener implements CommonsApplication.LogoutListener {
@Override
public void onLogoutComplete() {
Timber.d("Logout complete callback received.");
Intent nearbyIntent = new Intent(
NavigationBaseActivity.this, LoginActivity.class);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(nearbyIntent);
finish();
}
}
}

View file

@ -77,8 +77,8 @@ public class MultipleUploadListFragment extends DaggerFragment {
public View getView(int i, View view, ViewGroup viewGroup) {
UploadHolderView holder;
if(view == null) {
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
if (view == null) {
view = LayoutInflater.from(getContext()).inflate(R.layout.layout_upload_item, viewGroup, false);
holder = new UploadHolderView();
holder.image = (SimpleDraweeView) view.findViewById(R.id.uploadImage);
holder.title = (TextView) view.findViewById(R.id.uploadTitle);
@ -94,17 +94,17 @@ public class MultipleUploadListFragment extends DaggerFragment {
.build());
view.setTag(holder);
} else {
holder = (UploadHolderView)view.getTag();
holder = (UploadHolderView) view.getTag();
}
Contribution up = (Contribution)this.getItem(i);
Contribution up = (Contribution) this.getItem(i);
if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
if (holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
holder.image.setImageURI(up.getLocalUri().toString());
holder.imageUri = up.getLocalUri();
}
if(!imageOnlyMode) {
if (!imageOnlyMode) {
holder.overlay.setVisibility(View.VISIBLE);
holder.title.setText(up.getFilename());
} else {
@ -134,21 +134,21 @@ public class MultipleUploadListFragment extends DaggerFragment {
int screenHeight = screenMetrics.heightPixels;
int picWidth = Math.min((int) Math.sqrt(screenWidth * screenHeight / count), screenWidth);
picWidth = Math.min((int)(192 * screenMetrics.density), Math.max((int) (120 * screenMetrics.density), picWidth / 48 * 48));
int picHeight = Math.min(picWidth, (int)(192 * screenMetrics.density)); // Max Height is same as Contributions list
picWidth = Math.min((int) (192 * screenMetrics.density), Math.max((int) (120 * screenMetrics.density), picWidth / 48 * 48));
int picHeight = Math.min(picWidth, (int) (192 * screenMetrics.density)); // Max Height is same as Contributions list
return new Point(picWidth, picHeight);
}
public void notifyDatasetChanged() {
if(photosAdapter != null) {
if (photosAdapter != null) {
photosAdapter.notifyDataSetChanged();
}
}
public void setImageOnlyMode(boolean mode) {
imageOnlyMode = mode;
if(imageOnlyMode) {
if (imageOnlyMode) {
baseTitle.setVisibility(View.GONE);
} else {
baseTitle.setVisibility(View.VISIBLE);
@ -159,13 +159,13 @@ public class MultipleUploadListFragment extends DaggerFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, null);
photosGrid = (GridView)view.findViewById(R.id.multipleShareBackground);
baseTitle = (EditText)view.findViewById(R.id.multipleBaseTitle);
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false);
photosGrid = (GridView) view.findViewById(R.id.multipleShareBackground);
baseTitle = (EditText) view.findViewById(R.id.multipleBaseTitle);
photosAdapter = new PhotoDisplayAdapter();
photosGrid.setAdapter(photosAdapter);
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
photoSize = calculatePicDimension(detailProvider.getTotalMediaCount());
photosGrid.setColumnWidth(photoSize.x);
@ -188,7 +188,7 @@ public class MultipleUploadListFragment extends DaggerFragment {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.menu_upload_multiple:
multipleUploadInitiatedHandler.OnMultipleUploadInitiated();
return true;
@ -200,7 +200,7 @@ public class MultipleUploadListFragment extends DaggerFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
setHasOptionsMenu(true);
@ -213,12 +213,12 @@ public class MultipleUploadListFragment extends DaggerFragment {
@Override
public void onTextChanged(CharSequence charSequence, int i1, int i2, int i3) {
for(int i = 0; i < detailProvider.getTotalMediaCount(); i++) {
for (int i = 0; i < detailProvider.getTotalMediaCount(); i++) {
Contribution up = (Contribution) detailProvider.getMediaAtPosition(i);
Boolean isDirty = (Boolean)up.getTag("isDirty");
if(isDirty == null || !isDirty) {
if(!TextUtils.isEmpty(charSequence)) {
up.setFilename(charSequence.toString() + " - " + ((Integer)up.getTag("sequence") + 1));
Boolean isDirty = (Boolean) up.getTag("isDirty");
if (isDirty == null || !isDirty) {
if (!TextUtils.isEmpty(charSequence)) {
up.setFilename(charSequence.toString() + " - " + ((Integer) up.getTag("sequence") + 1));
} else {
up.setFilename("");
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.upload;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
@ -40,13 +39,10 @@ import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
public class SingleUploadFragment extends DaggerFragment {
private SharedPreferences prefs;
private String license;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
public class SingleUploadFragment extends DaggerFragment {
@BindView(R.id.titleEdit) EditText titleEdit;
@BindView(R.id.descEdit) EditText descEdit;
@ -54,13 +50,15 @@ public class SingleUploadFragment extends DaggerFragment {
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
private SharedPreferences prefs;
private String license;
private OnUploadActionInitiated uploadActionInitiatedHandler;
private TitleTextWatcher textWatcher = new TitleTextWatcher();
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.activity_share, menu);
if(titleEdit != null) {
if (titleEdit != null) {
menu.findItem(R.id.menu_upload_single).setEnabled(titleEdit.getText().length() != 0);
}
}
@ -90,7 +88,7 @@ public class SingleUploadFragment extends DaggerFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_single_upload, null);
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
ButterKnife.bind(this, rootView);
@ -113,10 +111,10 @@ public class SingleUploadFragment extends DaggerFragment {
Timber.d(license);
ArrayAdapter<String> adapter;
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",false)) {
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme", false)) {
// dark theme
adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_dropdown_item, licenseItems);
}else {
} else {
// light theme
adapter = new ArrayAdapter<>(getActivity(), R.layout.light_simple_spinner_dropdown_item, licenseItems);
}
@ -147,26 +145,27 @@ public class SingleUploadFragment extends DaggerFragment {
super.onDestroyView();
}
@OnItemSelected(R.id.licenseSpinner) void onLicenseSelected(AdapterView<?> parent, View view, int position, long id) {
@OnItemSelected(R.id.licenseSpinner)
void onLicenseSelected(AdapterView<?> parent, View view, int position, long id) {
String licenseName = parent.getItemAtPosition(position).toString();
// Set selected color to white because it should be readable on random images.
TextView selectedText = (TextView) licenseSpinner.getChildAt(0);
if (selectedText != null ) {
if (selectedText != null) {
selectedText.setTextColor(Color.WHITE);
selectedText.setBackgroundColor(Color.TRANSPARENT);
}
String license;
if(getString(R.string.license_name_cc0).equals(licenseName)) {
if (getString(R.string.license_name_cc0).equals(licenseName)) {
license = Prefs.Licenses.CC0;
} else if(getString(R.string.license_name_cc_by).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_3;
} else if(getString(R.string.license_name_cc_by_sa).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by_sa).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_SA_3;
} else if(getString(R.string.license_name_cc_by_four).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by_four).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_4;
} else if(getString(R.string.license_name_cc_by_sa_four).equals(licenseName)) {
} else if (getString(R.string.license_name_cc_by_sa_four).equals(licenseName)) {
license = Prefs.Licenses.CC_BY_SA_4;
} else {
throw new IllegalStateException("Unknown licenseName: " + licenseName);
@ -178,10 +177,9 @@ public class SingleUploadFragment extends DaggerFragment {
editor.commit();
}
@OnTouch(R.id.share_license_summary) boolean showLicence(View view, MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
@OnTouch(R.id.share_license_summary)
boolean showLicence(View view, MotionEvent motionEvent) {
if (motionEvent.getActionMasked() == ACTION_DOWN) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(licenseUrlFor(license)));
@ -192,7 +190,8 @@ public class SingleUploadFragment extends DaggerFragment {
}
}
@OnClick(R.id.titleDescButton) void setTitleDescButton() {
@OnClick(R.id.titleDescButton)
void setTitleDescButton() {
//Retrieve last title and desc entered
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
String title = titleDesc.getString("Title", "");
@ -206,57 +205,41 @@ public class SingleUploadFragment extends DaggerFragment {
/**
* Copied from https://stackoverflow.com/a/26269435/8065933
*/
@OnTouch
(R.id.titleEdit) boolean titleInfo(View view, MotionEvent motionEvent) {
@OnTouch(R.id.titleEdit)
boolean titleInfo(View view, MotionEvent motionEvent) {
//Should replace right with end to support different right-to-left languages as well
final int value = titleEdit.getRight() - titleEdit.getCompoundDrawables()[2].getBounds().width();
if (motionEvent.getAction() == motionEvent.ACTION_UP && motionEvent.getRawX() >= value) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.media_detail_title);
builder.setMessage(R.string.title_info);
builder.setCancelable(true);
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.media_detail_title)
.setMessage(R.string.title_info)
.setCancelable(true)
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
.create()
.show();
return true;
}
return false;
}
@OnTouch
(R.id.descEdit) boolean descriptionInfo(View view, MotionEvent motionEvent) {
@OnTouch(R.id.descEdit)
boolean descriptionInfo(View view, MotionEvent motionEvent) {
final int value = descEdit.getRight() - descEdit.getCompoundDrawables()[2].getBounds().width();
if (motionEvent.getAction() == motionEvent.ACTION_UP && motionEvent.getRawX() >= value) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.media_detail_description);
builder.setMessage(R.string.description_info);
builder.setCancelable(true);
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.media_detail_description)
.setMessage(R.string.description_info)
.setCancelable(true)
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
.create()
.show();
return true;
}
return false;
}
private void setLicenseSummary(String license) {
licenseSummaryView.setText(getString(R.string.share_license_summary, getString(Utils.licenseNameFor(license))));
}
@ -297,16 +280,22 @@ public class SingleUploadFragment extends DaggerFragment {
throw new RuntimeException("Unrecognized license value: " + license);
}
public interface OnUploadActionInitiated {
void uploadActionInitiated(String title, String description);
}
private class TitleTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { }
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
}
@Override
public void afterTextChanged(Editable editable) {
if(getActivity() != null) {
if (getActivity() != null) {
getActivity().invalidateOptionsMenu();
}
}

View file

@ -15,7 +15,7 @@ public class FileUtils {
* @return the content of the file
*/
public static String readFromResource(String fileName) throws IOException {
StringBuffer buffer = new StringBuffer();
StringBuilder buffer = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(

View file

@ -47,6 +47,8 @@
<string name="categories_search_text_hint">تصنيفات البحث</string>
<string name="menu_save_categories">احفظ</string>
<string name="refresh_button">أنعش</string>
<string name="enable_gps">تفعيل GPS</string>
<string name="contributions_subtitle_zero">لا مرفوعات بعد</string>
<string name="categories_not_found">لا توجد تصنيفات تطابق %1$s</string>
<string name="categories_skip_explanation">أضف التصانيف لتسهل اكتشاف صورك على ويكيميديا كومنز.\n\nابدأ الكتاب لتضيف التصانيف.\nانقر هذه الرسالة لتتجاوز هذه الخطوة.</string>
<string name="categories_activity_title">تصنيفات</string>
@ -81,10 +83,22 @@
<string name="detail_license_empty">ترخيص غير معلوم</string>
<string name="menu_refresh">تحديث</string>
<string name="ok">موافق</string>
<string name="title_activity_nearby">الأماكن القريبة</string>
<string name="warning">تحذير</string>
<string name="yes">نعم</string>
<string name="no">لا</string>
<string name="media_detail_title">العنوان</string>
<string name="media_detail_media_title">عنوان الوسيط</string>
<string name="media_detail_description">الوصف</string>
<string name="media_detail_license">الترخيص</string>
<string name="media_detail_coordinates">الإحداثيات</string>
<string name="use_wikidata">استخدم ويكي بيانات</string>
<string name="maximum_limit">الحد الأقصى</string>
<string name="navigation_item_home">الرئيسية</string>
<string name="navigation_item_upload">رفع</string>
<string name="navigation_item_nearby">بالقرب من هنا</string>
<string name="navigation_item_about">حول</string>
<string name="navigation_item_settings">الإعدادات</string>
<string name="navigation_item_logout">تسجيل الخروج</string>
<string name="nearby_info_menu_commons_article">صفحة ملف كومنز</string>
</resources>

View file

@ -85,6 +85,7 @@
<string name="menu_retry_upload">পুনঃচেষ্টা করুন</string>
<string name="menu_cancel_upload">বাতিল</string>
<string name="share_license_summary">এই ছবিটি %1$s এর অধীনে লাইসেন্স করা হবে</string>
<string name="media_upload_policy">এই চিত্র জমা দেয়ার দ্বারা, আমি ঘোষণা দিচ্ছি যে এটি আমার নিজস্ব কাজ, এতে কপিরাইটযুক্ত উপাদান নেই বা এটই সেলফি নয়, এবং &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines/bn\"&gt;উইকিমিডিয়া কমন্স নীতি&lt;/a&gt; অনুসরণ করে।</string>
<string name="menu_download">ডাউনলোড</string>
<string name="preference_license">লাইসেন্স</string>
<string name="use_previous">পূর্ববর্তী শিরোনাম/বিবরণ ব্যবহার করুন</string>
@ -148,6 +149,7 @@
<string name="media_detail_uploaded_date">আপলোডের তারিখ</string>
<string name="media_detail_license">লাইসেন্স</string>
<string name="media_detail_coordinates">স্থানাঙ্কসমূহ</string>
<string name="media_detail_coordinates_empty">কিছু দেয়া হয়নি</string>
<string name="become_a_tester_title">বিটা টেস্টার হোন</string>
<string name="use_wikidata">উইকিউপাত্ত ব্যবহার করুন</string>
<string name="use_wikidata_summary">(সতর্কতা: এটি নিষ্ক্রিয় করা অধিক পরিমাণে মোবাইল ডেটা খরচ হওয়ার কারণ হতে পারে)</string>
@ -166,6 +168,7 @@
<string name="welcome_image_rainbow_bridge">রংধনুর সেতু</string>
<string name="welcome_image_tulip">টিউলিপ</string>
<string name="welcome_image_no_selfies">কোন সেলফি নয়</string>
<string name="welcome_image_proprietary">মালিকানা চিত্র</string>
<string name="welcome_image_welcome_wikipedia">উইকিপিডিয়ায় স্বাগতম</string>
<string name="welcome_image_welcome_copyright">স্বাগতম কপিরাইট</string>
<string name="welcome_image_sydney_opera_house">সিডনি অপেরা হাউস</string>

View file

@ -111,4 +111,11 @@
<string name="warning">Tembe kem</string>
<string name="yes">E</string>
<string name="no"></string>
<string name="media_detail_title">Sername</string>
<string name="media_detail_license">Lisans</string>
<string name="media_detail_coordinates">Koordinati</string>
<string name="cancel">Bıtexelne</string>
<string name="navigation_drawer_open">Ake</string>
<string name="navigation_item_home">Keye</string>
<string name="navigation_item_upload">Bar ke</string>
</resources>

View file

@ -14,7 +14,7 @@
<string name="authentication_failed">Tunnistautuminen epäonnistui!</string>
<string name="uploading_started">Tallentaminen aloitettiin!</string>
<string name="upload_completed_notification_title">%1$s tallennettiin!</string>
<string name="upload_completed_notification_text">Napauta katsoaksesi tallennuksen</string>
<string name="upload_completed_notification_text">Napauta katsoaksesi tallennusta</string>
<string name="upload_progress_notification_title_start">Aloitetaan tiedoston %1$s tallennusta</string>
<string name="upload_progress_notification_title_in_progress">Tallennetaan %1$s</string>
<string name="upload_progress_notification_title_finishing">%1$s tallennettu</string>
@ -29,7 +29,7 @@
<string name="contribution_state_failed">Epäonnistui</string>
<string name="contribution_state_in_progress">%1$d%% valmis</string>
<string name="contribution_state_starting">Tallennetaan</string>
<string name="menu_from_gallery">Olemassaoleva</string>
<string name="menu_from_gallery">Galleriasta</string>
<string name="menu_from_camera">Ota kuva</string>
<string name="menu_nearby">Lähistöllä</string>
<string name="provider_contributions">Omat tallennukset</string>
@ -39,7 +39,7 @@
<string name="share_description_hint">Kuvaus</string>
<string name="login_failed_network">Kirjautuminen epäonnistui - verkkovirhe</string>
<string name="login_failed_username">Kirjautuminen epäonnistui - tarkista käyttäjätunnus</string>
<string name="login_failed_password">Kirjautuminen epäonnistui - tarkista salasana</string>
<string name="login_failed_password">Kirjautuminen epäonnistui - tarkista salasanasi</string>
<string name="login_failed_throttled">Liikaa epäonnistuneita yrityksiä. Yritä uudelleen parin minuutin kuluttua.</string>
<string name="login_failed_blocked">Pahoittelut, tämä käyttäjä on estetty Commonsissa</string>
<string name="login_failed_2fa_needed">Anna kaksivaiheisen tunnistuksen koodi.</string>
@ -54,18 +54,18 @@
<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" fuzzy="true">
<item quantity="zero">Ei tallennuksia</item>
<item quantity="one">1 tallennus</item>
<plurals name="contributions_subtitle">
<item quantity="zero">\@string/contributions_subtitle_zero</item>
<item quantity="one">%d tallennus</item>
<item quantity="other">%d tallennusta</item>
</plurals>
<plurals name="starting_multiple_uploads" fuzzy="true">
<item quantity="one">1 tallennus alkaa</item>
<item quantity="other">%d tallennusta alkaa</item>
<plurals name="starting_multiple_uploads">
<item quantity="one">aloitetaan %d tallennus</item>
<item quantity="other">aloitetaan %d tallennusta</item>
</plurals>
<plurals name="multiple_uploads_title" fuzzy="true">
<item quantity="one">1 lataus</item>
<item quantity="other">%d latausta</item>
<plurals name="multiple_uploads_title">
<item quantity="one">%d tallennus</item>
<item quantity="other">%d tallennusta</item>
</plurals>
<string name="categories_not_found">Luokkaa %1$s ei löytynyt</string>
<string name="categories_skip_explanation">Lisää luokkia tehdäksesi kuvistasi helpommin löydettäviä.\n\nAloita kirjoittaminen lisätäksesi luokkia.\nNapauta tätä viestiä (tai paina takaisin) ohittaaksesi tämän vaiheen.</string>
@ -73,7 +73,7 @@
<string name="title_activity_settings">Asetukset</string>
<string name="title_activity_signup">Rekisteröidy</string>
<string name="menu_about">Tietoja</string>
<string name="about_license" fuzzy="true">Tämä on vapaan lähdekoodin ohjelmisto, joka on julkaistu &lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\"&gt;Apache License v2&lt;/a&gt; -lisenssin alaisena. Wikimedia Commons ja sen logo ovat Wikimedia Foundationin tavaramerkkejä ja niitä käytetään Wikimedia Foundationin luvalla. Emme ole hyväksyttyjä tai sidoksissa Wikimedia Foundationioniin.</string>
<string name="about_license">Tämä on vapaan lähdekoodin ohjelmisto, joka on julkaistu &lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\"&gt;Apache License v2&lt;/a&gt; -lisenssin alaisena. Wikimedia Commons ja sen logo ovat Wikimedia Foundationin tavaramerkkejä ja niitä käytetään Wikimedia Foundationin luvalla. Emme ole hyväksyttyjä tai sidoksissa Wikimedia Foundationioniin.</string>
<string name="about_improve">&lt;a href=\"https://github.com/commons-app/apps-android-commons\"&gt;Lähde&lt;/a&gt; ja &lt;a href=\"https://commons-app.github.io/\"&gt;nettisivusto&lt;/a&gt; GitHubissa. Luo uusi &lt;a href=\"https://github.com/commons-app/apps-android-commons/issues\"&gt;GitHub-issue&lt;/a&gt; bugiraporteille ja ehdotuksille.</string>
<string name="about_privacy_policy">&lt;a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\"&gt;Yksityisyydensuoja&lt;/a&gt;</string>
<string name="about_credits">&lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\"&gt;Tekijät&lt;/a&gt;</string>
@ -86,6 +86,7 @@
<string name="menu_retry_upload">Yritä uudelleen</string>
<string name="menu_cancel_upload">Peruuta</string>
<string name="share_license_summary">Tiedosto tallennetaan lisenssin %1$s ehtojen mukaisesti.</string>
<string name="media_upload_policy">Lisäämällä kuvan, ilmoitan tämän olevan oma työ ja että se ei sisällä tekijänoikeuden alaista materiaalia tai selfietä ja muuten noudattaa &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;Wikimedia Commons policies&lt;/a&gt;.</string>
<string name="menu_download">Lataa</string>
<string name="preference_license">Lisenssi</string>
<string name="use_previous">Käytä edellistä otsikkoa/kuvausta</string>
@ -115,15 +116,15 @@
<string name="license_name_cc_zero">CC0</string>
<string name="tutorial_1_text">Wikimedia Commonsiin on tallennettu lähes kaikki Wikipediassa käytetyt kuvat.</string>
<string name="tutorial_1_subtext">Kuvasi auttavat ihmisiä kaikkialta maailmasta ymmärtämään maailmaa!</string>
<string name="tutorial_2_text">Tallenna kuvia, jotka sinä itse olet kuvannot tai luonut:</string>
<string name="tutorial_2_text">Tallenna kuvia, jotka sinä itse olet ottanut tai luonut:</string>
<string name="tutorial_2_subtext">- Luontokohteet (kukat, eläimet, vuoret)\n- Hyödylliset esineet (polkupyörät, rautatieasemat)\n- Kuuluisat henkilöt (kaupunginjohtajasi, tapaamasi olympiaurheilijat)</string>
<string name="tutorial_3_text">Ä tallenna seuraavia:</string>
<string name="tutorial_3_text">Ä tallenna seuraavia:</string>
<string name="tutorial_3_subtext">- Selfiet tai kuvat ystävistäsi\n- Netistä ladatut kuvat\n- Kuvakaappaukset kaupallisista sovelluksista</string>
<string name="tutorial_4_text">Tallennusesimerkki:</string>
<string name="tutorial_4_subtext">- Nimi: Sydneyn operatalo\n- Kuvaus: Sydneyn oopperatalo katsottuna lahden toisella puolella\n- Luokat: Sydneyn oopperatalo, Sydneyn oopperatalo lännestä, Sydneyn oopperatalo remote views</string>
<string name="welcome_wikipedia_text">Herätä Wikipedia-artikkelit eloon kuvillasi! Tuo kuvasi Wikipediaan.</string>
<string name="welcome_wikipedia_subtext">Wikipedian kuvat tulevat Wikimedia Commonsista.</string>
<string name="welcome_copyright_text">Kuvat auttavat useita ihmisiä ympäri maailmaa artikkeleiden ymmärtämisessä.</string>
<string name="welcome_copyright_text">Kuvasi auttavat useita ihmisiä ympäri maailmaa artikkeleiden ymmärtämisessä.</string>
<string name="welcome_copyright_subtext">Vältä tekijänoikeuksien alaista materiaalia, kuten julisteita, kirjan kansia ja useimpia Internetistä löydettyjä kuvia.</string>
<string name="welcome_final_text">Luuletko ymmärtäneesi tämän?</string>
<string name="welcome_final_button_text">Kyllä!</string>
@ -134,8 +135,9 @@
<string name="detail_license_empty">Tuntematon lisenssi</string>
<string name="menu_refresh">Päivitä</string>
<string name="storage_permission_rationale">Vaadittu oikeus: Ulkoisen tallennustilan luku. Appi ei toimi ilman tätä oikeutta.</string>
<string name="location_permission_rationale">Valinnainen lupa: Saada tämänhetkinen sijainti loukkasuosituksia varten.</string>
<string name="ok">OK</string>
<string name="title_activity_nearby">Läheiset paikat</string>
<string name="title_activity_nearby">Lähellä olevat paikat</string>
<string name="no_nearby">Lähistöltä ei löytynyt paikkoja</string>
<string name="warning">Varoitus</string>
<string name="file_exists">Tämä tiedosto on jo Wikimedia Commonsissa. Haluatko varmasti jatkaa?</string>
@ -147,14 +149,24 @@
<string name="media_detail_uploaded_date">Tallennuspäivämäärä</string>
<string name="media_detail_license">Lisenssi</string>
<string name="media_detail_coordinates">Koordinaatit</string>
<string name="media_detail_coordinates_empty">ei annettu</string>
<string name="become_a_tester_title">Ryhdy beetatestaajaksi</string>
<string name="use_wikidata">Käytä Wikidataa</string>
<string name="use_wikidata_summary">(Varoitus: poiskytkeminen voi aiheuttaa suuren mobiilidatankäytön)</string>
<string name="maximum_limit">Maksimimäärä</string>
<string name="maximum_limit_alert">Ei voida näyttää enempää, kuin 500</string>
<string name="login_failed_2fa_not_supported">Kaksivaiheinen tunnistus ei ole vielä tuettu.</string>
<string name="logout_verification">Haluatko kirjautua ulos?</string>
<string name="logout_verification">Haluatko varmasti kirjautua ulos?</string>
<string name="commons_logo">Commons Logo</string>
<string name="background_image">Taustakuva</string>
<string name="no_image_found">Kuvaa ei löytynyt</string>
<string name="upload_image">Lataa kuva</string>
<string name="welcome_image_mount_zao">Zao-vuori</string>
<string name="welcome_image_rainbow_bridge">Sateenkaarisilta</string>
<string name="welcome_image_tulip">Tulppaani</string>
<string name="welcome_image_no_selfies">Ei selfieitä</string>
<string name="welcome_image_welcome_wikipedia">Tervetuloa Wikipediaan</string>
<string name="welcome_image_welcome_copyright">Tervetuloa tekijänoikeus</string>
<string name="welcome_image_sydney_opera_house">Sydneyn oopperatalo</string>
<string name="cancel">Peru</string>
<string name="navigation_drawer_open">Avaa</string>
@ -166,6 +178,9 @@
<string name="navigation_item_settings">Asetukset</string>
<string name="navigation_item_feedback">Palaute</string>
<string name="navigation_item_logout">Kirjaudu ulos</string>
<string name="navigation_item_info">Opas</string>
<string name="nearby_needs_permissions">Lähellä olevia paikkoja ei voida näyttää ilman sijaintilupaa</string>
<string name="nearby_info_menu_commons_article">Commons-tiedostosivu</string>
<string name="nearby_info_menu_wikidata_article">Wikidata-kohde</string>
<string name="title_info">Tiedoston yksilöllinen ja kuvaava otsikko, jota käytetään tiedostonimenä. Voit käyttää tavallista kieltä välilyönnein. Älä sisällytä tiedoston päätettä.</string>
</resources>

View file

@ -114,7 +114,7 @@
<string name="license_name_cc_by_sa_4_0">CC BY-SA 4.0</string>
<string name="license_name_cc_by_4_0">CC BY 4.0</string>
<string name="license_name_cc_zero">CC Zéro</string>
<string name="tutorial_1_text">Wikimédia Communs héberge la plupart des images qui sont utilisées dans Wikipédia</string>
<string name="tutorial_1_text">Wikimedia Commons héberge la plupart des images qui sont utilisées dans Wikipédia.</string>
<string name="tutorial_1_subtext">Vos images aident à éduquer les gens dans le monde entier!</string>
<string name="tutorial_2_text">Veuillez téléverser des images qui sont prises ou créées entièrement par vous :</string>
<string name="tutorial_2_subtext">- Objets naturels (fleurs, animaux, montagnes) \n- Objets utiles (bicyclettes, gares ferroviaires) \n- Personnes célèbres (votre maire, les athlètes olympiques que vous avez rencontrés)</string>
@ -140,7 +140,7 @@
<string name="title_activity_nearby">Endroits à proximité</string>
<string name="no_nearby">Rien trouvé dans le voisinage</string>
<string name="warning">Avertissement</string>
<string name="file_exists">Ce fichier existe déjà sur Communs. Êtes-vous sûr de vouloir continuer?</string>
<string name="file_exists">Ce fichier existe déjà sur Commons. Êtes-vous sûr de vouloir continuer?</string>
<string name="yes">Oui</string>
<string name="no">Non</string>
<string name="media_detail_title">Titre</string>
@ -162,7 +162,7 @@
<string name="set_limit">Fixer la limite de téléversement récent</string>
<string name="login_failed_2fa_not_supported">Lauthentification à deux facteurs nest pas prise en charge pour le moment.</string>
<string name="logout_verification">Voulez-vous vraiment vous déconnecter?</string>
<string name="commons_logo">Logo de Communs</string>
<string name="commons_logo">Logo de Commons</string>
<string name="background_image">Image de fond</string>
<string name="mediaimage_failed">Échec sur limage du média</string>
<string name="no_image_found">Aucune image trouvée</string>
@ -189,7 +189,7 @@
<string name="navigation_item_info">Tutoriel</string>
<string name="nearby_needs_permissions">Les endroits proches ne peuvent pas être affichés si vous ne partagez pas votre position géographique.</string>
<string name="no_description_found">aucune description trouvée</string>
<string name="nearby_info_menu_commons_article">Page des fichiers de Communs</string>
<string name="nearby_info_menu_commons_article">Page des fichiers de Commons</string>
<string name="nearby_info_menu_wikidata_article">Élément de Wikidata</string>
<string name="error_while_cache">Erreur en mettant les images en cache</string>
<string name="title_info">Un titre descriptif unique pour le fichier, qui servira de nom de fichier. Vous pouvez utiliser un langage simple avec des espaces. Nincluez pas lextension du fichier</string>

View file

@ -42,6 +42,7 @@
<string name="login_failed_password">Nie można zalogować - sprawdź hasło</string>
<string name="login_failed_throttled">Zbyt wiele nieudanych prób zalogowania. Spróbuj ponownie za kilka minut.</string>
<string name="login_failed_blocked">Przepraszamy, ten użytkownik został zablokowany na Commons</string>
<string name="login_failed_2fa_needed">Wprowadź swój kod dla dwuetapowej autoryzacji.</string>
<string name="login_failed_generic">Logowanie nie powiodło się</string>
<string name="share_upload_button">Prześlij</string>
<string name="multiple_share_base_title">Nazwij ten zestaw</string>
@ -52,6 +53,7 @@
<string name="refresh_button">Odśwież</string>
<string name="gps_disabled">GPS w twoim urządzeniu jest wyłączony. Czy chcesz go włączyć?</string>
<string name="enable_gps">Włącz GPS</string>
<string name="contributions_subtitle_zero">Na razie brak przesłanych plików!</string>
<plurals name="contributions_subtitle">
<item quantity="zero">Przesłano @string/contributions_subtitle_zero</item>
<item quantity="one">Przesłano %d plik</item>
@ -151,6 +153,8 @@
<string name="become_a_tester_description">Dołącz do kanału bety w Google Play i dostań wczesny dostęp do nowych funkcji i łatek</string>
<string name="use_wikidata">Użyj Wikidanych</string>
<string name="_2fa_code">Kod 2FA</string>
<string name="maximum_limit">Górna Granica</string>
<string name="maximum_limit_alert">Nie można wyświetlić ponad 500</string>
<string name="login_failed_2fa_not_supported">Uwierzytelnianie dwuskładnikowe obecnie nie jest obsługiwane.</string>
<string name="logout_verification">Czy na pewno wylogować?</string>
<string name="commons_logo">Logo Commons</string>
@ -160,8 +164,10 @@
<string name="welcome_image_llamas">Lamy</string>
<string name="welcome_image_rainbow_bridge">Rainbow Bridge</string>
<string name="welcome_image_tulip">Tulipan</string>
<string name="welcome_image_no_selfies">Brak Selfie</string>
<string name="welcome_image_welcome_wikipedia">Witaj na Wikipedii</string>
<string name="welcome_image_welcome_copyright" fuzzy="true">Witaj w prawach autorskich.</string>
<string name="welcome_image_sydney_opera_house">Opera Sydnej</string>
<string name="cancel">Anuluj</string>
<string name="navigation_drawer_open">Otwórz</string>
<string name="navigation_drawer_close">Zamknij</string>

View file

@ -153,6 +153,7 @@
<string name="media_detail_uploaded_date">Дата загрузки</string>
<string name="media_detail_license">Лицензия</string>
<string name="media_detail_coordinates">Координаты</string>
<string name="media_detail_coordinates_empty">Не предоставлены</string>
<string name="become_a_tester_title">Стать бета-тестером</string>
<string name="become_a_tester_description">Подпишитесь на наш канал бета-версии на Google Play и получите ранний доступ к новым функциям и исправлениям ошибок</string>
<string name="use_wikidata">Использовать Викиданные</string>

View file

@ -2,7 +2,7 @@
<resources>
<string name="app_name">العام</string>
<string name="menu_settings">ترتيبون</string>
<string name="username">يُوزرنانءُ</string>
<string name="username">واپرائيندڙ-نانءُ</string>
<string name="password">ڳجھولفظ</string>
<string name="login">داخل ٿيو</string>
<string name="signup">کاتو کوليو</string>
@ -20,11 +20,11 @@
<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" fuzzy="true">
<item quantity="one">1 فائيل چاڙھيندي</item>
<plurals name="uploads_pending_notification_indicator">
<item quantity="one">%d فائيل چاڙھيندي</item>
<item quantity="other">%d فائيلَ چاڙھيندي</item>
</plurals>
<string name="title_activity_contributions" fuzzy="true">منھنجا چاڙھ</string>
<string name="title_activity_contributions">منھنجا تازا چاڙھ</string>
<string name="contribution_state_queued">قطار ۾</string>
<string name="contribution_state_failed">ناڪام</string>
<string name="contribution_state_in_progress">%1$d%% مڪمل</string>
@ -38,10 +38,10 @@
<string name="share_title_hint">عنوان</string>
<string name="share_description_hint">تشريح</string>
<string name="login_failed_network">ناقابلِ داخل ٿيڻ - باھمڄار ناڪامي</string>
<string name="login_failed_username">ناقابلِ داخل ٿيڻ - براءِ مھرباني پنھنجو يُوزرنانءُ چڪاسيو</string>
<string name="login_failed_username">ناقابلِ داخل ٿيڻ - براءِ مھرباني پنھنجو واپرائيندڙ-نانءُ چڪاسيو</string>
<string name="login_failed_password">ناقابل داخل ٿيڻ - براءِ مھرباني پنھنجو ڳجھولفظ چڪاسيو</string>
<string name="login_failed_throttled">ھيڪانديون ناڪام ڪوششون. براءِ مھرباني ڪجھ منٽن کانپوءِ ٻيھر ڪوشش ڪريو.</string>
<string name="login_failed_blocked">افسوس، ھي واھپ العام تي بندشيل آھي</string>
<string name="login_failed_blocked">افسوس، ھي واپرائيندڙ العام تي بندشيل آھي</string>
<string name="login_failed_generic">داخل ٿيڻ ناڪام</string>
<string name="share_upload_button">چاڙھيو</string>
<string name="multiple_share_base_title">ھن سيٽ کي نالو ڏيو</string>
@ -50,6 +50,8 @@
<string name="categories_search_text_hint">زمرا ڳوليو</string>
<string name="menu_save_categories">سانڍيو</string>
<string name="refresh_button">تازو ڪيو</string>
<string name="enable_gps">جي پي ايس چالو ڪيو (اين ايبل جي پي ايس)</string>
<string name="contributions_subtitle_zero">اڃان تائين ڪو به ڄاڙهه (اَپلوڊ) نه ٿيو آهي</string>
<plurals name="contributions_subtitle" fuzzy="true">
<item quantity="zero">اڃان تائين ڪو چاڙھ ناھي</item>
<item quantity="one">1 چاڙھ</item>
@ -121,7 +123,7 @@
<string name="welcome_final_text">توھان سمجھو ٿا توھان سمجھي ويئو؟</string>
<string name="welcome_final_button_text">ھا!</string>
<string name="detail_panel_cats_label">زمرا</string>
<string name="detail_panel_cats_loading" fuzzy="true">اتاريندي…</string>
<string name="detail_panel_cats_loading">لاهيندي...</string>
<string name="detail_panel_cats_none">ڪوبہ چونڊيل ناھي</string>
<string name="detail_description_empty">ڪا تشريح ناھي</string>
<string name="detail_license_empty">اڻڄاتل لائسنس</string>
@ -137,6 +139,28 @@
<string name="media_detail_title">عنوان</string>
<string name="media_detail_media_title">ابلاغ جو عنوان</string>
<string name="media_detail_description">تشريح</string>
<string name="media_detail_uploaded_date">چاڙهڻ جي تاريخ (اَپلوڊ ڊيٽ)</string>
<string name="media_detail_license">لائسنس (اجازت نامو)</string>
<string name="become_a_tester_title">آزمائشي آزمائيندڙ ٿيو</string>
<string name="use_wikidata">وڪيڊيٽا استعماڪ ڪريو</string>
<string name="number_of_uploads">منهنجي تازي چاڙهڻ (اَپلوڊ) جي حد (لِمٽ)</string>
<string name="maximum_limit">وڌ ۾ وڌ حد (ميگزيمم لِمٽ)</string>
<string name="no_image_found">ڪوبہ عڪس نہ لڌو</string>
<string name="upload_image">عڪس چاڙهيو</string>
<string name="welcome_image_tulip">گل لالا (ٽيولپ)</string>
<string name="welcome_image_no_selfies">ڪي به پاڻفيون نه (نو سيلفيز)</string>
<string name="welcome_image_proprietary">سڃاڻپ واري تصوير (پروپرائٽري اميج)</string>
<string name="welcome_image_welcome_wikipedia">وڪيپيڊيا تي ڀليڪار</string>
<string name="welcome_image_welcome_copyright">حقن ۽ واسطن جي آجيان (ويلڪم ڪاپي رائيٽ)</string>
<string name="welcome_image_sydney_opera_house">سڊني اوپيرا هائوس</string>
<string name="cancel">رد</string>
<string name="navigation_drawer_open">کوليو</string>
<string name="navigation_item_upload">چاڙهيو</string>
<string name="navigation_item_about">بابت</string>
<string name="navigation_item_settings">ترتيبون (سيٽنگس)</string>
<string name="navigation_item_feedback">اوهان جي راءِ</string>
<string name="navigation_item_info">سکيا (ٽيوٽوريل)</string>
<string name="no_description_found">ڪا به وضاحت نه ملي</string>
<string name="nearby_info_menu_commons_article">ڪامن فائيل جو ورق</string>
<string name="nearby_info_menu_wikidata_article">وڪيڊيٽا جزو (وڪيڊيٽا آئيٽم)</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="crash_dialog_title">ვიკიოწკარუექ აკორთუ</string>
<string name="crash_dialog_text">უი. მუდგაინქ ქჷმოხვადუǃ</string>
<string name="crash_dialog_comment_prompt">ქომწით მუს აკეთენდით დო ქჷმომჯღონით ელექტრონული ფოსტათ. თენა ჩქი პრობლემაშ გიშათინუა ქემეხვარებჷნა!</string>
<string name="crash_dialog_ok_toast">დიდი მარდიǃ</string>
</resources>

View file

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ვიკიოწკარუე</string>
<string name="menu_settings">პარამეტრეფი</string>
<string name="username">მახვარებუშ ჯოხო</string>
<string name="password">პაროლი</string>
<string name="login">მიშულა</string>
<string name="signup">რეგისტრაცია</string>
<string name="logging_in_title">სისტემაშა მიშულა</string>
<string name="logging_in_message">ქორთხინთ ქიმიცადით …</string>
<string name="login_success">სისტემაშა მიშულაქ წჷმოძინელო გეთუ!</string>
<string name="login_failed">სისტემაშა მიშულაქ ვემიხუჯინუ!</string>
<string name="upload_failed">ფაილქ ვეგორუ. ქორთხინთ, ქოცადით შხვა ფაილი.</string>
<string name="authentication_failed">აუთენტიფიკაციაქ ვემიხუჯინუ!</string>
<string name="uploading_started">ეხარგუაქ ქჷდიჭყუ!</string>
<string name="upload_completed_notification_title">%1$ ეხარგილი რე!</string>
<string name="upload_completed_notification_text">ქეგუწკანტეთ თქვანი ეხარგუაშ ოძირაფალო</string>
<string name="upload_progress_notification_title_start">ქჷდიჭყუ %1$ ეხარგუაქ</string>
<string name="upload_progress_notification_title_in_progress">%1$ იხარგუ</string>
<string name="upload_progress_notification_title_finishing">ეხარგუაშ თება %1$</string>
<string name="upload_failed_notification_title">%1$ ეხარგუაქ ვემიხუჯინუ</string>
<string name="upload_failed_notification_subtitle">ოძირაფალო ქეგუწკანტეთ</string>
<plurals name="uploads_pending_notification_indicator">
<item quantity="one">%d ფაილი იხარგუ</item>
<item quantity="other">%d ფაილი იხარგუ</item>
</plurals>
<string name="title_activity_contributions">ჩქიმი ბოლო ეხარგუეფი</string>
<string name="contribution_state_queued">რადი</string>
<string name="contribution_state_failed">ვემიხუჯინუ</string>
<string name="contribution_state_in_progress">%1$d%% გეთუ</string>
<string name="contribution_state_starting">იხარგუაფუ</string>
<string name="menu_from_gallery">გალერეაშე</string>
<string name="menu_from_camera">ფოტოშ გინოღალა</string>
<string name="menu_nearby">გოხოლუას</string>
<string name="provider_contributions">ჩქიმი ეხარგუეფი</string>
<string name="menu_share">გობჟინაფა</string>
<string name="menu_open_in_browser">ბრაუზერს გონწყუმა</string>
<string name="share_title_hint">დუდჯოხო</string>
<string name="share_description_hint">ეჭარუა</string>
<string name="login_failed_network">მიშულაქ ვემიხუჯინუ - რშვილიშ ჩილათა</string>
<string name="login_failed_username">მიშულაქ ვემიხუჯინუ - ქორთხინთ გეგნაჯინით ჯოხოს</string>
<string name="login_failed_password">მიშულაქ ვემიხუჯინუ - ქორთხინთ გეგნაჯინით პაროლს</string>
<string name="login_failed_throttled">ძალამ მიარე უმწუძინუ ცადება. ქორთხინ, მუხირენ წუთშა ხოლო ქოცადით.</string>
<string name="login_failed_blocked">მორდება, თე მახვარებუ ბლოკირი რე ვიკიოწკარუეს</string>
<string name="login_failed_2fa_needed">თქვა გემშიონათ ოკო ჟირფაქტორიანი ავტორიზაციაშ კოდი.</string>
<string name="login_failed_generic">მიშულაქ ვემიხუჯინუ</string>
<string name="share_upload_button">ეხარგუა</string>
<string name="multiple_share_base_title">სერიაშ ჯოხო</string>
<string name="provider_modifications">მოდიფიკაციეფი</string>
<string name="menu_upload_single">ეხარგუა</string>
<string name="categories_search_text_hint">კატეგორიაშ გიშაგორუა</string>
<string name="menu_save_categories">ჩუალა</string>
<string name="refresh_button">გოახალაფა</string>
<string name="gps_disabled">თქვან მონწყილობას GPS თიშილი რე. თახმო გოკონანო ჩართება?</string>
<string name="enable_gps">GPS-იშ ჩართება</string>
<string name="contributions_subtitle_zero">ეხარგუეფი ვა რე</string>
<plurals name="contributions_subtitle">
<item quantity="zero">\@string/contributions_subtitle_zero</item>
<item quantity="one">%d ეხარგუა</item>
<item quantity="other">%d ეხარგუა</item>
</plurals>
<plurals name="starting_multiple_uploads">
<item quantity="one">იჭყაფუ %d ეხარგუა</item>
<item quantity="other">იჭყაფუ %d ეხარგუა</item>
</plurals>
<plurals name="multiple_uploads_title">
<item quantity="one">%d ეხარგუა</item>
<item quantity="other">%d ეხარგუა</item>
</plurals>
<string name="categories_activity_title">კატეგორია</string>
<string name="title_activity_settings">პარამეტრეფი</string>
<string name="title_activity_signup">რეგისტრაცია</string>
<string name="menu_about">პროგრამაშ გეშა</string>
<string name="menu_retry_upload">გომაჟირაფა</string>
<string name="menu_cancel_upload">გოუქვაფა</string>
<string name="share_license_summary">თე სურათი გიბჟინუ %1$s ლიცენზიათ</string>
<string name="menu_download">გინოჭარუა</string>
<string name="preference_license">ლიცენზია</string>
<string name="use_previous">წჷმი სათაურიშ/ეჭარუაშ გჷმორინაფა</string>
<string name="allow_gps">ასეიანი ორენიშ ავტომატური მეღება</string>
<string name="preference_theme">სერიშ რეჟიმი</string>
<string name="preference_theme_summary">რუმე თემაშ გჷმორინაფა</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>
<string name="license_name_cc_by"> Attribution 3.0</string>
<string name="license_name_cc0">CC0</string>
<string name="license_name_cc_by_sa_3_0">CC BY-SA 3.0</string>
<string name="license_name_cc_by_sa_3_0_at">CC BY-SA 3.0 (ავსტრია)</string>
<string name="license_name_cc_by_sa_3_0_de">CC BY-SA 3.0 (გერმანია)</string>
<string name="license_name_cc_by_sa_3_0_ee">CC BY-SA 3.0 (ესტონეთი)</string>
<string name="license_name_cc_by_sa_3_0_es">CC BY-SA 3.0 (ესპანეთი)</string>
<string name="license_name_cc_by_sa_3_0_hr">CC BY-SA 3.0 (ხორვატია)</string>
<string name="license_name_cc_by_sa_3_0_lu">CC BY-SA 3.0 (ლუქსემბურგი)</string>
<string name="license_name_cc_by_sa_3_0_nl">CC BY-SA 3.0 (ნიდერლანდი)</string>
<string name="license_name_cc_by_sa_3_0_no">CC BY-SA 3.0 (ნორვეგია)</string>
<string name="license_name_cc_by_sa_3_0_pl">CC BY-SA 3.0 (პოლონეთი)</string>
<string name="license_name_cc_by_sa_3_0_ro">CC BY-SA 3.0 (რუმინეთი)</string>
<string name="license_name_cc_by_3_0">CC BY-SA 3.0</string>
<string name="license_name_cc_by_sa_4_0">CC BY-SA 4.0</string>
<string name="license_name_cc_by_4_0">CC BY 4.0</string>
<string name="license_name_cc_zero">CC Zero</string>
<string name="tutorial_2_text">ქორთხინთ გეხარგეთ, ხვალე თქვანით ინოღალირი ვარდა აკოქიმინელი სურათეფი:</string>
<string name="tutorial_2_subtext">- ორთაშობური ობიექტეფი (პიოლეფი, ჩხოლარეფი, გვალეფი)\n- ორგებელი ობიექტეფი (ველოსიპედეფი, მახინწალიშ დგჷმიელფი)\n- ჩინებული ადამიერეფი (თქვანი ნოღაშ მერი, ოლიმპიარი სპორტსმენეფი, ნამუეფსჷთ შეხვალამუთჷნ)</string>
<string name="tutorial_3_text">ქორთხინთ ვეხარგათ:</string>
<string name="tutorial_3_subtext">- სელფი ვარდა მაჸალეეფიშ სურათეფი\n- ინტერნეტშე გჷმოხარგილი ფოტოეფი\n- ვადუდიშული აპლიკაციეფიშ სკრინშოტეფი</string>
<string name="tutorial_4_text">ეხარგუაშ მინუში:</string>
<string name="tutorial_4_subtext">- დუდჯოხო: სიდნეიშ ოპერაშ თეატრი\n- ეჭარუა: სიდნეიშ ოპერაშ თეატრიშ მიოჯინი ლუბაშე\n- კატეგორიეფი: Sydney Opera House, Sydney Opera House from the west, Sydney Opera House remote views</string>
<string name="welcome_wikipedia_text">გეხარგეთ თქვანი ფოტოეფი. ქემეხვარით ვიკიპედიაშ სტატიეფიშ გაჭყანიერებას!</string>
<string name="welcome_wikipedia_subtext">ვიკიპედიაშ ფოტოეფი ვიკიოწკარუეს იჩუალუაფუ.</string>
<string name="welcome_copyright_text">თქვანი სურათეფი კათაშ გონათუას ოხვარუ ედომუშამ მოსოფელს.</string>
<string name="welcome_final_button_text">ქოǃ</string>
<string name="detail_panel_cats_label">კატეგორიეფი</string>
<string name="detail_panel_cats_loading">იხარგუ...</string>
<string name="detail_panel_cats_none">მუთუნ ვა რე გიშაგორილი</string>
<string name="detail_description_empty">ვა რე ეჭარუა</string>
<string name="detail_license_empty">უჩინებუ ლიცენზია</string>
<string name="menu_refresh">გოახალაფა</string>
<string name="ok">OK</string>
<string name="title_activity_nearby">გოხოლუაშ აბანეფი</string>
<string name="no_nearby">გოხოლუაშ აბანეფქ ვეგორუ</string>
<string name="warning">გათხილება</string>
<string name="yes">ქო</string>
<string name="no">ვარი</string>
<string name="media_detail_title">დუდჯოხო</string>
<string name="media_detail_media_title">მედიაფაილიშ დუდჯოხო</string>
<string name="media_detail_description">ეჭარუა</string>
<string name="media_detail_uploaded_date">ეხარგუაშ თარიღი</string>
<string name="media_detail_license">ლიცენზია</string>
<string name="media_detail_coordinates">კოორდინატეფი</string>
<string name="become_a_tester_title">ბეტა ტესტირებას კათაფი</string>
<string name="use_wikidata">ვიკიმუნაჩემეფიშ გჷმორინაფა</string>
<string name="_2fa_code">2ფა კოდი</string>
<string name="number_of_uploads">ჩქიმი ბოლო ეხარგუეფიშ ლიმიტი</string>
<string name="maximum_limit">მაქსიმალური ლიმიტი</string>
<string name="maximum_limit_alert">500-შე უმოსიშ ძირაფა ვეშილებე</string>
<string name="set_limit">ბოლო ეხარგუეფიშ ლიმიტიშ გერინაფა</string>
</resources>