diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index f58c9225b..34078f07e 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -4,6 +4,12 @@ Fixes #{GitHub issue number} {Describe the changes made and why they were made.} +## Tests performed + +Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}. + +{Please test your PR at least once before submitting.} + ## Screenshots showing what changed {Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)} diff --git a/app/build.gradle b/app/build.gradle index 6f225dc2f..349a5f432 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,8 +48,11 @@ dependencies { compile 'com.facebook.fresco:fresco:1.3.0' compile 'com.facebook.stetho:stetho:1.5.0' + compile 'com.android.support:multidex:1.0.3' + testCompile 'junit:junit:4.12' testCompile 'org.robolectric:robolectric:3.7.1' + testCompile "org.robolectric:multidex:3.4.2" testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1' androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.1' @@ -94,6 +97,8 @@ dependencies { implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION" kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION" kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION" + + compile 'com.borjabravo:readmoretextview:2.1.0' } android { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8e56b91d6..253bdaea8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -91,10 +91,6 @@ android:name=".notification.NotificationActivity" android:label="@string/navigation_item_notification" /> - - categories; // as loaded at runtime? + protected boolean requestedDeletion; private Map descriptions; // multilingual descriptions as loaded private HashMap tags = new HashMap<>(); private @Nullable LatLng coordinates; @@ -53,6 +54,7 @@ public class Media implements Parcelable { protected Media() { this.categories = new ArrayList<>(); this.descriptions = new HashMap<>(); + this.requestedDeletion = false; } /** @@ -416,4 +418,12 @@ public class Media implements Parcelable { parcel.writeStringList(categories); parcel.writeMap(descriptions); } + + public void setRequestedDeletion(){ + requestedDeletion = true; + } + + public boolean getRequestedDeletion(){ + return requestedDeletion; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java index 1044a7c17..2b6ba9907 100644 --- a/app/src/main/java/fr/free/nrw/commons/Utils.java +++ b/app/src/main/java/fr/free/nrw/commons/Utils.java @@ -166,14 +166,13 @@ public class Utils { } public static void rateApp(Context context) { - final String appPackageName = context.getPackageName(); + final String appPackageName = BuildConfig.class.getPackage().getName(); try { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); } catch (android.content.ActivityNotFoundException anfe) { context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + appPackageName))); } - return ; } public static void handleWebUrl(Context context,Uri url){ diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteActivity.java b/app/src/main/java/fr/free/nrw/commons/delete/DeleteActivity.java deleted file mode 100644 index b7437de5c..000000000 --- a/app/src/main/java/fr/free/nrw/commons/delete/DeleteActivity.java +++ /dev/null @@ -1,46 +0,0 @@ -package fr.free.nrw.commons.delete; - -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; - -import java.io.IOException; - -import javax.inject.Inject; - -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.auth.SessionManager; -import fr.free.nrw.commons.di.ApplicationlessInjection; -import fr.free.nrw.commons.mwapi.MediaWikiApi; -import timber.log.Timber; - -public class DeleteActivity extends AppCompatActivity { - - @Inject MediaWikiApi mwApi; - @Inject SessionManager sessionManager; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - ApplicationlessInjection - .getInstance(this.getApplicationContext()) - .getCommonsApplicationComponent() - .inject(this); - - setContentView(R.layout.activity_delete); - - String authCookie = sessionManager.getAuthCookie(); - Timber.d(authCookie); - - mwApi.setAuthCookie(authCookie); - String editToken = "noooooooo"; - - try { - editToken = mwApi.getEditToken(); - } catch (Exception e) { - Timber.d(e.getMessage()); - } - Timber.d(editToken); - - } -} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java b/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java new file mode 100644 index 000000000..3c3352712 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/delete/DeleteTask.java @@ -0,0 +1,130 @@ +package fr.free.nrw.commons.delete; + +import android.content.Context; +import android.os.AsyncTask; +import android.widget.Button; +import android.widget.Toast; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; + +import javax.inject.Inject; + +import fr.free.nrw.commons.Media; +import fr.free.nrw.commons.auth.SessionManager; +import fr.free.nrw.commons.di.ApplicationlessInjection; +import fr.free.nrw.commons.mwapi.MediaWikiApi; +import timber.log.Timber; + +import static android.view.View.GONE; + +public class DeleteTask extends AsyncTask { + + @Inject MediaWikiApi mwApi; + @Inject SessionManager sessionManager; + + private Context context; + private Media media; + private String reason; + + public DeleteTask (Context context, Media media, String reason){ + this.context = context; + this.media = media; + this.reason = reason; + } + + @Override + protected void onPreExecute(){ + ApplicationlessInjection + .getInstance(context.getApplicationContext()) + .getCommonsApplicationComponent() + .inject(this); + } + + @Override + protected Boolean doInBackground(Void ...voids) { + String editToken; + String authCookie; + String summary = "Nominating " + media.getFilename() +" for deletion."; + + authCookie = sessionManager.getAuthCookie(); + mwApi.setAuthCookie(authCookie); + + try { + editToken = mwApi.getEditToken(); + } + catch (Exception e){ + Timber.d(e.getMessage()); + return false; + } + if (editToken.equals("+\\")) { + return false; + } + + Calendar calendar = Calendar.getInstance(); + String fileDeleteString = "{{delete|reason=" + reason + + "|subpage=" +media.getFilename() + + "|day=" + calendar.get(Calendar.DAY_OF_MONTH) + + "|month=" + calendar.getDisplayName(Calendar.MONTH,Calendar.LONG, Locale.getDefault()) + + "|year=" + calendar.get(Calendar.YEAR) + + "}}"; + try{ + mwApi.prependEdit(editToken,fileDeleteString+"\n", + media.getFilename(),summary); + } + catch (Exception e) { + Timber.d(e.getMessage()); + return false; + } + + String subpageString = "=== [[:" + media.getFilename() + "]] ===\n" + + reason + + " ~~~~"; + try{ + mwApi.edit(editToken,subpageString+"\n", + "Commons:Deletion requests/"+media.getFilename(),summary); + } + catch (Exception e) { + Timber.d(e.getMessage()); + return false; + } + + String logPageString = "\n{{Commons:Deletion requests/" + media.getFilename() + + "}}\n"; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd"); + String date = sdf.format(calendar.getTime()); + try{ + mwApi.appendEdit(editToken,logPageString+"\n", + "Commons:Deletion requests/"+date,summary); + } + catch (Exception e) { + Timber.d(e.getMessage()); + return false; + } + + String userPageString = "\n{{subst:idw|" + media.getFilename() + + "}} ~~~~"; + try{ + mwApi.appendEdit(editToken,userPageString+"\n", + "User_Talk:"+sessionManager.getCurrentAccount().name,summary); + } + catch (Exception e) { + Timber.d(e.getMessage()); + return false; + } + return true; + } + + @Override + protected void onPostExecute(Boolean result) { + String toastText = ""; + if (result) { + toastText = "Successfully requested deletion."; + } + else{ + toastText = "Could not request deletion."; + } + Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java index 9c3e53641..4d0a14e9a 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java @@ -9,8 +9,10 @@ import dagger.android.support.AndroidSupportInjectionModule; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.MediaWikiImageView; import fr.free.nrw.commons.auth.LoginActivity; +import fr.free.nrw.commons.contributions.Contribution; +import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsSyncAdapter; -import fr.free.nrw.commons.delete.DeleteActivity; +import fr.free.nrw.commons.delete.DeleteTask; import fr.free.nrw.commons.modifications.ModificationsSyncAdapter; import fr.free.nrw.commons.settings.SettingsFragment; @@ -35,7 +37,7 @@ public interface CommonsApplicationComponent extends AndroidInjector openMap(media.getCoordinates())); } - if (delete.getVisibility()!=View.GONE){ + if (delete.getVisibility()==View.VISIBLE){ delete.setOnClickListener(v -> { - Timber.d("clicked delete"); - Bundle bundle = new Bundle(); - bundle.putParcelable("media",media); - Intent deleteIntent = new Intent(getActivity(), DeleteActivity.class); - startActivity(deleteIntent); + AlertDialog alert; + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage("Sure you want to delete?"); + builder.setCancelable(true); + builder.setPositiveButton( + R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + AlertDialog.Builder alert = new AlertDialog.Builder(getActivity()); + alert.setTitle("Why should this file be deleted?"); + final EditText input = new EditText(getActivity()); + alert.setView(input); + alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String reason = input.getText().toString(); + DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason); + deleteTask.execute(); + } + }); + alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) {} + }); + AlertDialog d = alert.create(); + input.addTextChangedListener(new TextWatcher() { + private void handleText() { + final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE); + if(input.getText().length() == 0) { + okButton.setEnabled(false); + } else { + okButton.setEnabled(true); + } + } + @Override + public void afterTextChanged(Editable arg0) { + handleText(); + } + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + }); + d.show(); + d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + } + }); + builder.setNegativeButton( + R.string.no, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) {} + }); + alert = builder.create(); + alert.show(); }); } } @@ -326,7 +374,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { Intent viewIntent = new Intent(); viewIntent.setAction(Intent.ACTION_VIEW); viewIntent.setData(new PageTitle(selectedCategoryTitle).getCanonicalUri()); - startActivity(viewIntent); + //check if web browser available + if(viewIntent.resolveActivity(getActivity().getPackageManager()) != null){ + startActivity(viewIntent); + } else { + Toast toast = Toast.makeText(getContext(), getString(R.string.no_web_browser), LENGTH_SHORT); + toast.show(); + } }); } return item; @@ -406,7 +460,14 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment { private void openWebBrowser(String url) { Intent browser = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browser); + //check if web browser available + if (browser.resolveActivity(getActivity().getPackageManager()) != null) { + startActivity(browser); + } else { + Toast toast = Toast.makeText(getContext(), getString(R.string.no_web_browser), LENGTH_SHORT); + toast.show(); + } + } private void openMap(LatLng coordinates) { diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index 3dd8d69e8..be7aea836 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -24,6 +24,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import javax.inject.Inject; import javax.inject.Named; @@ -40,6 +41,7 @@ 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 android.widget.Toast.LENGTH_SHORT; public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener { @@ -118,7 +120,14 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple Intent viewIntent = new Intent(); viewIntent.setAction(ACTION_VIEW); viewIntent.setData(m.getFilePageTitle().getMobileUri()); - startActivity(viewIntent); + //check if web browser available + if(viewIntent.resolveActivity(getActivity().getPackageManager()) != null){ + startActivity(viewIntent); + } else { + Toast toast = Toast.makeText(getContext(), getString(R.string.no_web_browser), LENGTH_SHORT); + toast.show(); + } + return true; case R.id.menu_download_current_image: // Download diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java index fe9700ef5..6058f7062 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java @@ -205,6 +205,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { .getNodes("/api/query/pages/page/imageinfo").size() > 0; } + @Override @Nullable public String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException { @@ -217,6 +218,31 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi { .getString("/api/edit/@result"); } + + @Override + @Nullable + public String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException { + return api.action("edit") + .param("title", filename) + .param("token", editToken) + .param("appendtext", processedPageContent) + .param("summary", summary) + .post() + .getString("/api/edit/@result"); + } + + @Override + @Nullable + public String prependEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException { + return api.action("edit") + .param("title", filename) + .param("token", editToken) + .param("prependtext", processedPageContent) + .param("summary", summary) + .post() + .getString("/api/edit/@result"); + } + @Override public String findThumbnailByFilename(String filename) throws IOException { return api.action("query") diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java index da9403b62..2d815fed4 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/MediaWikiApi.java @@ -38,6 +38,12 @@ public interface MediaWikiApi { @Nullable String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException; + @Nullable + String prependEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException; + + @Nullable + String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException; + @NonNull MediaResult fetchMediaByFilename(String filename) throws IOException; @@ -58,8 +64,6 @@ public interface MediaWikiApi { boolean existingFile(String fileSha1) throws IOException; - - @NonNull LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException; diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java index cdb9e97d3..f66b0b6a0 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java @@ -8,6 +8,7 @@ import android.os.Bundle; import android.support.v7.widget.DividerItemDecoration; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.widget.Toast; import com.pedrogomez.renderers.RVRendererAdapter; @@ -25,6 +26,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import timber.log.Timber; +import static android.widget.Toast.LENGTH_SHORT; + /** * Created by root on 18.12.2017. */ @@ -71,7 +74,14 @@ public class NotificationActivity extends NavigationBaseActivity { if (url == null || url.equals("")) { return; } - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + Intent browser = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + //check if web browser available + if(browser.resolveActivity(this.getPackageManager()) != null){ + startActivity(browser); + } else { + Toast toast = Toast.makeText(this, getString(R.string.no_web_browser), LENGTH_SHORT); + toast.show(); + } } private void setAdapter(List notificationList) { @@ -85,6 +95,7 @@ public class NotificationActivity extends NavigationBaseActivity { public static void startYourself(Context context) { Intent intent = new Intent(context, NotificationActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); context.startActivity(intent); } } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java index a5aac0508..a02b9eff4 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationRenderer.java @@ -1,11 +1,13 @@ package fr.free.nrw.commons.notification; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import com.borjabravo.readmoretextview.ReadMoreTextView; import com.pedrogomez.renderers.Renderer; import butterknife.BindView; @@ -17,8 +19,8 @@ import fr.free.nrw.commons.R; */ public class NotificationRenderer extends Renderer { - @BindView(R.id.title) TextView title; - @BindView(R.id.description) TextView description; + @BindView(R.id.title) ReadMoreTextView title; + @BindView(R.id.description) ReadMoreTextView description; @BindView(R.id.time) TextView time; @BindView(R.id.icon) ImageView icon; private NotificationClicked listener; @@ -46,9 +48,13 @@ public class NotificationRenderer extends Renderer { @Override public void render() { Notification notification = getContent(); - title.setText(notification.notificationText); + StringBuilder str = new StringBuilder(notification.notificationText); + str.append(" " ); + title.setText(str); time.setText(notification.date); - description.setText(notification.description); + StringBuilder desc = new StringBuilder(notification.description); + desc.append(" "); + description.setText(desc); switch (notification.notificationType) { case THANK_YOU_EDIT: icon.setImageResource(R.drawable.ic_edit_black_24dp); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java index 50e22396a..6a6083403 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/SingleUploadFragment.java @@ -28,6 +28,7 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; import java.util.ArrayList; @@ -65,9 +66,6 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.activity_share, menu); - if (titleEdit != null) { - menu.findItem(R.id.menu_upload_single).setEnabled(titleEdit.getText().length() != 0); - } } @Override @@ -76,6 +74,11 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment { //What happens when the 'submit' icon is tapped case R.id.menu_upload_single: + if (titleEdit.getText().toString().isEmpty()) { + Toast.makeText(getContext(), R.string.add_title_toast, Toast.LENGTH_LONG).show(); + return false; + } + String title = titleEdit.getText().toString(); String desc = descEdit.getText().toString(); diff --git a/app/src/main/res/layout/activity_delete.xml b/app/src/main/res/layout/activity_delete.xml deleted file mode 100644 index 42b83f37d..000000000 --- a/app/src/main/res/layout/activity_delete.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_media_detail.xml b/app/src/main/res/layout/fragment_media_detail.xml index d2438bd2a..4881c2061 100644 --- a/app/src/main/res/layout/fragment_media_detail.xml +++ b/app/src/main/res/layout/fragment_media_detail.xml @@ -251,19 +251,15 @@ android:layout_width="match_parent" android:layout_height="@dimen/small_gap" /> - -