mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 14:53:59 +01:00 
			
		
		
		
	Merge branch 'main' into psh/convert-profile-to-kotlin
This commit is contained in:
		
						commit
						0d114db5cd
					
				
					 65 changed files with 2859 additions and 3162 deletions
				
			
		|  | @ -1,77 +0,0 @@ | ||||||
| package fr.free.nrw.commons.LocationPicker; |  | ||||||
| 
 |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.Intent; |  | ||||||
| import fr.free.nrw.commons.CameraPosition; |  | ||||||
| import fr.free.nrw.commons.Media; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Helper class for starting the activity |  | ||||||
|  */ |  | ||||||
| public final class LocationPicker { |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Getting camera position from the intent using constants |  | ||||||
|      * |  | ||||||
|      * @param data intent |  | ||||||
|      * @return CameraPosition |  | ||||||
|      */ |  | ||||||
|     public static CameraPosition getCameraPosition(final Intent data) { |  | ||||||
|         return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static class IntentBuilder { |  | ||||||
| 
 |  | ||||||
|         private final Intent intent; |  | ||||||
| 
 |  | ||||||
|         /** |  | ||||||
|          * Creates a new builder that creates an intent to launch the place picker activity. |  | ||||||
|          */ |  | ||||||
|         public IntentBuilder() { |  | ||||||
|             intent = new Intent(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /** |  | ||||||
|          * Gets and puts location in intent |  | ||||||
|          * @param position CameraPosition |  | ||||||
|          * @return LocationPicker.IntentBuilder |  | ||||||
|          */ |  | ||||||
|         public LocationPicker.IntentBuilder defaultLocation( |  | ||||||
|             final CameraPosition position) { |  | ||||||
|           intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position); |  | ||||||
|           return this; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /** |  | ||||||
|          * Gets and puts activity name in intent |  | ||||||
|          * @param activity activity key |  | ||||||
|          * @return LocationPicker.IntentBuilder |  | ||||||
|          */ |  | ||||||
|         public LocationPicker.IntentBuilder activityKey( |  | ||||||
|             final String activity) { |  | ||||||
|           intent.putExtra(LocationPickerConstants.ACTIVITY_KEY, activity); |  | ||||||
|           return this; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         /** |  | ||||||
|          * Gets and puts media in intent |  | ||||||
|          * @param media Media |  | ||||||
|          * @return LocationPicker.IntentBuilder |  | ||||||
|          */ |  | ||||||
|         public LocationPicker.IntentBuilder media( |  | ||||||
|                 final Media media) { |  | ||||||
|               intent.putExtra(LocationPickerConstants.MEDIA, media); |  | ||||||
|               return this; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|         /** |  | ||||||
|          * Gets and sets the activity |  | ||||||
|          * @param activity Activity |  | ||||||
|          * @return Intent |  | ||||||
|          */ |  | ||||||
|        public Intent build(final Activity activity) { |  | ||||||
|           intent.setClass(activity, LocationPickerActivity.class); |  | ||||||
|           return intent; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,72 @@ | ||||||
|  | package fr.free.nrw.commons.LocationPicker | ||||||
|  | 
 | ||||||
|  | import android.app.Activity | ||||||
|  | import android.content.Intent | ||||||
|  | import fr.free.nrw.commons.CameraPosition | ||||||
|  | import fr.free.nrw.commons.Media | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Helper class for starting the activity | ||||||
|  |  */ | ||||||
|  | object LocationPicker { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Getting camera position from the intent using constants | ||||||
|  |      * | ||||||
|  |      * @param data intent | ||||||
|  |      * @return CameraPosition | ||||||
|  |      */ | ||||||
|  |     @JvmStatic | ||||||
|  |     fun getCameraPosition(data: Intent): CameraPosition? { | ||||||
|  |         return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     class IntentBuilder | ||||||
|  |     /** | ||||||
|  |      * Creates a new builder that creates an intent to launch the place picker activity. | ||||||
|  |      */() { | ||||||
|  | 
 | ||||||
|  |         private val intent: Intent = Intent() | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Gets and puts location in intent | ||||||
|  |          * @param position CameraPosition | ||||||
|  |          * @return LocationPicker.IntentBuilder | ||||||
|  |          */ | ||||||
|  |         fun defaultLocation(position: CameraPosition): IntentBuilder { | ||||||
|  |             intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Gets and puts activity name in intent | ||||||
|  |          * @param activity activity key | ||||||
|  |          * @return LocationPicker.IntentBuilder | ||||||
|  |          */ | ||||||
|  |         fun activityKey(activity: String): IntentBuilder { | ||||||
|  |             intent.putExtra(LocationPickerConstants.ACTIVITY_KEY, activity) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Gets and puts media in intent | ||||||
|  |          * @param media Media | ||||||
|  |          * @return LocationPicker.IntentBuilder | ||||||
|  |          */ | ||||||
|  |         fun media(media: Media): IntentBuilder { | ||||||
|  |             intent.putExtra(LocationPickerConstants.MEDIA, media) | ||||||
|  |             return this | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Gets and sets the activity | ||||||
|  |          * @param activity Activity | ||||||
|  |          * @return Intent | ||||||
|  |          */ | ||||||
|  |         fun build(activity: Activity): Intent { | ||||||
|  |             intent.setClass(activity, LocationPickerActivity::class.java) | ||||||
|  |             return intent | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,681 +0,0 @@ | ||||||
| package fr.free.nrw.commons.LocationPicker; |  | ||||||
| 
 |  | ||||||
| import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION; |  | ||||||
| import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM; |  | ||||||
| import static fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL; |  | ||||||
| 
 |  | ||||||
| import android.Manifest.permission; |  | ||||||
| import android.annotation.SuppressLint; |  | ||||||
| import android.content.Intent; |  | ||||||
| import android.content.pm.PackageManager; |  | ||||||
| import android.graphics.Color; |  | ||||||
| import android.graphics.Paint; |  | ||||||
| import android.graphics.drawable.Drawable; |  | ||||||
| import android.location.LocationManager; |  | ||||||
| import android.os.Bundle; |  | ||||||
| import android.preference.PreferenceManager; |  | ||||||
| import android.text.Html; |  | ||||||
| import android.text.method.LinkMovementMethod; |  | ||||||
| import android.view.MotionEvent; |  | ||||||
| import android.view.View; |  | ||||||
| import android.view.Window; |  | ||||||
| import android.view.animation.OvershootInterpolator; |  | ||||||
| import android.widget.Button; |  | ||||||
| import android.widget.ImageView; |  | ||||||
| import android.widget.TextView; |  | ||||||
| import android.widget.Toast; |  | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| import androidx.annotation.Nullable; |  | ||||||
| import androidx.appcompat.app.ActionBar; |  | ||||||
| import androidx.appcompat.app.AppCompatActivity; |  | ||||||
| import androidx.appcompat.widget.AppCompatTextView; |  | ||||||
| import androidx.constraintlayout.widget.ConstraintLayout; |  | ||||||
| import androidx.core.app.ActivityCompat; |  | ||||||
| import androidx.core.content.ContextCompat; |  | ||||||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; |  | ||||||
| import fr.free.nrw.commons.CameraPosition; |  | ||||||
| import fr.free.nrw.commons.CommonsApplication; |  | ||||||
| import fr.free.nrw.commons.Media; |  | ||||||
| import fr.free.nrw.commons.R; |  | ||||||
| import fr.free.nrw.commons.Utils; |  | ||||||
| import fr.free.nrw.commons.auth.SessionManager; |  | ||||||
| import fr.free.nrw.commons.auth.csrf.CsrfTokenClient; |  | ||||||
| import fr.free.nrw.commons.coordinates.CoordinateEditHelper; |  | ||||||
| import fr.free.nrw.commons.filepicker.Constants; |  | ||||||
| import fr.free.nrw.commons.kvstore.BasicKvStore; |  | ||||||
| import fr.free.nrw.commons.kvstore.JsonKvStore; |  | ||||||
| import fr.free.nrw.commons.location.LocationPermissionsHelper; |  | ||||||
| import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback; |  | ||||||
| import fr.free.nrw.commons.location.LocationServiceManager; |  | ||||||
| import fr.free.nrw.commons.theme.BaseActivity; |  | ||||||
| import fr.free.nrw.commons.utils.DialogUtil; |  | ||||||
| import fr.free.nrw.commons.utils.SystemThemeUtils; |  | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; |  | ||||||
| import io.reactivex.schedulers.Schedulers; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Locale; |  | ||||||
| import javax.inject.Inject; |  | ||||||
| import javax.inject.Named; |  | ||||||
| import org.osmdroid.tileprovider.tilesource.TileSourceFactory; |  | ||||||
| import org.osmdroid.util.GeoPoint; |  | ||||||
| import org.osmdroid.util.constants.GeoConstants; |  | ||||||
| import org.osmdroid.views.CustomZoomButtonsController; |  | ||||||
| import org.osmdroid.views.overlay.Marker; |  | ||||||
| import org.osmdroid.views.overlay.Overlay; |  | ||||||
| import org.osmdroid.views.overlay.ScaleDiskOverlay; |  | ||||||
| import org.osmdroid.views.overlay.TilesOverlay; |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Helps to pick location and return the result with an intent |  | ||||||
|  */ |  | ||||||
| public class LocationPickerActivity extends BaseActivity implements |  | ||||||
|     LocationPermissionCallback { |  | ||||||
|     /** |  | ||||||
|      * coordinateEditHelper: helps to edit coordinates |  | ||||||
|      */ |  | ||||||
|     @Inject |  | ||||||
|     CoordinateEditHelper coordinateEditHelper; |  | ||||||
|     /** |  | ||||||
|      * media : Media object |  | ||||||
|      */ |  | ||||||
|     private Media media; |  | ||||||
|     /** |  | ||||||
|      * cameraPosition : position of picker |  | ||||||
|      */ |  | ||||||
|     private CameraPosition cameraPosition; |  | ||||||
|     /** |  | ||||||
|      * markerImage : picker image |  | ||||||
|      */ |  | ||||||
|     private ImageView markerImage; |  | ||||||
|     /** |  | ||||||
|      * mapView : OSM Map |  | ||||||
|      */ |  | ||||||
|     private org.osmdroid.views.MapView mapView; |  | ||||||
|     /** |  | ||||||
|      * tvAttribution : credit |  | ||||||
|      */ |  | ||||||
|     private AppCompatTextView tvAttribution; |  | ||||||
|     /** |  | ||||||
|      * activity : activity key |  | ||||||
|      */ |  | ||||||
|     private String activity; |  | ||||||
|     /** |  | ||||||
|      * modifyLocationButton : button for start editing location |  | ||||||
|      */ |  | ||||||
|     Button modifyLocationButton; |  | ||||||
|     /** |  | ||||||
|      * removeLocationButton : button to remove location metadata |  | ||||||
|      */ |  | ||||||
|     Button removeLocationButton; |  | ||||||
|     /** |  | ||||||
|      * showInMapButton : button for showing in map |  | ||||||
|      */ |  | ||||||
|     TextView showInMapButton; |  | ||||||
|     /** |  | ||||||
|      * placeSelectedButton : fab for selecting location |  | ||||||
|      */ |  | ||||||
|     FloatingActionButton placeSelectedButton; |  | ||||||
|     /** |  | ||||||
|      * fabCenterOnLocation: button for center on location; |  | ||||||
|      */ |  | ||||||
|     FloatingActionButton fabCenterOnLocation; |  | ||||||
|     /** |  | ||||||
|      * shadow : imageview of shadow |  | ||||||
|      */ |  | ||||||
|     private ImageView shadow; |  | ||||||
|     /** |  | ||||||
|      * largeToolbarText : textView of shadow |  | ||||||
|      */ |  | ||||||
|     private TextView largeToolbarText; |  | ||||||
|     /** |  | ||||||
|      * smallToolbarText : textView of shadow |  | ||||||
|      */ |  | ||||||
|     private TextView smallToolbarText; |  | ||||||
|     /** |  | ||||||
|      * applicationKvStore : for storing values |  | ||||||
|      */ |  | ||||||
|     @Inject |  | ||||||
|     @Named("default_preferences") |  | ||||||
|     public |  | ||||||
|     JsonKvStore applicationKvStore; |  | ||||||
|     BasicKvStore store; |  | ||||||
|     /** |  | ||||||
|      * isDarkTheme: for keeping a track of the device theme and modifying the map theme accordingly |  | ||||||
|      */ |  | ||||||
|     @Inject |  | ||||||
|     SystemThemeUtils systemThemeUtils; |  | ||||||
|     private boolean isDarkTheme; |  | ||||||
|     private boolean moveToCurrentLocation; |  | ||||||
| 
 |  | ||||||
|     @Inject |  | ||||||
|     LocationServiceManager locationManager; |  | ||||||
|     LocationPermissionsHelper locationPermissionsHelper; |  | ||||||
| 
 |  | ||||||
|     @Inject |  | ||||||
|     SessionManager sessionManager; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Constants |  | ||||||
|      */ |  | ||||||
|     private static final String CAMERA_POS = "cameraPosition"; |  | ||||||
|     private static final String ACTIVITY = "activity"; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     @SuppressLint("ClickableViewAccessibility") |  | ||||||
|     @Override |  | ||||||
|     protected void onCreate(@Nullable final Bundle savedInstanceState) { |  | ||||||
|         getWindow().requestFeature(Window.FEATURE_ACTION_BAR); |  | ||||||
|         super.onCreate(savedInstanceState); |  | ||||||
| 
 |  | ||||||
|         isDarkTheme = systemThemeUtils.isDeviceInNightMode(); |  | ||||||
|         moveToCurrentLocation = false; |  | ||||||
|         store = new BasicKvStore(this, "LocationPermissions"); |  | ||||||
| 
 |  | ||||||
|         getWindow().requestFeature(Window.FEATURE_ACTION_BAR); |  | ||||||
|         final ActionBar actionBar = getSupportActionBar(); |  | ||||||
|         if (actionBar != null) { |  | ||||||
|             actionBar.hide(); |  | ||||||
|         } |  | ||||||
|         setContentView(R.layout.activity_location_picker); |  | ||||||
| 
 |  | ||||||
|         if (savedInstanceState == null) { |  | ||||||
|             cameraPosition = getIntent() |  | ||||||
|                 .getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION); |  | ||||||
|             activity = getIntent().getStringExtra(LocationPickerConstants.ACTIVITY_KEY); |  | ||||||
|             media = getIntent().getParcelableExtra(LocationPickerConstants.MEDIA); |  | ||||||
|         }else{ |  | ||||||
|             cameraPosition = savedInstanceState.getParcelable(CAMERA_POS); |  | ||||||
|             activity = savedInstanceState.getString(ACTIVITY); |  | ||||||
|             media = savedInstanceState.getParcelable("sMedia"); |  | ||||||
|         } |  | ||||||
|         bindViews(); |  | ||||||
|         addBackButtonListener(); |  | ||||||
|         addPlaceSelectedButton(); |  | ||||||
|         addCredits(); |  | ||||||
|         getToolbarUI(); |  | ||||||
|         addCenterOnGPSButton(); |  | ||||||
| 
 |  | ||||||
|         org.osmdroid.config.Configuration.getInstance().load(getApplicationContext(), |  | ||||||
|             PreferenceManager.getDefaultSharedPreferences(getApplicationContext())); |  | ||||||
| 
 |  | ||||||
|         mapView.setTileSource(TileSourceFactory.WIKIMEDIA); |  | ||||||
|         mapView.setTilesScaledToDpi(true); |  | ||||||
|         mapView.setMultiTouchControls(true); |  | ||||||
| 
 |  | ||||||
|         org.osmdroid.config.Configuration.getInstance().getAdditionalHttpRequestProperties().put( |  | ||||||
|             "Referer", "http://maps.wikimedia.org/" |  | ||||||
|         ); |  | ||||||
|         mapView.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); |  | ||||||
|         mapView.getController().setZoom(ZOOM_LEVEL); |  | ||||||
|         mapView.setOnTouchListener((v, event) -> { |  | ||||||
|             if (event.getAction() == MotionEvent.ACTION_MOVE) { |  | ||||||
|                 if (markerImage.getTranslationY() == 0) { |  | ||||||
|                     markerImage.animate().translationY(-75) |  | ||||||
|                         .setInterpolator(new OvershootInterpolator()).setDuration(250).start(); |  | ||||||
|                 } |  | ||||||
|             } else if (event.getAction() == MotionEvent.ACTION_UP) { |  | ||||||
|                 markerImage.animate().translationY(0) |  | ||||||
|                     .setInterpolator(new OvershootInterpolator()).setDuration(250).start(); |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         if ("UploadActivity".equals(activity)) { |  | ||||||
|             placeSelectedButton.setVisibility(View.GONE); |  | ||||||
|             modifyLocationButton.setVisibility(View.VISIBLE); |  | ||||||
|             removeLocationButton.setVisibility(View.VISIBLE); |  | ||||||
|             showInMapButton.setVisibility(View.VISIBLE); |  | ||||||
|             largeToolbarText.setText(getResources().getString(R.string.image_location)); |  | ||||||
|             smallToolbarText.setText(getResources(). |  | ||||||
|                 getString(R.string.check_whether_location_is_correct)); |  | ||||||
|             fabCenterOnLocation.setVisibility(View.GONE); |  | ||||||
|             markerImage.setVisibility(View.GONE); |  | ||||||
|             shadow.setVisibility(View.GONE); |  | ||||||
|             assert cameraPosition != null; |  | ||||||
|             showSelectedLocationMarker(new GeoPoint(cameraPosition.getLatitude(), |  | ||||||
|                 cameraPosition.getLongitude())); |  | ||||||
|         } |  | ||||||
|         setupMapView(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Moves the center of the map to the specified coordinates |  | ||||||
|      * |  | ||||||
|      */ |  | ||||||
|     private void moveMapTo(double latitude, double longitude){ |  | ||||||
|         if(mapView != null && mapView.getController() != null){ |  | ||||||
|             GeoPoint point = new GeoPoint(latitude, longitude); |  | ||||||
| 
 |  | ||||||
|             mapView.getController().setCenter(point); |  | ||||||
|             mapView.getController().animateTo(point); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Moves the center of the map to the specified coordinates |  | ||||||
|      * @param point The GeoPoint object which contains the coordinates to move to |  | ||||||
|      */ |  | ||||||
|     private void moveMapTo(GeoPoint point){ |  | ||||||
|         if(point != null){ |  | ||||||
|             moveMapTo(point.getLatitude(), point.getLongitude()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * For showing credits |  | ||||||
|      */ |  | ||||||
|     private void addCredits() { |  | ||||||
|         tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); |  | ||||||
|         tvAttribution.setMovementMethod(LinkMovementMethod.getInstance()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * For setting up Dark Theme |  | ||||||
|      */ |  | ||||||
|     private void darkThemeSetup() { |  | ||||||
|         if (isDarkTheme) { |  | ||||||
|             shadow.setColorFilter(Color.argb(255, 255, 255, 255)); |  | ||||||
|             mapView.getOverlayManager().getTilesOverlay() |  | ||||||
|                 .setColorFilter(TilesOverlay.INVERT_COLORS); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Clicking back button destroy locationPickerActivity |  | ||||||
|      */ |  | ||||||
|     private void addBackButtonListener() { |  | ||||||
|         final ImageView backButton = findViewById(R.id.maplibre_place_picker_toolbar_back_button); |  | ||||||
|         backButton.setOnClickListener(v -> { |  | ||||||
|             finish(); |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Binds mapView and location picker icon |  | ||||||
|      */ |  | ||||||
|     private void bindViews() { |  | ||||||
|         mapView = findViewById(R.id.map_view); |  | ||||||
|         markerImage = findViewById(R.id.location_picker_image_view_marker); |  | ||||||
|         tvAttribution = findViewById(R.id.tv_attribution); |  | ||||||
|         modifyLocationButton = findViewById(R.id.modify_location); |  | ||||||
|         removeLocationButton = findViewById(R.id.remove_location); |  | ||||||
|         showInMapButton = findViewById(R.id.show_in_map); |  | ||||||
|         showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase( |  | ||||||
|             Locale.ROOT)); |  | ||||||
|         shadow = findViewById(R.id.location_picker_image_view_shadow); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Gets toolbar color |  | ||||||
|      */ |  | ||||||
|     private void getToolbarUI() { |  | ||||||
|         final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar); |  | ||||||
|         largeToolbarText = findViewById(R.id.location_picker_toolbar_primary_text_view); |  | ||||||
|         smallToolbarText = findViewById(R.id.location_picker_toolbar_secondary_text_view); |  | ||||||
|         toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void setupMapView() { |  | ||||||
|         requestLocationPermissions(); |  | ||||||
| 
 |  | ||||||
|         //If location metadata is available, move map to that location. |  | ||||||
|         if(activity.equals("UploadActivity") || activity.equals("MediaActivity")){ |  | ||||||
|             moveMapToMediaLocation(); |  | ||||||
|         } else { |  | ||||||
|             //If location metadata is not available, move map to device GPS location. |  | ||||||
|             moveMapToGPSLocation(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         modifyLocationButton.setOnClickListener(v -> onClickModifyLocation()); |  | ||||||
|         removeLocationButton.setOnClickListener(v -> onClickRemoveLocation()); |  | ||||||
|         showInMapButton.setOnClickListener(v -> showInMapApp()); |  | ||||||
|         darkThemeSetup(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Handles onclick event of modifyLocationButton |  | ||||||
|      */ |  | ||||||
|     private void onClickModifyLocation() { |  | ||||||
|         placeSelectedButton.setVisibility(View.VISIBLE); |  | ||||||
|         modifyLocationButton.setVisibility(View.GONE); |  | ||||||
|         removeLocationButton.setVisibility(View.GONE); |  | ||||||
|         showInMapButton.setVisibility(View.GONE); |  | ||||||
|         markerImage.setVisibility(View.VISIBLE); |  | ||||||
|         shadow.setVisibility(View.VISIBLE); |  | ||||||
|         largeToolbarText.setText(getResources().getString(R.string.choose_a_location)); |  | ||||||
|         smallToolbarText.setText(getResources().getString(R.string.pan_and_zoom_to_adjust)); |  | ||||||
|         fabCenterOnLocation.setVisibility(View.VISIBLE); |  | ||||||
|         removeSelectedLocationMarker(); |  | ||||||
|         moveMapToMediaLocation(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Handles onclick event of removeLocationButton |  | ||||||
|      */ |  | ||||||
|     private void onClickRemoveLocation() { |  | ||||||
|         DialogUtil.showAlertDialog(this, |  | ||||||
|             getString(R.string.remove_location_warning_title), |  | ||||||
|             getString(R.string.remove_location_warning_desc), |  | ||||||
|             getString(R.string.continue_message), |  | ||||||
|             getString(R.string.cancel), () -> removeLocationFromImage(), null); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Method to remove the location from the picture |  | ||||||
|      */ |  | ||||||
|     private void removeLocationFromImage() { |  | ||||||
|         if (media != null) { |  | ||||||
|             getCompositeDisposable().add(coordinateEditHelper.makeCoordinatesEdit(getApplicationContext() |  | ||||||
|                     , media, "0.0", "0.0", "0.0f") |  | ||||||
|                 .subscribeOn(Schedulers.io()) |  | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |  | ||||||
|                 .subscribe(s -> { |  | ||||||
|                     Timber.d("Coordinates are removed from the image"); |  | ||||||
|                 })); |  | ||||||
|         } |  | ||||||
|         final Intent returningIntent = new Intent(); |  | ||||||
|         setResult(AppCompatActivity.RESULT_OK, returningIntent); |  | ||||||
|         finish(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Show the location in map app. Map will center on the location metadata, if available. |  | ||||||
|      * If there is no location metadata, the map will center on the commons app map center. |  | ||||||
|      */ |  | ||||||
|     private void showInMapApp() { |  | ||||||
|         fr.free.nrw.commons.location.LatLng position = null; |  | ||||||
| 
 |  | ||||||
|         if(activity.equals("UploadActivity") && cameraPosition != null){ |  | ||||||
|             //location metadata is available |  | ||||||
|             position = new fr.free.nrw.commons.location.LatLng(cameraPosition.getLatitude(), |  | ||||||
|                 cameraPosition.getLongitude(), 0.0f); |  | ||||||
|         } else if(mapView != null){ |  | ||||||
|             //location metadata is not available |  | ||||||
|             position = new fr.free.nrw.commons.location.LatLng(mapView.getMapCenter().getLatitude(), |  | ||||||
|                 mapView.getMapCenter().getLongitude(), 0.0f); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(position != null){ |  | ||||||
|             Utils.handleGeoCoordinates(this, position); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Moves the center of the map to the media's location, if that data |  | ||||||
|      * is available. |  | ||||||
|      */ |  | ||||||
|     private void moveMapToMediaLocation() { |  | ||||||
|         if (cameraPosition != null) { |  | ||||||
| 
 |  | ||||||
|             GeoPoint point = new GeoPoint(cameraPosition.getLatitude(), |  | ||||||
|                 cameraPosition.getLongitude()); |  | ||||||
| 
 |  | ||||||
|             moveMapTo(point); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Moves the center of the map to the device's GPS location, if that data is available. |  | ||||||
|      */ |  | ||||||
|     private void moveMapToGPSLocation(){ |  | ||||||
|         if(locationManager != null){ |  | ||||||
|             fr.free.nrw.commons.location.LatLng location = locationManager.getLastLocation(); |  | ||||||
| 
 |  | ||||||
|             if(location != null){ |  | ||||||
|                 GeoPoint point = new GeoPoint(location.getLatitude(), location.getLongitude()); |  | ||||||
| 
 |  | ||||||
|                 moveMapTo(point); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Select the preferable location |  | ||||||
|      */ |  | ||||||
|     private void addPlaceSelectedButton() { |  | ||||||
|         placeSelectedButton = findViewById(R.id.location_chosen_button); |  | ||||||
|         placeSelectedButton.setOnClickListener(view -> placeSelected()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the intent with required data |  | ||||||
|      */ |  | ||||||
|     void placeSelected() { |  | ||||||
|         if (activity.equals("NoLocationUploadActivity")) { |  | ||||||
|             applicationKvStore.putString(LAST_LOCATION, |  | ||||||
|                 mapView.getMapCenter().getLatitude() |  | ||||||
|                     + "," |  | ||||||
|                     + mapView.getMapCenter().getLongitude()); |  | ||||||
|             applicationKvStore.putString(LAST_ZOOM, mapView.getZoomLevel() + ""); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (media == null) { |  | ||||||
|             final Intent returningIntent = new Intent(); |  | ||||||
|             returningIntent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, |  | ||||||
|                 new CameraPosition(mapView.getMapCenter().getLatitude(), |  | ||||||
|                     mapView.getMapCenter().getLongitude(), 14.0)); |  | ||||||
|             setResult(AppCompatActivity.RESULT_OK, returningIntent); |  | ||||||
|         } else { |  | ||||||
|             updateCoordinates(String.valueOf(mapView.getMapCenter().getLatitude()), |  | ||||||
|                 String.valueOf(mapView.getMapCenter().getLongitude()), |  | ||||||
|                 String.valueOf(0.0f)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         finish(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Fetched coordinates are replaced with existing coordinates by a POST API call. |  | ||||||
|      * @param Latitude to be added |  | ||||||
|      * @param Longitude to be added |  | ||||||
|      * @param Accuracy to be added |  | ||||||
|      */ |  | ||||||
|     public void updateCoordinates(final String Latitude, final String Longitude, |  | ||||||
|         final String Accuracy) { |  | ||||||
|         if (media == null) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         try { |  | ||||||
|             getCompositeDisposable().add( |  | ||||||
|                 coordinateEditHelper.makeCoordinatesEdit(getApplicationContext(), media, |  | ||||||
|                         Latitude, Longitude, Accuracy) |  | ||||||
|                     .subscribeOn(Schedulers.io()) |  | ||||||
|                     .observeOn(AndroidSchedulers.mainThread()) |  | ||||||
|                     .subscribe(s -> { |  | ||||||
|                             Timber.d("Coordinates are added."); |  | ||||||
|                         })); |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) { |  | ||||||
|                 final String username = sessionManager.getUserName(); |  | ||||||
|                 final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener( |  | ||||||
|                     this, |  | ||||||
|                     getString(R.string.invalid_login_message), |  | ||||||
|                     username |  | ||||||
|                 ); |  | ||||||
| 
 |  | ||||||
|                 CommonsApplication.getInstance().clearApplicationData( |  | ||||||
|                     this, logoutListener); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Center the camera on the last saved location |  | ||||||
|      */ |  | ||||||
|     private void addCenterOnGPSButton() { |  | ||||||
|         fabCenterOnLocation = findViewById(R.id.center_on_gps); |  | ||||||
|         fabCenterOnLocation.setOnClickListener(view -> { |  | ||||||
|             moveToCurrentLocation = true; |  | ||||||
|             requestLocationPermissions(); |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Adds selected location marker on the map |  | ||||||
|      */ |  | ||||||
|     private void showSelectedLocationMarker(GeoPoint point) { |  | ||||||
|         Drawable icon = ContextCompat.getDrawable(this, R.drawable.map_default_map_marker); |  | ||||||
|         Marker marker = new Marker(mapView); |  | ||||||
|         marker.setPosition(point); |  | ||||||
|         marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM); |  | ||||||
|         marker.setIcon(icon); |  | ||||||
|         marker.setInfoWindow(null); |  | ||||||
|         mapView.getOverlays().add(marker); |  | ||||||
|         mapView.invalidate(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Removes selected location marker from the map |  | ||||||
|      */ |  | ||||||
|     private void removeSelectedLocationMarker() { |  | ||||||
|         List<Overlay> overlays = mapView.getOverlays(); |  | ||||||
|         for (int i = 0; i < overlays.size(); i++) { |  | ||||||
|             if (overlays.get(i) instanceof Marker) { |  | ||||||
|                 Marker item = (Marker) overlays.get(i); |  | ||||||
|                 if (cameraPosition.getLatitude() == item.getPosition().getLatitude() |  | ||||||
|                     && cameraPosition.getLongitude() == item.getPosition().getLongitude()) { |  | ||||||
|                     mapView.getOverlays().remove(i); |  | ||||||
|                     mapView.invalidate(); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Center the map at user's current location |  | ||||||
|      */ |  | ||||||
|     private void requestLocationPermissions() { |  | ||||||
|         locationPermissionsHelper = new LocationPermissionsHelper( |  | ||||||
|             this, locationManager, this); |  | ||||||
|         locationPermissionsHelper.requestForLocationAccess(R.string.location_permission_title, |  | ||||||
|             R.string.upload_map_location_access); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onRequestPermissionsResult(final int requestCode, |  | ||||||
|         @NonNull final String[] permissions, |  | ||||||
|         @NonNull final int[] grantResults) { |  | ||||||
|         if (requestCode == Constants.RequestCodes.LOCATION |  | ||||||
|             && grantResults[0] == PackageManager.PERMISSION_GRANTED) { |  | ||||||
|             onLocationPermissionGranted(); |  | ||||||
|         } else { |  | ||||||
|             onLocationPermissionDenied(getString(R.string.upload_map_location_access)); |  | ||||||
|         } |  | ||||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     protected void onResume() { |  | ||||||
|         super.onResume(); |  | ||||||
|         mapView.onResume(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     protected void onPause() { |  | ||||||
|         super.onPause(); |  | ||||||
|         mapView.onPause(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onLocationPermissionDenied(String toastMessage) { |  | ||||||
|         if (!ActivityCompat.shouldShowRequestPermissionRationale(this, |  | ||||||
|             permission.ACCESS_FINE_LOCATION)) { |  | ||||||
|             if (!locationPermissionsHelper.checkLocationPermission(this)) { |  | ||||||
|                 if (store.getBoolean("isPermissionDenied", false)) { |  | ||||||
|                     // means user has denied location permission twice or checked the "Don't show again" |  | ||||||
|                     locationPermissionsHelper.showAppSettingsDialog(this, |  | ||||||
|                         R.string.upload_map_location_access); |  | ||||||
|                 } else { |  | ||||||
|                     Toast.makeText(getBaseContext(), toastMessage, Toast.LENGTH_LONG).show(); |  | ||||||
|                 } |  | ||||||
|                 store.putBoolean("isPermissionDenied", true); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             Toast.makeText(getBaseContext(), toastMessage, Toast.LENGTH_LONG).show(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onLocationPermissionGranted() { |  | ||||||
|         if (moveToCurrentLocation || !(activity.equals("MediaActivity"))) { |  | ||||||
|             if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { |  | ||||||
|                 locationManager.requestLocationUpdatesFromProvider( |  | ||||||
|                     LocationManager.NETWORK_PROVIDER); |  | ||||||
|                 locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); |  | ||||||
|                 addMarkerAtGPSLocation(); |  | ||||||
|             } else { |  | ||||||
|                 addMarkerAtGPSLocation(); |  | ||||||
|                 locationPermissionsHelper.showLocationOffDialog(this, |  | ||||||
|                     R.string.ask_to_turn_location_on_text); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Adds a marker to the map at the most recent GPS location |  | ||||||
|      * (which may be the current GPS location). |  | ||||||
|      */ |  | ||||||
|     private void addMarkerAtGPSLocation() { |  | ||||||
|         fr.free.nrw.commons.location.LatLng currLocation = locationManager.getLastLocation(); |  | ||||||
|         if (currLocation != null) { |  | ||||||
|             GeoPoint currLocationGeopoint = new GeoPoint(currLocation.getLatitude(), |  | ||||||
|                 currLocation.getLongitude()); |  | ||||||
|             addLocationMarker(currLocationGeopoint); |  | ||||||
|             markerImage.setTranslationY(0); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void addLocationMarker(GeoPoint geoPoint) { |  | ||||||
|         if (moveToCurrentLocation) { |  | ||||||
|             mapView.getOverlays().clear(); |  | ||||||
|         } |  | ||||||
|         ScaleDiskOverlay diskOverlay = |  | ||||||
|             new ScaleDiskOverlay(this, |  | ||||||
|                 geoPoint, 2000, GeoConstants.UnitOfMeasure.foot); |  | ||||||
|         Paint circlePaint = new Paint(); |  | ||||||
|         circlePaint.setColor(Color.rgb(128, 128, 128)); |  | ||||||
|         circlePaint.setStyle(Paint.Style.STROKE); |  | ||||||
|         circlePaint.setStrokeWidth(2f); |  | ||||||
|         diskOverlay.setCirclePaint2(circlePaint); |  | ||||||
|         Paint diskPaint = new Paint(); |  | ||||||
|         diskPaint.setColor(Color.argb(40, 128, 128, 128)); |  | ||||||
|         diskPaint.setStyle(Paint.Style.FILL_AND_STROKE); |  | ||||||
|         diskOverlay.setCirclePaint1(diskPaint); |  | ||||||
|         diskOverlay.setDisplaySizeMin(900); |  | ||||||
|         diskOverlay.setDisplaySizeMax(1700); |  | ||||||
|         mapView.getOverlays().add(diskOverlay); |  | ||||||
|         org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker( |  | ||||||
|             mapView); |  | ||||||
|         startMarker.setPosition(geoPoint); |  | ||||||
|         startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, |  | ||||||
|             org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM); |  | ||||||
|         startMarker.setIcon( |  | ||||||
|             ContextCompat.getDrawable(this, R.drawable.current_location_marker)); |  | ||||||
|         startMarker.setTitle("Your Location"); |  | ||||||
|         startMarker.setTextLabelFontSize(24); |  | ||||||
|         mapView.getOverlays().add(startMarker); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Saves the state of the activity |  | ||||||
|      * @param outState Bundle |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     public void onSaveInstanceState(@NonNull final Bundle outState) { |  | ||||||
|         super.onSaveInstanceState(outState); |  | ||||||
|         if(cameraPosition!=null){ |  | ||||||
|             outState.putParcelable(CAMERA_POS, cameraPosition); |  | ||||||
|         } |  | ||||||
|         if(activity!=null){ |  | ||||||
|             outState.putString(ACTIVITY, activity); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if(media!=null){ |  | ||||||
|             outState.putParcelable("sMedia", media); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,678 @@ | ||||||
|  | package fr.free.nrw.commons.LocationPicker | ||||||
|  | 
 | ||||||
|  | import android.Manifest.permission | ||||||
|  | import android.annotation.SuppressLint | ||||||
|  | import android.content.Intent | ||||||
|  | import android.content.pm.PackageManager | ||||||
|  | import android.graphics.Color | ||||||
|  | import android.graphics.Paint | ||||||
|  | import android.location.LocationManager | ||||||
|  | import android.os.Bundle | ||||||
|  | import android.preference.PreferenceManager | ||||||
|  | import android.text.Html | ||||||
|  | import android.text.method.LinkMovementMethod | ||||||
|  | import android.view.MotionEvent | ||||||
|  | import android.view.View | ||||||
|  | import android.view.Window | ||||||
|  | import android.view.animation.OvershootInterpolator | ||||||
|  | import android.widget.Button | ||||||
|  | import android.widget.ImageView | ||||||
|  | import android.widget.TextView | ||||||
|  | import android.widget.Toast | ||||||
|  | import androidx.appcompat.widget.AppCompatTextView | ||||||
|  | import androidx.constraintlayout.widget.ConstraintLayout | ||||||
|  | import androidx.core.app.ActivityCompat | ||||||
|  | import androidx.core.content.ContextCompat | ||||||
|  | import com.google.android.material.floatingactionbutton.FloatingActionButton | ||||||
|  | import fr.free.nrw.commons.CameraPosition | ||||||
|  | import fr.free.nrw.commons.CommonsApplication | ||||||
|  | import fr.free.nrw.commons.Media | ||||||
|  | import fr.free.nrw.commons.R | ||||||
|  | import fr.free.nrw.commons.Utils | ||||||
|  | import fr.free.nrw.commons.auth.SessionManager | ||||||
|  | import fr.free.nrw.commons.auth.csrf.CsrfTokenClient | ||||||
|  | import fr.free.nrw.commons.coordinates.CoordinateEditHelper | ||||||
|  | import fr.free.nrw.commons.filepicker.Constants | ||||||
|  | import fr.free.nrw.commons.kvstore.BasicKvStore | ||||||
|  | import fr.free.nrw.commons.kvstore.JsonKvStore | ||||||
|  | import fr.free.nrw.commons.location.LocationPermissionsHelper | ||||||
|  | import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback | ||||||
|  | import fr.free.nrw.commons.location.LocationServiceManager | ||||||
|  | import fr.free.nrw.commons.theme.BaseActivity | ||||||
|  | import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION | ||||||
|  | import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM | ||||||
|  | import fr.free.nrw.commons.utils.DialogUtil | ||||||
|  | import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL | ||||||
|  | import io.reactivex.android.schedulers.AndroidSchedulers | ||||||
|  | import io.reactivex.schedulers.Schedulers | ||||||
|  | import org.osmdroid.tileprovider.tilesource.TileSourceFactory | ||||||
|  | import org.osmdroid.util.GeoPoint | ||||||
|  | import org.osmdroid.util.constants.GeoConstants | ||||||
|  | import org.osmdroid.views.CustomZoomButtonsController | ||||||
|  | import org.osmdroid.views.overlay.Marker | ||||||
|  | import org.osmdroid.views.overlay.ScaleDiskOverlay | ||||||
|  | import org.osmdroid.views.overlay.TilesOverlay | ||||||
|  | import timber.log.Timber | ||||||
|  | import java.util.Locale | ||||||
|  | import javax.inject.Inject | ||||||
|  | import javax.inject.Named | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Helps to pick location and return the result with an intent | ||||||
|  |  */ | ||||||
|  | class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { | ||||||
|  |     /** | ||||||
|  |      * coordinateEditHelper: helps to edit coordinates | ||||||
|  |      */ | ||||||
|  |     @Inject | ||||||
|  |     lateinit var coordinateEditHelper: CoordinateEditHelper | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * media : Media object | ||||||
|  |      */ | ||||||
|  |     private var media: Media? = null | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * cameraPosition : position of picker | ||||||
|  |      */ | ||||||
|  |     private var cameraPosition: CameraPosition? = null | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * markerImage : picker image | ||||||
|  |      */ | ||||||
|  |     private lateinit var markerImage: ImageView | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * mapView : OSM Map | ||||||
|  |      */ | ||||||
|  |     private var mapView: org.osmdroid.views.MapView? = null | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * tvAttribution : credit | ||||||
|  |      */ | ||||||
|  |     private lateinit var tvAttribution: AppCompatTextView | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * activity : activity key | ||||||
|  |      */ | ||||||
|  |     private var activity: String? = null | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * modifyLocationButton : button for start editing location | ||||||
|  |      */ | ||||||
|  |     private lateinit var modifyLocationButton: Button | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * removeLocationButton : button to remove location metadata | ||||||
|  |      */ | ||||||
|  |     private lateinit var removeLocationButton: Button | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * showInMapButton : button for showing in map | ||||||
|  |      */ | ||||||
|  |     private lateinit var showInMapButton: TextView | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * placeSelectedButton : fab for selecting location | ||||||
|  |      */ | ||||||
|  |     private lateinit var placeSelectedButton: FloatingActionButton | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * fabCenterOnLocation: button for center on location; | ||||||
|  |      */ | ||||||
|  |     private lateinit var fabCenterOnLocation: FloatingActionButton | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * shadow : imageview of shadow | ||||||
|  |      */ | ||||||
|  |     private lateinit var shadow: ImageView | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * largeToolbarText : textView of shadow | ||||||
|  |      */ | ||||||
|  |     private lateinit var largeToolbarText: TextView | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * smallToolbarText : textView of shadow | ||||||
|  |      */ | ||||||
|  |     private lateinit var smallToolbarText: TextView | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * applicationKvStore : for storing values | ||||||
|  |      */ | ||||||
|  |     @Inject | ||||||
|  |     @field: Named("default_preferences") | ||||||
|  |     lateinit var applicationKvStore: JsonKvStore | ||||||
|  |     private lateinit var store: BasicKvStore | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * isDarkTheme: for keeping a track of the device theme and modifying the map theme accordingly | ||||||
|  |      */ | ||||||
|  |     private var isDarkTheme: Boolean = false | ||||||
|  |     private var moveToCurrentLocation: Boolean = false | ||||||
|  | 
 | ||||||
|  |     @Inject | ||||||
|  |     lateinit var locationManager: LocationServiceManager | ||||||
|  |     private lateinit var locationPermissionsHelper: LocationPermissionsHelper | ||||||
|  | 
 | ||||||
|  |     @Inject | ||||||
|  |     lateinit var sessionManager: SessionManager | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Constants | ||||||
|  |      */ | ||||||
|  |     companion object { | ||||||
|  |         private const val CAMERA_POS = "cameraPosition" | ||||||
|  |         private const val ACTIVITY = "activity" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @SuppressLint("ClickableViewAccessibility") | ||||||
|  |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|  |         requestWindowFeature(Window.FEATURE_ACTION_BAR) | ||||||
|  |         super.onCreate(savedInstanceState) | ||||||
|  | 
 | ||||||
|  |         isDarkTheme = systemThemeUtils.isDeviceInNightMode() | ||||||
|  |         moveToCurrentLocation = false | ||||||
|  |         store = BasicKvStore(this, "LocationPermissions") | ||||||
|  | 
 | ||||||
|  |         requestWindowFeature(Window.FEATURE_ACTION_BAR) | ||||||
|  |         supportActionBar?.hide() | ||||||
|  |         setContentView(R.layout.activity_location_picker) | ||||||
|  | 
 | ||||||
|  |         if (savedInstanceState == null) { | ||||||
|  |             cameraPosition = intent.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION) | ||||||
|  |             activity = intent.getStringExtra(LocationPickerConstants.ACTIVITY_KEY) | ||||||
|  |             media = intent.getParcelableExtra(LocationPickerConstants.MEDIA) | ||||||
|  |         } else { | ||||||
|  |             cameraPosition = savedInstanceState.getParcelable(CAMERA_POS) | ||||||
|  |             activity = savedInstanceState.getString(ACTIVITY) | ||||||
|  |             media = savedInstanceState.getParcelable("sMedia") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         bindViews() | ||||||
|  |         addBackButtonListener() | ||||||
|  |         addPlaceSelectedButton() | ||||||
|  |         addCredits() | ||||||
|  |         getToolbarUI() | ||||||
|  |         addCenterOnGPSButton() | ||||||
|  | 
 | ||||||
|  |         org.osmdroid.config.Configuration.getInstance() | ||||||
|  |             .load( | ||||||
|  |                 applicationContext, PreferenceManager.getDefaultSharedPreferences( | ||||||
|  |                 applicationContext | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         mapView?.setTileSource(TileSourceFactory.WIKIMEDIA) | ||||||
|  |         mapView?.setTilesScaledToDpi(true) | ||||||
|  |         mapView?.setMultiTouchControls(true) | ||||||
|  | 
 | ||||||
|  |         org.osmdroid.config.Configuration.getInstance().additionalHttpRequestProperties["Referer"] = | ||||||
|  |             "http://maps.wikimedia.org/" | ||||||
|  |         mapView?.zoomController?.setVisibility(CustomZoomButtonsController.Visibility.NEVER) | ||||||
|  |         mapView?.controller?.setZoom(ZOOM_LEVEL.toDouble()) | ||||||
|  |         mapView?.setOnTouchListener { _, event -> | ||||||
|  |             when (event.action) { | ||||||
|  |                 MotionEvent.ACTION_MOVE -> { | ||||||
|  |                     if (markerImage.translationY == 0f) { | ||||||
|  |                         markerImage.animate().translationY(-75f) | ||||||
|  |                             .setInterpolator(OvershootInterpolator()).duration = 250 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 MotionEvent.ACTION_UP -> { | ||||||
|  |                     markerImage.animate().translationY(0f) | ||||||
|  |                         .setInterpolator(OvershootInterpolator()).duration = 250 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             false | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (activity == "UploadActivity") { | ||||||
|  |             placeSelectedButton.visibility = View.GONE | ||||||
|  |             modifyLocationButton.visibility = View.VISIBLE | ||||||
|  |             removeLocationButton.visibility = View.VISIBLE | ||||||
|  |             showInMapButton.visibility = View.VISIBLE | ||||||
|  |             largeToolbarText.text = getString(R.string.image_location) | ||||||
|  |             smallToolbarText.text = getString(R.string.check_whether_location_is_correct) | ||||||
|  |             fabCenterOnLocation.visibility = View.GONE | ||||||
|  |             markerImage.visibility = View.GONE | ||||||
|  |             shadow.visibility = View.GONE | ||||||
|  |             cameraPosition?.let { | ||||||
|  |                 showSelectedLocationMarker(GeoPoint(it.latitude, it.longitude)) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         setupMapView() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Moves the center of the map to the specified coordinates | ||||||
|  |      */ | ||||||
|  |     private fun moveMapTo(latitude: Double, longitude: Double) { | ||||||
|  |         mapView?.controller?.let { | ||||||
|  |             val point = GeoPoint(latitude, longitude) | ||||||
|  |             it.setCenter(point) | ||||||
|  |             it.animateTo(point) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Moves the center of the map to the specified coordinates | ||||||
|  |      * @param point The GeoPoint object which contains the coordinates to move to | ||||||
|  |      */ | ||||||
|  |     private fun moveMapTo(point: GeoPoint?) { | ||||||
|  |         point?.let { | ||||||
|  |             moveMapTo(it.latitude, it.longitude) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * For showing credits | ||||||
|  |      */ | ||||||
|  |     private fun addCredits() { | ||||||
|  |         tvAttribution.text = Html.fromHtml(getString(R.string.map_attribution)) | ||||||
|  |         tvAttribution.movementMethod = LinkMovementMethod.getInstance() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * For setting up Dark Theme | ||||||
|  |      */ | ||||||
|  |     private fun darkThemeSetup() { | ||||||
|  |         if (isDarkTheme) { | ||||||
|  |             shadow.setColorFilter(Color.argb(255, 255, 255, 255)) | ||||||
|  |             mapView?.overlayManager?.tilesOverlay?.setColorFilter(TilesOverlay.INVERT_COLORS) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Clicking back button destroy locationPickerActivity | ||||||
|  |      */ | ||||||
|  |     private fun addBackButtonListener() { | ||||||
|  |         val backButton = findViewById<ImageView>(R.id.maplibre_place_picker_toolbar_back_button) | ||||||
|  |         backButton.setOnClickListener { | ||||||
|  |             finish() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Binds mapView and location picker icon | ||||||
|  |      */ | ||||||
|  |     private fun bindViews() { | ||||||
|  |         mapView = findViewById(R.id.map_view) | ||||||
|  |         markerImage = findViewById(R.id.location_picker_image_view_marker) | ||||||
|  |         tvAttribution = findViewById(R.id.tv_attribution) | ||||||
|  |         modifyLocationButton = findViewById(R.id.modify_location) | ||||||
|  |         removeLocationButton = findViewById(R.id.remove_location) | ||||||
|  |         showInMapButton = findViewById(R.id.show_in_map) | ||||||
|  |         showInMapButton.text = getString(R.string.show_in_map_app).uppercase(Locale.ROOT) | ||||||
|  |         shadow = findViewById(R.id.location_picker_image_view_shadow) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets toolbar color | ||||||
|  |      */ | ||||||
|  |     private fun getToolbarUI() { | ||||||
|  |         val toolbar: ConstraintLayout = findViewById(R.id.location_picker_toolbar) | ||||||
|  |         largeToolbarText = findViewById(R.id.location_picker_toolbar_primary_text_view) | ||||||
|  |         smallToolbarText = findViewById(R.id.location_picker_toolbar_secondary_text_view) | ||||||
|  |         toolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.primaryColor)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun setupMapView() { | ||||||
|  |         requestLocationPermissions() | ||||||
|  | 
 | ||||||
|  |         //If location metadata is available, move map to that location. | ||||||
|  |         if (activity == "UploadActivity" || activity == "MediaActivity") { | ||||||
|  |             moveMapToMediaLocation() | ||||||
|  |         } else { | ||||||
|  |             //If location metadata is not available, move map to device GPS location. | ||||||
|  |             moveMapToGPSLocation() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         modifyLocationButton.setOnClickListener { onClickModifyLocation() } | ||||||
|  |         removeLocationButton.setOnClickListener { onClickRemoveLocation() } | ||||||
|  |         showInMapButton.setOnClickListener { showInMapApp() } | ||||||
|  |         darkThemeSetup() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Handles onClick event of modifyLocationButton | ||||||
|  |      */ | ||||||
|  |     private fun onClickModifyLocation() { | ||||||
|  |         placeSelectedButton.visibility = View.VISIBLE | ||||||
|  |         modifyLocationButton.visibility = View.GONE | ||||||
|  |         removeLocationButton.visibility = View.GONE | ||||||
|  |         showInMapButton.visibility = View.GONE | ||||||
|  |         markerImage.visibility = View.VISIBLE | ||||||
|  |         shadow.visibility = View.VISIBLE | ||||||
|  |         largeToolbarText.text = getString(R.string.choose_a_location) | ||||||
|  |         smallToolbarText.text = getString(R.string.pan_and_zoom_to_adjust) | ||||||
|  |         fabCenterOnLocation.visibility = View.VISIBLE | ||||||
|  |         removeSelectedLocationMarker() | ||||||
|  |         moveMapToMediaLocation() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Handles onClick event of removeLocationButton | ||||||
|  |      */ | ||||||
|  |     private fun onClickRemoveLocation() { | ||||||
|  |         DialogUtil.showAlertDialog( | ||||||
|  |             this, | ||||||
|  |             getString(R.string.remove_location_warning_title), | ||||||
|  |             getString(R.string.remove_location_warning_desc), | ||||||
|  |             getString(R.string.continue_message), | ||||||
|  |             getString(R.string.cancel), | ||||||
|  |             { removeLocationFromImage() }, | ||||||
|  |             null | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Removes location metadata from the image | ||||||
|  |      */ | ||||||
|  |     private fun removeLocationFromImage() { | ||||||
|  |         media?.let { | ||||||
|  |             compositeDisposable.add( | ||||||
|  |                 coordinateEditHelper.makeCoordinatesEdit( | ||||||
|  |                     applicationContext, it, "0.0", "0.0", "0.0f" | ||||||
|  |                 ) | ||||||
|  |                     .subscribeOn(Schedulers.io()) | ||||||
|  |                     .observeOn(AndroidSchedulers.mainThread()) | ||||||
|  |                     .subscribe { _ -> | ||||||
|  |                         Timber.d("Coordinates removed from the image") | ||||||
|  |                     } | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |         setResult(RESULT_OK, Intent()) | ||||||
|  |         finish() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Show location in map app | ||||||
|  |      */ | ||||||
|  |     private fun showInMapApp() { | ||||||
|  |         val position = when { | ||||||
|  |             //location metadata is available | ||||||
|  |             activity == "UploadActivity" && cameraPosition != null -> { | ||||||
|  |                 fr.free.nrw.commons.location.LatLng(cameraPosition!!.latitude, cameraPosition!!.longitude, 0.0f) | ||||||
|  |             } | ||||||
|  |             //location metadata is not available | ||||||
|  |             mapView != null -> { | ||||||
|  |                 fr.free.nrw.commons.location.LatLng( | ||||||
|  |                     mapView?.mapCenter?.latitude!!, | ||||||
|  |                     mapView?.mapCenter?.longitude!!, | ||||||
|  |                     0.0f | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             else -> null | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         position?.let { Utils.handleGeoCoordinates(this, it) } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Moves map to media's location | ||||||
|  |      */ | ||||||
|  |     private fun moveMapToMediaLocation() { | ||||||
|  |         cameraPosition?.let { | ||||||
|  |             moveMapTo(GeoPoint(it.latitude, it.longitude)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Moves map to GPS location | ||||||
|  |      */ | ||||||
|  |     private fun moveMapToGPSLocation() { | ||||||
|  |         locationManager.getLastLocation()?.let { | ||||||
|  |             moveMapTo(GeoPoint(it.latitude, it.longitude)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds "Place Selected" button | ||||||
|  |      */ | ||||||
|  |     private fun addPlaceSelectedButton() { | ||||||
|  |         placeSelectedButton = findViewById(R.id.location_chosen_button) | ||||||
|  |         placeSelectedButton.setOnClickListener { placeSelected() } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Handles "Place Selected" action | ||||||
|  |      */ | ||||||
|  |     private fun placeSelected() { | ||||||
|  |         if (activity == "NoLocationUploadActivity") { | ||||||
|  |             applicationKvStore.putString( | ||||||
|  |                 LAST_LOCATION, | ||||||
|  |                 "${mapView?.mapCenter?.latitude},${mapView?.mapCenter?.longitude}" | ||||||
|  |             ) | ||||||
|  |             applicationKvStore.putString(LAST_ZOOM, mapView?.zoomLevel?.toString()!!) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (media == null) { | ||||||
|  |             val intent = Intent().apply { | ||||||
|  |                 putExtra( | ||||||
|  |                     LocationPickerConstants.MAP_CAMERA_POSITION, | ||||||
|  |                     CameraPosition(mapView?.mapCenter?.latitude!!, mapView?.mapCenter?.longitude!!, 14.0) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |             setResult(RESULT_OK, intent) | ||||||
|  |         } else { | ||||||
|  |             updateCoordinates( | ||||||
|  |                 mapView?.mapCenter?.latitude.toString(), | ||||||
|  |                 mapView?.mapCenter?.longitude.toString(), | ||||||
|  |                 "0.0f" | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         finish() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Updates image with new coordinates | ||||||
|  |      */ | ||||||
|  |     fun updateCoordinates(latitude: String, longitude: String, accuracy: String) { | ||||||
|  |         media?.let { | ||||||
|  |             try { | ||||||
|  |                 compositeDisposable.add( | ||||||
|  |                     coordinateEditHelper.makeCoordinatesEdit( | ||||||
|  |                         applicationContext, | ||||||
|  |                         it, | ||||||
|  |                         latitude, | ||||||
|  |                         longitude, | ||||||
|  |                         accuracy | ||||||
|  |                     ).subscribeOn(Schedulers.io()) | ||||||
|  |                         .observeOn(AndroidSchedulers.mainThread()) | ||||||
|  |                         .subscribe { _ -> | ||||||
|  |                             Timber.d("Coordinates updated") | ||||||
|  |                         } | ||||||
|  |                 ) | ||||||
|  |             } catch (e: Exception) { | ||||||
|  |                 if (e.localizedMessage == CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE) { | ||||||
|  |                     val username = sessionManager.userName | ||||||
|  |                     CommonsApplication.BaseLogoutListener( | ||||||
|  |                         this, | ||||||
|  |                         getString(R.string.invalid_login_message) | ||||||
|  |                         , username | ||||||
|  |                     ).let { | ||||||
|  |                         CommonsApplication.instance.clearApplicationData(this, it) | ||||||
|  |                     } | ||||||
|  |                 } else { } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds a button to center the map at user's location | ||||||
|  |      */ | ||||||
|  |     private fun addCenterOnGPSButton() { | ||||||
|  |         fabCenterOnLocation = findViewById(R.id.center_on_gps) | ||||||
|  |         fabCenterOnLocation.setOnClickListener { | ||||||
|  |             moveToCurrentLocation = true | ||||||
|  |             requestLocationPermissions() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Shows a selected location marker | ||||||
|  |      */ | ||||||
|  |     private fun showSelectedLocationMarker(point: GeoPoint) { | ||||||
|  |         val icon = ContextCompat.getDrawable(this, R.drawable.map_default_map_marker) | ||||||
|  |         Marker(mapView).apply { | ||||||
|  |             position = point | ||||||
|  |             setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) | ||||||
|  |             setIcon(icon) | ||||||
|  |             infoWindow = null | ||||||
|  |             mapView?.overlays?.add(this) | ||||||
|  |         } | ||||||
|  |         mapView?.invalidate() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Removes selected location marker | ||||||
|  |      */ | ||||||
|  |     private fun removeSelectedLocationMarker() { | ||||||
|  |         val overlays = mapView?.overlays | ||||||
|  |         overlays?.filterIsInstance<Marker>()?.firstOrNull { | ||||||
|  |             it.position.latitude == | ||||||
|  |                     cameraPosition?.latitude && it.position.longitude == cameraPosition?.longitude | ||||||
|  |         }?.let { | ||||||
|  |             overlays.remove(it) | ||||||
|  |             mapView?.invalidate() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Centers map at user's location | ||||||
|  |      */ | ||||||
|  |     private fun requestLocationPermissions() { | ||||||
|  |         locationPermissionsHelper = LocationPermissionsHelper(this, locationManager, this) | ||||||
|  |         locationPermissionsHelper.requestForLocationAccess( | ||||||
|  |             R.string.location_permission_title, | ||||||
|  |             R.string.upload_map_location_access | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { | ||||||
|  |         if (requestCode == Constants.RequestCodes.LOCATION && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { | ||||||
|  |             onLocationPermissionGranted() | ||||||
|  |         } else { | ||||||
|  |             onLocationPermissionDenied(getString(R.string.upload_map_location_access)) | ||||||
|  |         } | ||||||
|  |         super.onRequestPermissionsResult(requestCode, permissions, grantResults) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onResume() { | ||||||
|  |         super.onResume() | ||||||
|  |         mapView?.onResume() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onPause() { | ||||||
|  |         super.onPause() | ||||||
|  |         mapView?.onPause() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onLocationPermissionDenied(toastMessage: String) { | ||||||
|  |         val isDeniedBefore = store.getBoolean("isPermissionDenied", false) | ||||||
|  |         val showRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, permission.ACCESS_FINE_LOCATION) | ||||||
|  | 
 | ||||||
|  |         if (!showRationale) { | ||||||
|  |             if (!locationPermissionsHelper.checkLocationPermission(this)) { | ||||||
|  |                 if (isDeniedBefore) { | ||||||
|  |                     locationPermissionsHelper.showAppSettingsDialog(this, R.string.upload_map_location_access) | ||||||
|  |                 } else { | ||||||
|  |                     Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show() | ||||||
|  |                 } | ||||||
|  |                 store.putBoolean("isPermissionDenied", true) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onLocationPermissionGranted() { | ||||||
|  |         if (moveToCurrentLocation || activity != "MediaActivity") { | ||||||
|  |             if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { | ||||||
|  |                 locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) | ||||||
|  |                 locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) | ||||||
|  |                 addMarkerAtGPSLocation() | ||||||
|  |             } else { | ||||||
|  |                 addMarkerAtGPSLocation() | ||||||
|  |                 locationPermissionsHelper.showLocationOffDialog(this, R.string.ask_to_turn_location_on_text) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds a marker at the user's GPS location | ||||||
|  |      */ | ||||||
|  |     private fun addMarkerAtGPSLocation() { | ||||||
|  |         locationManager.getLastLocation()?.let { | ||||||
|  |             addLocationMarker(GeoPoint(it.latitude, it.longitude)) | ||||||
|  |             markerImage.translationY = 0f | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun addLocationMarker(geoPoint: GeoPoint) { | ||||||
|  |         if (moveToCurrentLocation) { | ||||||
|  |             mapView?.overlays?.clear() | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val diskOverlay = ScaleDiskOverlay( | ||||||
|  |             this, | ||||||
|  |             geoPoint, | ||||||
|  |             2000, | ||||||
|  |             GeoConstants.UnitOfMeasure.foot | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         val circlePaint = Paint().apply { | ||||||
|  |             color = Color.rgb(128, 128, 128) | ||||||
|  |             style = Paint.Style.STROKE | ||||||
|  |             strokeWidth = 2f | ||||||
|  |         } | ||||||
|  |         diskOverlay.setCirclePaint2(circlePaint) | ||||||
|  | 
 | ||||||
|  |         val diskPaint = Paint().apply { | ||||||
|  |             color = Color.argb(40, 128, 128, 128) | ||||||
|  |             style = Paint.Style.FILL_AND_STROKE | ||||||
|  |         } | ||||||
|  |         diskOverlay.setCirclePaint1(diskPaint) | ||||||
|  | 
 | ||||||
|  |         diskOverlay.setDisplaySizeMin(900) | ||||||
|  |         diskOverlay.setDisplaySizeMax(1700) | ||||||
|  | 
 | ||||||
|  |         mapView?.overlays?.add(diskOverlay) | ||||||
|  | 
 | ||||||
|  |         val startMarker = Marker(mapView).apply { | ||||||
|  |             position = geoPoint | ||||||
|  |             setAnchor( | ||||||
|  |                 Marker.ANCHOR_CENTER, | ||||||
|  |                 Marker.ANCHOR_BOTTOM | ||||||
|  |             ) | ||||||
|  |             icon = ContextCompat.getDrawable(this@LocationPickerActivity, R.drawable.current_location_marker) | ||||||
|  |             title = "Your Location" | ||||||
|  |             textLabelFontSize = 24 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         mapView?.overlays?.add(startMarker) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Saves the state of the activity | ||||||
|  |      * @param outState Bundle | ||||||
|  |      */ | ||||||
|  |     override fun onSaveInstanceState(outState: Bundle) { | ||||||
|  |         super.onSaveInstanceState(outState) | ||||||
|  | 
 | ||||||
|  |         cameraPosition?.let { | ||||||
|  |             outState.putParcelable(CAMERA_POS, it) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         activity?.let { | ||||||
|  |             outState.putString(ACTIVITY, it) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         media?.let { | ||||||
|  |             outState.putParcelable("sMedia", it) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,20 +0,0 @@ | ||||||
| package fr.free.nrw.commons.LocationPicker; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Constants need for location picking |  | ||||||
|  */ |  | ||||||
| public final class LocationPickerConstants { |  | ||||||
| 
 |  | ||||||
|     public static final String ACTIVITY_KEY |  | ||||||
|         = "location.picker.activity"; |  | ||||||
| 
 |  | ||||||
|     public static final String MAP_CAMERA_POSITION |  | ||||||
|         = "location.picker.cameraPosition"; |  | ||||||
| 
 |  | ||||||
|     public static final String MEDIA |  | ||||||
|         = "location.picker.media"; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     private LocationPickerConstants() { |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | package fr.free.nrw.commons.LocationPicker | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Constants need for location picking | ||||||
|  |  */ | ||||||
|  | object LocationPickerConstants { | ||||||
|  | 
 | ||||||
|  |     const val ACTIVITY_KEY = "location.picker.activity" | ||||||
|  | 
 | ||||||
|  |     const val MAP_CAMERA_POSITION = "location.picker.cameraPosition" | ||||||
|  | 
 | ||||||
|  |     const val MEDIA = "location.picker.media" | ||||||
|  | } | ||||||
|  | @ -1,63 +0,0 @@ | ||||||
| package fr.free.nrw.commons.LocationPicker; |  | ||||||
| 
 |  | ||||||
| import android.app.Application; |  | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| import androidx.lifecycle.AndroidViewModel; |  | ||||||
| import androidx.lifecycle.MutableLiveData; |  | ||||||
| import fr.free.nrw.commons.CameraPosition; |  | ||||||
| import org.jetbrains.annotations.NotNull; |  | ||||||
| import retrofit2.Call; |  | ||||||
| import retrofit2.Callback; |  | ||||||
| import retrofit2.Response; |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Observes live camera position data |  | ||||||
|  */ |  | ||||||
| public class LocationPickerViewModel extends AndroidViewModel implements Callback<CameraPosition> { |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Wrapping CameraPosition with MutableLiveData |  | ||||||
|      */ |  | ||||||
|     private final MutableLiveData<CameraPosition> result = new MutableLiveData<>(); |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Constructor for this class |  | ||||||
|      * |  | ||||||
|      * @param application Application |  | ||||||
|      */ |  | ||||||
|     public LocationPickerViewModel(@NonNull final Application application) { |  | ||||||
|         super(application); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Responses on camera position changing |  | ||||||
|      * |  | ||||||
|      * @param call     Call<CameraPosition> |  | ||||||
|      * @param response Response<CameraPosition> |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     public void onResponse(final @NotNull Call<CameraPosition> call, |  | ||||||
|         final Response<CameraPosition> response) { |  | ||||||
|         if (response.body() == null) { |  | ||||||
|             result.setValue(null); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         result.setValue(response.body()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onFailure(final @NotNull Call<CameraPosition> call, final @NotNull Throwable t) { |  | ||||||
|         Timber.e(t); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Gets live CameraPosition |  | ||||||
|      * |  | ||||||
|      * @return MutableLiveData<CameraPosition> |  | ||||||
|      */ |  | ||||||
|     public MutableLiveData<CameraPosition> getResult() { |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,44 @@ | ||||||
|  | package fr.free.nrw.commons.LocationPicker | ||||||
|  | 
 | ||||||
|  | import android.app.Application | ||||||
|  | import androidx.lifecycle.AndroidViewModel | ||||||
|  | import androidx.lifecycle.MutableLiveData | ||||||
|  | import fr.free.nrw.commons.CameraPosition | ||||||
|  | import retrofit2.Call | ||||||
|  | import retrofit2.Callback | ||||||
|  | import retrofit2.Response | ||||||
|  | import timber.log.Timber | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Observes live camera position data | ||||||
|  |  */ | ||||||
|  | class LocationPickerViewModel( | ||||||
|  |     application: Application | ||||||
|  | ): AndroidViewModel(application), Callback<CameraPosition> { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Wrapping CameraPosition with MutableLiveData | ||||||
|  |      */ | ||||||
|  |     val result = MutableLiveData<CameraPosition?>() | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Responses on camera position changing | ||||||
|  |      * | ||||||
|  |      * @param call     Call<CameraPosition> | ||||||
|  |      * @param response Response<CameraPosition> | ||||||
|  |      */ | ||||||
|  |     override fun onResponse( | ||||||
|  |         call: Call<CameraPosition>, | ||||||
|  |         response: Response<CameraPosition> | ||||||
|  |     ) { | ||||||
|  |         if(response.body() == null) { | ||||||
|  |             result.value = null | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         result.value = response.body() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onFailure(call: Call<CameraPosition>, t: Throwable) { | ||||||
|  |         Timber.e(t) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -3,7 +3,7 @@ package fr.free.nrw.commons.actions | ||||||
| import fr.free.nrw.commons.CommonsApplication | import fr.free.nrw.commons.CommonsApplication | ||||||
| import fr.free.nrw.commons.auth.csrf.CsrfTokenClient | import fr.free.nrw.commons.auth.csrf.CsrfTokenClient | ||||||
| import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException | import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException | ||||||
| import fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF | import fr.free.nrw.commons.di.NetworkingModule.Companion.NAMED_COMMONS_CSRF | ||||||
| import io.reactivex.Observable | import io.reactivex.Observable | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| import javax.inject.Named | import javax.inject.Named | ||||||
|  |  | ||||||
|  | @ -1,32 +0,0 @@ | ||||||
| package fr.free.nrw.commons.bookmarks; |  | ||||||
| 
 |  | ||||||
| import androidx.fragment.app.Fragment; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Data class for handling a bookmark fragment and it title |  | ||||||
|  */ |  | ||||||
| public class BookmarkPages { |  | ||||||
|     private Fragment page; |  | ||||||
|     private String title; |  | ||||||
| 
 |  | ||||||
|     BookmarkPages(Fragment fragment, String title) { |  | ||||||
|         this.title = title; |  | ||||||
|         this.page = fragment; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the fragment |  | ||||||
|      * @return fragment object |  | ||||||
|      */ |  | ||||||
|     public Fragment getPage() { |  | ||||||
|         return page; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the fragment title |  | ||||||
|      * @return title |  | ||||||
|      */ |  | ||||||
|     public String getTitle() { |  | ||||||
|         return title; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | package fr.free.nrw.commons.bookmarks | ||||||
|  | 
 | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  | 
 | ||||||
|  | data class BookmarkPages ( | ||||||
|  |     val page: Fragment? = null, | ||||||
|  |     val title: String? = null | ||||||
|  | ) | ||||||
|  | @ -3,9 +3,8 @@ package fr.free.nrw.commons.campaigns | ||||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||||
| import fr.free.nrw.commons.BasePresenter | import fr.free.nrw.commons.BasePresenter | ||||||
| import fr.free.nrw.commons.campaigns.models.Campaign | import fr.free.nrw.commons.campaigns.models.Campaign | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.MAIN_THREAD | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD |  | ||||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient | import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient | ||||||
| import fr.free.nrw.commons.utils.CommonsDateUtil.getIso8601DateFormatShort | import fr.free.nrw.commons.utils.CommonsDateUtil.getIso8601DateFormatShort | ||||||
| import io.reactivex.Scheduler | import io.reactivex.Scheduler | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package fr.free.nrw.commons.contributions | ||||||
| import androidx.paging.PagedList.BoundaryCallback | import androidx.paging.PagedList.BoundaryCallback | ||||||
| import fr.free.nrw.commons.auth.SessionManager | import fr.free.nrw.commons.auth.SessionManager | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule | import fr.free.nrw.commons.di.CommonsApplicationModule | ||||||
|  | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD | ||||||
| import fr.free.nrw.commons.media.MediaClient | import fr.free.nrw.commons.media.MediaClient | ||||||
| import io.reactivex.Scheduler | import io.reactivex.Scheduler | ||||||
| import io.reactivex.disposables.CompositeDisposable | import io.reactivex.disposables.CompositeDisposable | ||||||
|  | @ -20,7 +21,7 @@ class ContributionBoundaryCallback | ||||||
|         private val repository: ContributionsRepository, |         private val repository: ContributionsRepository, | ||||||
|         private val sessionManager: SessionManager, |         private val sessionManager: SessionManager, | ||||||
|         private val mediaClient: MediaClient, |         private val mediaClient: MediaClient, | ||||||
|         @param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler, |         @param:Named(IO_THREAD) private val ioThreadScheduler: Scheduler, | ||||||
|     ) : BoundaryCallback<Contribution>() { |     ) : BoundaryCallback<Contribution>() { | ||||||
|         private val compositeDisposable: CompositeDisposable = CompositeDisposable() |         private val compositeDisposable: CompositeDisposable = CompositeDisposable() | ||||||
|         var userName: String? = null |         var userName: String? = null | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| package fr.free.nrw.commons.contributions; | package fr.free.nrw.commons.contributions; | ||||||
| 
 | 
 | ||||||
|  | import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; | ||||||
|  | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.lifecycle.LiveData; | import androidx.lifecycle.LiveData; | ||||||
| import androidx.paging.DataSource; | import androidx.paging.DataSource; | ||||||
|  | @ -34,7 +36,7 @@ public class ContributionsListPresenter implements UserActionListener { | ||||||
|         final ContributionBoundaryCallback contributionBoundaryCallback, |         final ContributionBoundaryCallback contributionBoundaryCallback, | ||||||
|         final ContributionsRemoteDataSource contributionsRemoteDataSource, |         final ContributionsRemoteDataSource contributionsRemoteDataSource, | ||||||
|         final ContributionsRepository repository, |         final ContributionsRepository repository, | ||||||
|         @Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) { |         @Named(IO_THREAD) final Scheduler ioThreadScheduler) { | ||||||
|         this.contributionBoundaryCallback = contributionBoundaryCallback; |         this.contributionBoundaryCallback = contributionBoundaryCallback; | ||||||
|         this.repository = repository; |         this.repository = repository; | ||||||
|         this.ioThreadScheduler = ioThreadScheduler; |         this.ioThreadScheduler = ioThreadScheduler; | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.contributions; | package fr.free.nrw.commons.contributions; | ||||||
| 
 | 
 | ||||||
|  | import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; | ||||||
| import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; | import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; | ||||||
| 
 | 
 | ||||||
| import androidx.work.ExistingWorkPolicy; | import androidx.work.ExistingWorkPolicy; | ||||||
|  | @ -31,7 +32,7 @@ public class ContributionsPresenter implements UserActionListener { | ||||||
|     @Inject |     @Inject | ||||||
|     ContributionsPresenter(ContributionsRepository repository, |     ContributionsPresenter(ContributionsRepository repository, | ||||||
|         UploadRepository uploadRepository, |         UploadRepository uploadRepository, | ||||||
|         @Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) { |         @Named(IO_THREAD) Scheduler ioThreadScheduler) { | ||||||
|         this.contributionsRepository = repository; |         this.contributionsRepository = repository; | ||||||
|         this.uploadRepository = uploadRepository; |         this.uploadRepository = uploadRepository; | ||||||
|         this.ioThreadScheduler = ioThreadScheduler; |         this.ioThreadScheduler = ioThreadScheduler; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| package fr.free.nrw.commons.contributions | package fr.free.nrw.commons.contributions | ||||||
| 
 | 
 | ||||||
| import androidx.paging.ItemKeyedDataSource | import androidx.paging.ItemKeyedDataSource | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD | ||||||
| import fr.free.nrw.commons.media.MediaClient | import fr.free.nrw.commons.media.MediaClient | ||||||
| import io.reactivex.Scheduler | import io.reactivex.Scheduler | ||||||
| import io.reactivex.disposables.CompositeDisposable | import io.reactivex.disposables.CompositeDisposable | ||||||
|  | @ -16,7 +16,7 @@ class ContributionsRemoteDataSource | ||||||
|     @Inject |     @Inject | ||||||
|     constructor( |     constructor( | ||||||
|         private val mediaClient: MediaClient, |         private val mediaClient: MediaClient, | ||||||
|         @param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler, |         @param:Named(IO_THREAD) private val ioThreadScheduler: Scheduler, | ||||||
|     ) : ItemKeyedDataSource<Int, Contribution>() { |     ) : ItemKeyedDataSource<Int, Contribution>() { | ||||||
|         private val compositeDisposable: CompositeDisposable = CompositeDisposable() |         private val compositeDisposable: CompositeDisposable = CompositeDisposable() | ||||||
|         var userName: String? = null |         var userName: String? = null | ||||||
|  |  | ||||||
|  | @ -22,7 +22,10 @@ import java.util.Map; | ||||||
| public class Converters { | public class Converters { | ||||||
| 
 | 
 | ||||||
|     public static Gson getGson() { |     public static Gson getGson() { | ||||||
|         return ApplicationlessInjection.getInstance(CommonsApplication.getInstance()).getCommonsApplicationComponent().gson(); |         return ApplicationlessInjection | ||||||
|  |             .getInstance(CommonsApplication.getInstance()) | ||||||
|  |             .getCommonsApplicationComponent() | ||||||
|  |             .gson(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -1,90 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import dagger.Module; |  | ||||||
| import dagger.android.ContributesAndroidInjector; |  | ||||||
| import fr.free.nrw.commons.AboutActivity; |  | ||||||
| import fr.free.nrw.commons.LocationPicker.LocationPickerActivity; |  | ||||||
| import fr.free.nrw.commons.WelcomeActivity; |  | ||||||
| import fr.free.nrw.commons.auth.LoginActivity; |  | ||||||
| import fr.free.nrw.commons.auth.SignupActivity; |  | ||||||
| import fr.free.nrw.commons.category.CategoryDetailsActivity; |  | ||||||
| import fr.free.nrw.commons.contributions.MainActivity; |  | ||||||
| import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity; |  | ||||||
| import fr.free.nrw.commons.description.DescriptionEditActivity; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity; |  | ||||||
| import fr.free.nrw.commons.explore.SearchActivity; |  | ||||||
| import fr.free.nrw.commons.media.ZoomableActivity; |  | ||||||
| import fr.free.nrw.commons.nearby.WikidataFeedback; |  | ||||||
| import fr.free.nrw.commons.notification.NotificationActivity; |  | ||||||
| import fr.free.nrw.commons.profile.ProfileActivity; |  | ||||||
| import fr.free.nrw.commons.review.ReviewActivity; |  | ||||||
| import fr.free.nrw.commons.settings.SettingsActivity; |  | ||||||
| import fr.free.nrw.commons.upload.UploadActivity; |  | ||||||
| import fr.free.nrw.commons.upload.UploadProgressActivity; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * This Class handles the dependency injection (using dagger) |  | ||||||
|  * so, if a developer needs to add a new activity to the commons app |  | ||||||
|  * then that must be mentioned here to inject the dependencies |  | ||||||
|  */ |  | ||||||
| @Module |  | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) |  | ||||||
| public abstract class ActivityBuilderModule { |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract LoginActivity bindLoginActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract WelcomeActivity bindWelcomeActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract MainActivity bindContributionsActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract CustomSelectorActivity bindCustomSelectorActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SettingsActivity bindSettingsActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract AboutActivity bindAboutActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract LocationPickerActivity bindLocationPickerActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SignupActivity bindSignupActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract NotificationActivity bindNotificationActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract UploadActivity bindUploadActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SearchActivity bindSearchActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract CategoryDetailsActivity bindCategoryDetailsActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract WikidataItemDetailsActivity bindDepictionDetailsActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ProfileActivity bindAchievementsActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ReviewActivity bindReviewActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract DescriptionEditActivity bindDescriptionEditActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ZoomableActivity bindZoomableActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract UploadProgressActivity bindUploadProgressActivity(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract WikidataFeedback bindWikiFeedback(); |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,89 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import dagger.Module | ||||||
|  | import dagger.android.ContributesAndroidInjector | ||||||
|  | import fr.free.nrw.commons.AboutActivity | ||||||
|  | import fr.free.nrw.commons.LocationPicker.LocationPickerActivity | ||||||
|  | import fr.free.nrw.commons.WelcomeActivity | ||||||
|  | import fr.free.nrw.commons.auth.LoginActivity | ||||||
|  | import fr.free.nrw.commons.auth.SignupActivity | ||||||
|  | import fr.free.nrw.commons.category.CategoryDetailsActivity | ||||||
|  | import fr.free.nrw.commons.contributions.MainActivity | ||||||
|  | import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity | ||||||
|  | import fr.free.nrw.commons.description.DescriptionEditActivity | ||||||
|  | import fr.free.nrw.commons.explore.SearchActivity | ||||||
|  | import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity | ||||||
|  | import fr.free.nrw.commons.media.ZoomableActivity | ||||||
|  | import fr.free.nrw.commons.nearby.WikidataFeedback | ||||||
|  | import fr.free.nrw.commons.notification.NotificationActivity | ||||||
|  | import fr.free.nrw.commons.profile.ProfileActivity | ||||||
|  | import fr.free.nrw.commons.review.ReviewActivity | ||||||
|  | import fr.free.nrw.commons.settings.SettingsActivity | ||||||
|  | import fr.free.nrw.commons.upload.UploadActivity | ||||||
|  | import fr.free.nrw.commons.upload.UploadProgressActivity | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This Class handles the dependency injection (using dagger) | ||||||
|  |  * so, if a developer needs to add a new activity to the commons app | ||||||
|  |  * then that must be mentioned here to inject the dependencies | ||||||
|  |  */ | ||||||
|  | @Module | ||||||
|  | @Suppress("unused") | ||||||
|  | abstract class ActivityBuilderModule { | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindLoginActivity(): LoginActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindWelcomeActivity(): WelcomeActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindContributionsActivity(): MainActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindCustomSelectorActivity(): CustomSelectorActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSettingsActivity(): SettingsActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindAboutActivity(): AboutActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindLocationPickerActivity(): LocationPickerActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSignupActivity(): SignupActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindNotificationActivity(): NotificationActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindUploadActivity(): UploadActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSearchActivity(): SearchActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindCategoryDetailsActivity(): CategoryDetailsActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindDepictionDetailsActivity(): WikidataItemDetailsActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindAchievementsActivity(): ProfileActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindReviewActivity(): ReviewActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindDescriptionEditActivity(): DescriptionEditActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindZoomableActivity(): ZoomableActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindUploadProgressActivity(): UploadProgressActivity | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindWikiFeedback(): WikidataFeedback | ||||||
|  | } | ||||||
|  | @ -1,105 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.app.Service; |  | ||||||
| import android.content.BroadcastReceiver; |  | ||||||
| import android.content.ContentProvider; |  | ||||||
| import android.content.Context; |  | ||||||
| 
 |  | ||||||
| import androidx.fragment.app.Fragment; |  | ||||||
| 
 |  | ||||||
| import dagger.android.HasAndroidInjector; |  | ||||||
| import javax.inject.Inject; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| import dagger.android.DispatchingAndroidInjector; |  | ||||||
| import dagger.android.HasActivityInjector; |  | ||||||
| import dagger.android.HasBroadcastReceiverInjector; |  | ||||||
| import dagger.android.HasContentProviderInjector; |  | ||||||
| import dagger.android.HasFragmentInjector; |  | ||||||
| import dagger.android.HasServiceInjector; |  | ||||||
| import dagger.android.support.HasSupportFragmentInjector; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Provides injectors for all sorts of components |  | ||||||
|  * Ex: Activities, Fragments, Services, ContentProviders |  | ||||||
|  */ |  | ||||||
| public class ApplicationlessInjection |  | ||||||
|         implements |  | ||||||
|         HasAndroidInjector, |  | ||||||
|         HasActivityInjector, |  | ||||||
|         HasFragmentInjector, |  | ||||||
|         HasSupportFragmentInjector, |  | ||||||
|         HasServiceInjector, |  | ||||||
|         HasBroadcastReceiverInjector, |  | ||||||
|         HasContentProviderInjector { |  | ||||||
| 
 |  | ||||||
|     private static ApplicationlessInjection instance = null; |  | ||||||
| 
 |  | ||||||
|     @Inject DispatchingAndroidInjector<Object> androidInjector; |  | ||||||
|     @Inject DispatchingAndroidInjector<Activity> activityInjector; |  | ||||||
|     @Inject DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector; |  | ||||||
|     @Inject DispatchingAndroidInjector<android.app.Fragment> fragmentInjector; |  | ||||||
|     @Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector; |  | ||||||
|     @Inject DispatchingAndroidInjector<Service> serviceInjector; |  | ||||||
|     @Inject DispatchingAndroidInjector<ContentProvider> contentProviderInjector; |  | ||||||
| 
 |  | ||||||
|     private CommonsApplicationComponent commonsApplicationComponent; |  | ||||||
| 
 |  | ||||||
|     public ApplicationlessInjection(Context applicationContext) { |  | ||||||
|         commonsApplicationComponent = DaggerCommonsApplicationComponent.builder() |  | ||||||
|                 .appModule(new CommonsApplicationModule(applicationContext)).build(); |  | ||||||
|         commonsApplicationComponent.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public AndroidInjector<Object> androidInjector() { |  | ||||||
|         return androidInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public DispatchingAndroidInjector<Activity> activityInjector() { |  | ||||||
|         return activityInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public DispatchingAndroidInjector<android.app.Fragment> fragmentInjector() { |  | ||||||
|         return fragmentInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public DispatchingAndroidInjector<Fragment> supportFragmentInjector() { |  | ||||||
|         return supportFragmentInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector() { |  | ||||||
|         return broadcastReceiverInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public DispatchingAndroidInjector<Service> serviceInjector() { |  | ||||||
|         return serviceInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public AndroidInjector<ContentProvider> contentProviderInjector() { |  | ||||||
|         return contentProviderInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public CommonsApplicationComponent getCommonsApplicationComponent() { |  | ||||||
|         return commonsApplicationComponent; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static ApplicationlessInjection getInstance(Context applicationContext) { |  | ||||||
|         if (instance == null) { |  | ||||||
|             synchronized (ApplicationlessInjection.class) { |  | ||||||
|                 if (instance == null) { |  | ||||||
|                     instance = new ApplicationlessInjection(applicationContext); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return instance; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,98 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.app.Activity | ||||||
|  | import android.app.Fragment | ||||||
|  | import android.app.Service | ||||||
|  | import android.content.BroadcastReceiver | ||||||
|  | import android.content.ContentProvider | ||||||
|  | import android.content.Context | ||||||
|  | import dagger.android.AndroidInjector | ||||||
|  | import dagger.android.DispatchingAndroidInjector | ||||||
|  | import dagger.android.HasActivityInjector | ||||||
|  | import dagger.android.HasAndroidInjector | ||||||
|  | import dagger.android.HasBroadcastReceiverInjector | ||||||
|  | import dagger.android.HasContentProviderInjector | ||||||
|  | import dagger.android.HasFragmentInjector | ||||||
|  | import dagger.android.HasServiceInjector | ||||||
|  | import dagger.android.support.HasSupportFragmentInjector | ||||||
|  | import javax.inject.Inject | ||||||
|  | import androidx.fragment.app.Fragment as AndroidXFragmen | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Provides injectors for all sorts of components | ||||||
|  |  * Ex: Activities, Fragments, Services, ContentProviders | ||||||
|  |  */ | ||||||
|  | class ApplicationlessInjection(applicationContext: Context) : HasAndroidInjector, | ||||||
|  |     HasActivityInjector, HasFragmentInjector, HasSupportFragmentInjector, HasServiceInjector, | ||||||
|  |     HasBroadcastReceiverInjector, HasContentProviderInjector { | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var androidInjector: DispatchingAndroidInjector<Any>? = null | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var activityInjector: DispatchingAndroidInjector<Activity>? = null | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var broadcastReceiverInjector: DispatchingAndroidInjector<BroadcastReceiver>? = null | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var fragmentInjector: DispatchingAndroidInjector<Fragment>? = null | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var supportFragmentInjector: DispatchingAndroidInjector<AndroidXFragmen>? = null | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var serviceInjector: DispatchingAndroidInjector<Service>? = null | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var contentProviderInjector: DispatchingAndroidInjector<ContentProvider>? = null | ||||||
|  | 
 | ||||||
|  |     val instance: ApplicationlessInjection get() = _instance!! | ||||||
|  | 
 | ||||||
|  |     val commonsApplicationComponent: CommonsApplicationComponent = | ||||||
|  |         DaggerCommonsApplicationComponent | ||||||
|  |             .builder() | ||||||
|  |             .appModule(CommonsApplicationModule(applicationContext)) | ||||||
|  |             .build() | ||||||
|  | 
 | ||||||
|  |     init { | ||||||
|  |         commonsApplicationComponent.inject(this) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun androidInjector(): AndroidInjector<Any>? = | ||||||
|  |         androidInjector | ||||||
|  | 
 | ||||||
|  |     override fun activityInjector(): DispatchingAndroidInjector<Activity>? = | ||||||
|  |         activityInjector | ||||||
|  | 
 | ||||||
|  |     override fun fragmentInjector(): DispatchingAndroidInjector<Fragment>? = | ||||||
|  |         fragmentInjector | ||||||
|  | 
 | ||||||
|  |     override fun supportFragmentInjector(): DispatchingAndroidInjector<AndroidXFragmen>? = | ||||||
|  |         supportFragmentInjector | ||||||
|  | 
 | ||||||
|  |     override fun broadcastReceiverInjector(): DispatchingAndroidInjector<BroadcastReceiver>? = | ||||||
|  |         broadcastReceiverInjector | ||||||
|  | 
 | ||||||
|  |     override fun serviceInjector(): DispatchingAndroidInjector<Service>? = | ||||||
|  |         serviceInjector | ||||||
|  | 
 | ||||||
|  |     override fun contentProviderInjector(): AndroidInjector<ContentProvider>? = | ||||||
|  |         contentProviderInjector | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         private var _instance: ApplicationlessInjection? = null | ||||||
|  | 
 | ||||||
|  |         @JvmStatic | ||||||
|  |         fun getInstance(applicationContext: Context): ApplicationlessInjection { | ||||||
|  |             if (_instance == null) { | ||||||
|  |                 synchronized(ApplicationlessInjection::class.java) { | ||||||
|  |                     if (_instance == null) { | ||||||
|  |                         _instance = ApplicationlessInjection(applicationContext) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return _instance!! | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,85 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import com.google.gson.Gson; |  | ||||||
| 
 |  | ||||||
| import fr.free.nrw.commons.explore.categories.CategoriesModule; |  | ||||||
| import fr.free.nrw.commons.navtab.MoreBottomSheetFragment; |  | ||||||
| import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment; |  | ||||||
| import fr.free.nrw.commons.nearby.NearbyController; |  | ||||||
| import fr.free.nrw.commons.upload.worker.UploadWorker; |  | ||||||
| import javax.inject.Singleton; |  | ||||||
| 
 |  | ||||||
| import dagger.Component; |  | ||||||
| import dagger.android.AndroidInjectionModule; |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| import dagger.android.support.AndroidSupportInjectionModule; |  | ||||||
| import fr.free.nrw.commons.CommonsApplication; |  | ||||||
| import fr.free.nrw.commons.auth.LoginActivity; |  | ||||||
| import fr.free.nrw.commons.contributions.ContributionsModule; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.DepictionModule; |  | ||||||
| import fr.free.nrw.commons.explore.SearchModule; |  | ||||||
| import fr.free.nrw.commons.review.ReviewController; |  | ||||||
| import fr.free.nrw.commons.settings.SettingsFragment; |  | ||||||
| import fr.free.nrw.commons.upload.FileProcessor; |  | ||||||
| import fr.free.nrw.commons.upload.UploadModule; |  | ||||||
| import fr.free.nrw.commons.widget.PicOfDayAppWidget; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Facilitates Injection from CommonsApplicationModule to all the  |  | ||||||
|  * classes seeking a dependency to be injected |  | ||||||
|  */ |  | ||||||
| @Singleton |  | ||||||
| @Component(modules = { |  | ||||||
|         CommonsApplicationModule.class, |  | ||||||
|         NetworkingModule.class, |  | ||||||
|         AndroidInjectionModule.class, |  | ||||||
|         AndroidSupportInjectionModule.class, |  | ||||||
|         ActivityBuilderModule.class, |  | ||||||
|         FragmentBuilderModule.class, |  | ||||||
|         ServiceBuilderModule.class, |  | ||||||
|         ContentProviderBuilderModule.class, |  | ||||||
|         UploadModule.class, |  | ||||||
|         ContributionsModule.class, |  | ||||||
|         SearchModule.class, |  | ||||||
|         DepictionModule.class, |  | ||||||
|         CategoriesModule.class |  | ||||||
| }) |  | ||||||
| public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> { |  | ||||||
|     void inject(CommonsApplication application); |  | ||||||
| 
 |  | ||||||
|     void inject(UploadWorker worker); |  | ||||||
| 
 |  | ||||||
|     void inject(LoginActivity activity); |  | ||||||
| 
 |  | ||||||
|     void inject(SettingsFragment fragment); |  | ||||||
| 
 |  | ||||||
|     void inject(MoreBottomSheetFragment fragment); |  | ||||||
| 
 |  | ||||||
|     void inject(MoreBottomSheetLoggedOutFragment fragment); |  | ||||||
| 
 |  | ||||||
|     void inject(ReviewController reviewController); |  | ||||||
| 
 |  | ||||||
|     //void inject(NavTabLayout view); |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     void inject(ApplicationlessInjection instance); |  | ||||||
| 
 |  | ||||||
|     void inject(FileProcessor fileProcessor); |  | ||||||
| 
 |  | ||||||
|     void inject(PicOfDayAppWidget picOfDayAppWidget); |  | ||||||
| 
 |  | ||||||
|     @Singleton |  | ||||||
|     void inject(NearbyController nearbyController); |  | ||||||
| 
 |  | ||||||
|     Gson gson(); |  | ||||||
| 
 |  | ||||||
|     @Component.Builder |  | ||||||
|     @SuppressWarnings({"WeakerAccess", "unused"}) |  | ||||||
|     interface Builder { |  | ||||||
| 
 |  | ||||||
|         Builder appModule(CommonsApplicationModule applicationModule); |  | ||||||
| 
 |  | ||||||
|         CommonsApplicationComponent build(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,80 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import com.google.gson.Gson | ||||||
|  | import dagger.Component | ||||||
|  | import dagger.android.AndroidInjectionModule | ||||||
|  | import dagger.android.AndroidInjector | ||||||
|  | import dagger.android.support.AndroidSupportInjectionModule | ||||||
|  | import fr.free.nrw.commons.CommonsApplication | ||||||
|  | import fr.free.nrw.commons.auth.LoginActivity | ||||||
|  | import fr.free.nrw.commons.contributions.ContributionsModule | ||||||
|  | import fr.free.nrw.commons.explore.SearchModule | ||||||
|  | import fr.free.nrw.commons.explore.categories.CategoriesModule | ||||||
|  | import fr.free.nrw.commons.explore.depictions.DepictionModule | ||||||
|  | import fr.free.nrw.commons.navtab.MoreBottomSheetFragment | ||||||
|  | import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment | ||||||
|  | import fr.free.nrw.commons.nearby.NearbyController | ||||||
|  | import fr.free.nrw.commons.review.ReviewController | ||||||
|  | import fr.free.nrw.commons.settings.SettingsFragment | ||||||
|  | import fr.free.nrw.commons.upload.FileProcessor | ||||||
|  | import fr.free.nrw.commons.upload.UploadModule | ||||||
|  | import fr.free.nrw.commons.upload.worker.UploadWorker | ||||||
|  | import fr.free.nrw.commons.widget.PicOfDayAppWidget | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Facilitates Injection from CommonsApplicationModule to all the | ||||||
|  |  * classes seeking a dependency to be injected | ||||||
|  |  */ | ||||||
|  | @Singleton | ||||||
|  | @Component( | ||||||
|  |     modules = [ | ||||||
|  |         CommonsApplicationModule::class, | ||||||
|  |         NetworkingModule::class, | ||||||
|  |         AndroidInjectionModule::class, | ||||||
|  |         AndroidSupportInjectionModule::class, | ||||||
|  |         ActivityBuilderModule::class, | ||||||
|  |         FragmentBuilderModule::class, | ||||||
|  |         ServiceBuilderModule::class, | ||||||
|  |         ContentProviderBuilderModule::class, | ||||||
|  |         UploadModule::class, | ||||||
|  |         ContributionsModule::class, | ||||||
|  |         SearchModule::class, | ||||||
|  |         DepictionModule::class, | ||||||
|  |         CategoriesModule::class | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | interface CommonsApplicationComponent : AndroidInjector<ApplicationlessInjection> { | ||||||
|  |     fun inject(application: CommonsApplication) | ||||||
|  | 
 | ||||||
|  |     fun inject(worker: UploadWorker) | ||||||
|  | 
 | ||||||
|  |     fun inject(activity: LoginActivity) | ||||||
|  | 
 | ||||||
|  |     fun inject(fragment: SettingsFragment) | ||||||
|  | 
 | ||||||
|  |     fun inject(fragment: MoreBottomSheetFragment) | ||||||
|  | 
 | ||||||
|  |     fun inject(fragment: MoreBottomSheetLoggedOutFragment) | ||||||
|  | 
 | ||||||
|  |     fun inject(reviewController: ReviewController) | ||||||
|  | 
 | ||||||
|  |     override fun inject(instance: ApplicationlessInjection) | ||||||
|  | 
 | ||||||
|  |     fun inject(fileProcessor: FileProcessor) | ||||||
|  | 
 | ||||||
|  |     fun inject(picOfDayAppWidget: PicOfDayAppWidget) | ||||||
|  | 
 | ||||||
|  |     @Singleton | ||||||
|  |     fun inject(nearbyController: NearbyController) | ||||||
|  | 
 | ||||||
|  |     fun gson(): Gson | ||||||
|  | 
 | ||||||
|  |     @Component.Builder | ||||||
|  |     @Suppress("unused") | ||||||
|  |     interface Builder { | ||||||
|  |         fun appModule(applicationModule: CommonsApplicationModule): Builder | ||||||
|  | 
 | ||||||
|  |         fun build(): CommonsApplicationComponent | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,314 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.ContentProviderClient; |  | ||||||
| import android.content.ContentResolver; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.view.inputmethod.InputMethodManager; |  | ||||||
| import androidx.collection.LruCache; |  | ||||||
| import androidx.room.Room; |  | ||||||
| import androidx.room.migration.Migration; |  | ||||||
| import androidx.sqlite.db.SupportSQLiteDatabase; |  | ||||||
| import com.google.gson.Gson; |  | ||||||
| import dagger.Module; |  | ||||||
| import dagger.Provides; |  | ||||||
| import fr.free.nrw.commons.BuildConfig; |  | ||||||
| import fr.free.nrw.commons.R; |  | ||||||
| import fr.free.nrw.commons.auth.SessionManager; |  | ||||||
| import fr.free.nrw.commons.contributions.ContributionDao; |  | ||||||
| import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao; |  | ||||||
| import fr.free.nrw.commons.customselector.database.UploadedStatusDao; |  | ||||||
| import fr.free.nrw.commons.customselector.ui.selector.ImageFileLoader; |  | ||||||
| import fr.free.nrw.commons.data.DBOpenHelper; |  | ||||||
| import fr.free.nrw.commons.db.AppDatabase; |  | ||||||
| import fr.free.nrw.commons.kvstore.JsonKvStore; |  | ||||||
| import fr.free.nrw.commons.location.LocationServiceManager; |  | ||||||
| import fr.free.nrw.commons.nearby.PlaceDao; |  | ||||||
| import fr.free.nrw.commons.review.ReviewDao; |  | ||||||
| import fr.free.nrw.commons.settings.Prefs; |  | ||||||
| import fr.free.nrw.commons.upload.UploadController; |  | ||||||
| import fr.free.nrw.commons.upload.depicts.DepictsDao; |  | ||||||
| import fr.free.nrw.commons.utils.ConfigUtils; |  | ||||||
| import fr.free.nrw.commons.wikidata.WikidataEditListener; |  | ||||||
| import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl; |  | ||||||
| import io.reactivex.Scheduler; |  | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; |  | ||||||
| import io.reactivex.schedulers.Schedulers; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Objects; |  | ||||||
| import javax.inject.Named; |  | ||||||
| import javax.inject.Singleton; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * The Dependency Provider class for Commons Android. |  | ||||||
|  * |  | ||||||
|  * Provides all sorts of ContentProviderClients used by the app |  | ||||||
|  * along with the Liscences, AccountUtility, UploadController, Logged User, |  | ||||||
|  * Location manager etc |  | ||||||
|  */ |  | ||||||
| @Module |  | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) |  | ||||||
| public class CommonsApplicationModule { |  | ||||||
|     private Context applicationContext; |  | ||||||
|     public static final String IO_THREAD="io_thread"; |  | ||||||
|     public static final String MAIN_THREAD="main_thread"; |  | ||||||
|     private AppDatabase appDatabase; |  | ||||||
| 
 |  | ||||||
|     static final Migration MIGRATION_1_2 = new Migration(1, 2) { |  | ||||||
|         @Override |  | ||||||
|         public void migrate(SupportSQLiteDatabase database) { |  | ||||||
|             database.execSQL("ALTER TABLE contribution " |  | ||||||
|                 + " ADD COLUMN hasInvalidLocation INTEGER NOT NULL DEFAULT 0"); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     public CommonsApplicationModule(Context applicationContext) { |  | ||||||
|         this.applicationContext = applicationContext; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides ImageFileLoader used to fetch device images. |  | ||||||
|      * @param context |  | ||||||
|      * @return |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     public ImageFileLoader providesImageFileLoader(Context context) { |  | ||||||
|         return new ImageFileLoader(context); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     public Context providesApplicationContext() { |  | ||||||
|         return this.applicationContext; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     public InputMethodManager provideInputMethodManager() { |  | ||||||
|         return (InputMethodManager) applicationContext.getSystemService(Activity.INPUT_METHOD_SERVICE); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("licenses") |  | ||||||
|     public List<String> provideLicenses(Context context) { |  | ||||||
|         List<String> licenseItems = new ArrayList<>(); |  | ||||||
|         licenseItems.add(context.getString(R.string.license_name_cc0)); |  | ||||||
|         licenseItems.add(context.getString(R.string.license_name_cc_by)); |  | ||||||
|         licenseItems.add(context.getString(R.string.license_name_cc_by_sa)); |  | ||||||
|         licenseItems.add(context.getString(R.string.license_name_cc_by_four)); |  | ||||||
|         licenseItems.add(context.getString(R.string.license_name_cc_by_sa_four)); |  | ||||||
|         return licenseItems; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("licenses_by_name") |  | ||||||
|     public Map<String, String> provideLicensesByName(Context context) { |  | ||||||
|         Map<String, String> byName = new HashMap<>(); |  | ||||||
|         byName.put(context.getString(R.string.license_name_cc0), Prefs.Licenses.CC0); |  | ||||||
|         byName.put(context.getString(R.string.license_name_cc_by), Prefs.Licenses.CC_BY_3); |  | ||||||
|         byName.put(context.getString(R.string.license_name_cc_by_sa), Prefs.Licenses.CC_BY_SA_3); |  | ||||||
|         byName.put(context.getString(R.string.license_name_cc_by_four), Prefs.Licenses.CC_BY_4); |  | ||||||
|         byName.put(context.getString(R.string.license_name_cc_by_sa_four), Prefs.Licenses.CC_BY_SA_4); |  | ||||||
|         return byName; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides an instance of CategoryContentProviderClient i.e. the categories |  | ||||||
|      * that are there in local storage |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Named("category") |  | ||||||
|     public ContentProviderClient provideCategoryContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.CATEGORY_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * This method is used to provide instance of RecentSearchContentProviderClient |  | ||||||
|      * which provides content of Recent Searches from database |  | ||||||
|      * @param context |  | ||||||
|      * @return returns RecentSearchContentProviderClient |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Named("recentsearch") |  | ||||||
|     public ContentProviderClient provideRecentSearchContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.RECENT_SEARCH_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("contribution") |  | ||||||
|     public ContentProviderClient provideContributionContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.CONTRIBUTION_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("modification") |  | ||||||
|     public ContentProviderClient provideModificationContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.MODIFICATION_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("bookmarks") |  | ||||||
|     public ContentProviderClient provideBookmarkContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.BOOKMARK_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("bookmarksLocation") |  | ||||||
|     public ContentProviderClient provideBookmarkLocationContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("bookmarksItem") |  | ||||||
|     public ContentProviderClient provideBookmarkItemContentProviderClient(Context context) { |  | ||||||
|         return context.getContentResolver().acquireContentProviderClient(BuildConfig.BOOKMARK_ITEMS_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * This method is used to provide instance of RecentLanguagesContentProvider |  | ||||||
|      * which provides content of recent used languages from database |  | ||||||
|      * @param context Context |  | ||||||
|      * @return returns RecentLanguagesContentProvider |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Named("recent_languages") |  | ||||||
|     public ContentProviderClient provideRecentLanguagesContentProviderClient(final Context context) { |  | ||||||
|         return context.getContentResolver() |  | ||||||
|             .acquireContentProviderClient(BuildConfig.RECENT_LANGUAGE_AUTHORITY); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides a Json store instance(JsonKvStore) which keeps |  | ||||||
|      * the provided Gson in it's instance |  | ||||||
|      * @param gson stored inside the store instance |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Named("default_preferences") |  | ||||||
|     public JsonKvStore providesDefaultKvStore(Context context, Gson gson) { |  | ||||||
|         String storeName = context.getPackageName() + "_preferences"; |  | ||||||
|         return new JsonKvStore(context, storeName, gson); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     public UploadController providesUploadController(SessionManager sessionManager, |  | ||||||
|                                                      @Named("default_preferences") JsonKvStore kvStore, |  | ||||||
|                                                      Context context, ContributionDao contributionDao) { |  | ||||||
|         return new UploadController(sessionManager, context, kvStore); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public LocationServiceManager provideLocationServiceManager(Context context) { |  | ||||||
|         return new LocationServiceManager(context); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public DBOpenHelper provideDBOpenHelper(Context context) { |  | ||||||
|         return new DBOpenHelper(context); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     @Named("thumbnail-cache") |  | ||||||
|     public LruCache<String, String> provideLruCache() { |  | ||||||
|         return new LruCache<>(1024); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public WikidataEditListener provideWikidataEditListener() { |  | ||||||
|         return new WikidataEditListenerImpl(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides app flavour. Can be used to alter flows in the app |  | ||||||
|      * @return |  | ||||||
|      */ |  | ||||||
|     @Named("isBeta") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public boolean provideIsBetaVariant() { |  | ||||||
|         return ConfigUtils.isBetaFlavour(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provide JavaRx IO scheduler which manages IO operations |  | ||||||
|      * across various Threads |  | ||||||
|      */ |  | ||||||
|     @Named(IO_THREAD) |  | ||||||
|     @Provides |  | ||||||
|     public Scheduler providesIoThread(){ |  | ||||||
|         return Schedulers.io(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named(MAIN_THREAD) |  | ||||||
|     @Provides |  | ||||||
|     public Scheduler providesMainThread() { |  | ||||||
|         return AndroidSchedulers.mainThread(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named("username") |  | ||||||
|     @Provides |  | ||||||
|     public String provideLoggedInUsername(SessionManager sessionManager) { |  | ||||||
|         return Objects.toString(sessionManager.getUserName(), ""); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public AppDatabase provideAppDataBase() { |  | ||||||
|         appDatabase = Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db") |  | ||||||
|             .addMigrations(MIGRATION_1_2) |  | ||||||
|             .fallbackToDestructiveMigration() |  | ||||||
|             .build(); |  | ||||||
|         return appDatabase; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     public ContributionDao providesContributionsDao(AppDatabase appDatabase) { |  | ||||||
|         return appDatabase.contributionDao(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     public PlaceDao providesPlaceDao(AppDatabase appDatabase) { |  | ||||||
|         return appDatabase.PlaceDao(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the reference of DepictsDao class. |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     public DepictsDao providesDepictDao(AppDatabase appDatabase) { |  | ||||||
|         return appDatabase.DepictsDao(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the reference of UploadedStatus class. |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     public UploadedStatusDao providesUploadedStatusDao(AppDatabase appDatabase) { |  | ||||||
|         return appDatabase.UploadedStatusDao(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the reference of NotForUploadStatus class. |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     public NotForUploadStatusDao providesNotForUploadStatusDao(AppDatabase appDatabase) { |  | ||||||
|         return appDatabase.NotForUploadStatusDao(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get the reference of ReviewDao class |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     public ReviewDao providesReviewDao(AppDatabase appDatabase){ |  | ||||||
|         return appDatabase.ReviewDao(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     public ContentResolver providesContentResolver(Context context){ |  | ||||||
|         return context.getContentResolver(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,239 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.app.Activity | ||||||
|  | import android.content.ContentProviderClient | ||||||
|  | import android.content.ContentResolver | ||||||
|  | import android.content.Context | ||||||
|  | import android.view.inputmethod.InputMethodManager | ||||||
|  | import androidx.collection.LruCache | ||||||
|  | import androidx.room.Room.databaseBuilder | ||||||
|  | import androidx.room.migration.Migration | ||||||
|  | import androidx.sqlite.db.SupportSQLiteDatabase | ||||||
|  | import com.google.gson.Gson | ||||||
|  | import dagger.Module | ||||||
|  | import dagger.Provides | ||||||
|  | import fr.free.nrw.commons.BuildConfig | ||||||
|  | import fr.free.nrw.commons.R | ||||||
|  | import fr.free.nrw.commons.auth.SessionManager | ||||||
|  | import fr.free.nrw.commons.contributions.ContributionDao | ||||||
|  | import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao | ||||||
|  | import fr.free.nrw.commons.customselector.database.UploadedStatusDao | ||||||
|  | import fr.free.nrw.commons.customselector.ui.selector.ImageFileLoader | ||||||
|  | import fr.free.nrw.commons.data.DBOpenHelper | ||||||
|  | import fr.free.nrw.commons.db.AppDatabase | ||||||
|  | import fr.free.nrw.commons.kvstore.JsonKvStore | ||||||
|  | import fr.free.nrw.commons.location.LocationServiceManager | ||||||
|  | import fr.free.nrw.commons.nearby.PlaceDao | ||||||
|  | import fr.free.nrw.commons.review.ReviewDao | ||||||
|  | import fr.free.nrw.commons.settings.Prefs | ||||||
|  | import fr.free.nrw.commons.upload.UploadController | ||||||
|  | import fr.free.nrw.commons.upload.depicts.DepictsDao | ||||||
|  | import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour | ||||||
|  | import fr.free.nrw.commons.wikidata.WikidataEditListener | ||||||
|  | import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl | ||||||
|  | import io.reactivex.Scheduler | ||||||
|  | import io.reactivex.android.schedulers.AndroidSchedulers | ||||||
|  | import io.reactivex.schedulers.Schedulers | ||||||
|  | import java.util.Objects | ||||||
|  | import javax.inject.Named | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * The Dependency Provider class for Commons Android. | ||||||
|  |  * Provides all sorts of ContentProviderClients used by the app | ||||||
|  |  * along with the Liscences, AccountUtility, UploadController, Logged User, | ||||||
|  |  * Location manager etc | ||||||
|  |  */ | ||||||
|  | @Module | ||||||
|  | @Suppress("unused") | ||||||
|  | open class CommonsApplicationModule(private val applicationContext: Context) { | ||||||
|  |     @Provides | ||||||
|  |     fun providesImageFileLoader(context: Context): ImageFileLoader = | ||||||
|  |         ImageFileLoader(context) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesApplicationContext(): Context = | ||||||
|  |         applicationContext | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun provideInputMethodManager(): InputMethodManager = | ||||||
|  |         applicationContext.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("licenses") | ||||||
|  |     fun provideLicenses(context: Context): List<String> = listOf( | ||||||
|  |         context.getString(R.string.license_name_cc0), | ||||||
|  |         context.getString(R.string.license_name_cc_by), | ||||||
|  |         context.getString(R.string.license_name_cc_by_sa), | ||||||
|  |         context.getString(R.string.license_name_cc_by_four), | ||||||
|  |         context.getString(R.string.license_name_cc_by_sa_four) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("licenses_by_name") | ||||||
|  |     fun provideLicensesByName(context: Context): Map<String, String> = mapOf( | ||||||
|  |         context.getString(R.string.license_name_cc0) to Prefs.Licenses.CC0, | ||||||
|  |         context.getString(R.string.license_name_cc_by) to Prefs.Licenses.CC_BY_3, | ||||||
|  |         context.getString(R.string.license_name_cc_by_sa) to Prefs.Licenses.CC_BY_SA_3, | ||||||
|  |         context.getString(R.string.license_name_cc_by_four) to Prefs.Licenses.CC_BY_4, | ||||||
|  |         context.getString(R.string.license_name_cc_by_sa_four) to Prefs.Licenses.CC_BY_SA_4 | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Provides an instance of CategoryContentProviderClient i.e. the categories | ||||||
|  |      * that are there in local storage | ||||||
|  |      */ | ||||||
|  |     @Provides | ||||||
|  |     @Named("category") | ||||||
|  |     open fun provideCategoryContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.CATEGORY_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("recentsearch") | ||||||
|  |     fun provideRecentSearchContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.RECENT_SEARCH_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("contribution") | ||||||
|  |     open fun provideContributionContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.CONTRIBUTION_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("modification") | ||||||
|  |     open fun provideModificationContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.MODIFICATION_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("bookmarks") | ||||||
|  |     fun provideBookmarkContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("bookmarksLocation") | ||||||
|  |     fun provideBookmarkLocationContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("bookmarksItem") | ||||||
|  |     fun provideBookmarkItemContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_ITEMS_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * This method is used to provide instance of RecentLanguagesContentProvider | ||||||
|  |      * which provides content of recent used languages from database | ||||||
|  |      * @param context Context | ||||||
|  |      * @return returns RecentLanguagesContentProvider | ||||||
|  |      */ | ||||||
|  |     @Provides | ||||||
|  |     @Named("recent_languages") | ||||||
|  |     fun provideRecentLanguagesContentProviderClient(context: Context): ContentProviderClient? = | ||||||
|  |         context.contentResolver.acquireContentProviderClient(BuildConfig.RECENT_LANGUAGE_AUTHORITY) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Provides a Json store instance(JsonKvStore) which keeps | ||||||
|  |      * the provided Gson in it's instance | ||||||
|  |      * @param gson stored inside the store instance | ||||||
|  |      */ | ||||||
|  |     @Provides | ||||||
|  |     @Named("default_preferences") | ||||||
|  |     open fun providesDefaultKvStore(context: Context, gson: Gson): JsonKvStore = | ||||||
|  |         JsonKvStore(context, "${context.packageName}_preferences", gson) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesUploadController( | ||||||
|  |         sessionManager: SessionManager, | ||||||
|  |         @Named("default_preferences") kvStore: JsonKvStore, | ||||||
|  |         context: Context | ||||||
|  |     ): UploadController = UploadController(sessionManager, context, kvStore) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     open fun provideLocationServiceManager(context: Context): LocationServiceManager = | ||||||
|  |         LocationServiceManager(context) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     open fun provideDBOpenHelper(context: Context): DBOpenHelper = | ||||||
|  |         DBOpenHelper(context) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     @Named("thumbnail-cache") | ||||||
|  |     open fun provideLruCache(): LruCache<String, String> = | ||||||
|  |         LruCache(1024) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikidataEditListener(): WikidataEditListener = | ||||||
|  |         WikidataEditListenerImpl() | ||||||
|  | 
 | ||||||
|  |     @Named("isBeta") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideIsBetaVariant(): Boolean = | ||||||
|  |         isBetaFlavour | ||||||
|  | 
 | ||||||
|  |     @Named(IO_THREAD) | ||||||
|  |     @Provides | ||||||
|  |     fun providesIoThread(): Scheduler = | ||||||
|  |         Schedulers.io() | ||||||
|  | 
 | ||||||
|  |     @Named(MAIN_THREAD) | ||||||
|  |     @Provides | ||||||
|  |     fun providesMainThread(): Scheduler = | ||||||
|  |         AndroidSchedulers.mainThread() | ||||||
|  | 
 | ||||||
|  |     @Named("username") | ||||||
|  |     @Provides | ||||||
|  |     fun provideLoggedInUsername(sessionManager: SessionManager): String = | ||||||
|  |         Objects.toString(sessionManager.userName, "") | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideAppDataBase(): AppDatabase = databaseBuilder( | ||||||
|  |         applicationContext, | ||||||
|  |         AppDatabase::class.java, | ||||||
|  |         "commons_room.db" | ||||||
|  |     ).addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesContributionsDao(appDatabase: AppDatabase): ContributionDao = | ||||||
|  |         appDatabase.contributionDao() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesPlaceDao(appDatabase: AppDatabase): PlaceDao = | ||||||
|  |         appDatabase.PlaceDao() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesDepictDao(appDatabase: AppDatabase): DepictsDao = | ||||||
|  |         appDatabase.DepictsDao() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesUploadedStatusDao(appDatabase: AppDatabase): UploadedStatusDao = | ||||||
|  |         appDatabase.UploadedStatusDao() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesNotForUploadStatusDao(appDatabase: AppDatabase): NotForUploadStatusDao = | ||||||
|  |         appDatabase.NotForUploadStatusDao() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesReviewDao(appDatabase: AppDatabase): ReviewDao = | ||||||
|  |         appDatabase.ReviewDao() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     fun providesContentResolver(context: Context): ContentResolver = | ||||||
|  |         context.contentResolver | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         const val IO_THREAD: String = "io_thread" | ||||||
|  |         const val MAIN_THREAD: String = "main_thread" | ||||||
|  | 
 | ||||||
|  |         val MIGRATION_1_2: Migration = object : Migration(1, 2) { | ||||||
|  |             override fun migrate(db: SupportSQLiteDatabase) { | ||||||
|  |                 db.execSQL( | ||||||
|  |                     "ALTER TABLE contribution " + " ADD COLUMN hasInvalidLocation INTEGER NOT NULL DEFAULT 0" | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.os.Bundle; |  | ||||||
| 
 |  | ||||||
| import androidx.annotation.Nullable; |  | ||||||
| import androidx.appcompat.app.AppCompatActivity; |  | ||||||
| import androidx.fragment.app.Fragment; |  | ||||||
| 
 |  | ||||||
| import javax.inject.Inject; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| import dagger.android.DispatchingAndroidInjector; |  | ||||||
| import dagger.android.support.HasSupportFragmentInjector; |  | ||||||
| 
 |  | ||||||
| public abstract class CommonsDaggerAppCompatActivity extends AppCompatActivity implements HasSupportFragmentInjector { |  | ||||||
| 
 |  | ||||||
|     @Inject |  | ||||||
|     DispatchingAndroidInjector<Fragment> supportFragmentInjector; |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     protected void onCreate(@Nullable Bundle savedInstanceState) { |  | ||||||
|         inject(); |  | ||||||
|         super.onCreate(savedInstanceState); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public AndroidInjector<Fragment> supportFragmentInjector() { |  | ||||||
|         return supportFragmentInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * when this Activity is created it injects an instance of this class inside |  | ||||||
|      * activityInjector method of ApplicationlessInjection |  | ||||||
|      */ |  | ||||||
|     private void inject() { |  | ||||||
|         ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext()); |  | ||||||
| 
 |  | ||||||
|         AndroidInjector<Activity> activityInjector = injection.activityInjector(); |  | ||||||
| 
 |  | ||||||
|         if (activityInjector == null) { |  | ||||||
|             throw new NullPointerException("ApplicationlessInjection.activityInjector() returned null"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         activityInjector.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.os.Bundle | ||||||
|  | import androidx.appcompat.app.AppCompatActivity | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  | import dagger.android.AndroidInjector | ||||||
|  | import dagger.android.DispatchingAndroidInjector | ||||||
|  | import dagger.android.support.HasSupportFragmentInjector | ||||||
|  | import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | abstract class CommonsDaggerAppCompatActivity : AppCompatActivity(), HasSupportFragmentInjector { | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var supportFragmentInjector: DispatchingAndroidInjector<Fragment>? = null | ||||||
|  | 
 | ||||||
|  |     override fun onCreate(savedInstanceState: Bundle?) { | ||||||
|  |         inject() | ||||||
|  |         super.onCreate(savedInstanceState) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun supportFragmentInjector(): AndroidInjector<Fragment> { | ||||||
|  |         return supportFragmentInjector!! | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * when this Activity is created it injects an instance of this class inside | ||||||
|  |      * activityInjector method of ApplicationlessInjection | ||||||
|  |      */ | ||||||
|  |     private fun inject() { | ||||||
|  |         val injection = getInstance(applicationContext) | ||||||
|  | 
 | ||||||
|  |         val activityInjector = injection.activityInjector() | ||||||
|  |             ?: throw NullPointerException("ApplicationlessInjection.activityInjector() returned null") | ||||||
|  | 
 | ||||||
|  |         activityInjector.inject(this) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.content.BroadcastReceiver; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.content.Intent; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Receives broadcast then injects it's instance to the broadcastReceiverInjector method of  |  | ||||||
|  * ApplicationlessInjection class |  | ||||||
|  */ |  | ||||||
| public abstract class CommonsDaggerBroadcastReceiver extends BroadcastReceiver { |  | ||||||
| 
 |  | ||||||
|     public CommonsDaggerBroadcastReceiver() { |  | ||||||
|         super(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onReceive(Context context, Intent intent) { |  | ||||||
|         inject(context); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void inject(Context context) { |  | ||||||
|         ApplicationlessInjection injection = ApplicationlessInjection.getInstance(context.getApplicationContext()); |  | ||||||
| 
 |  | ||||||
|         AndroidInjector<BroadcastReceiver> serviceInjector = injection.broadcastReceiverInjector(); |  | ||||||
| 
 |  | ||||||
|         if (serviceInjector == null) { |  | ||||||
|             throw new NullPointerException("ApplicationlessInjection.broadcastReceiverInjector() returned null"); |  | ||||||
|         } |  | ||||||
|         serviceInjector.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,25 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.content.BroadcastReceiver | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.Intent | ||||||
|  | import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Receives broadcast then injects it's instance to the broadcastReceiverInjector method of | ||||||
|  |  * ApplicationlessInjection class | ||||||
|  |  */ | ||||||
|  | abstract class CommonsDaggerBroadcastReceiver : BroadcastReceiver() { | ||||||
|  |     override fun onReceive(context: Context, intent: Intent) { | ||||||
|  |         inject(context) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun inject(context: Context) { | ||||||
|  |         val injection = getInstance(context.applicationContext) | ||||||
|  | 
 | ||||||
|  |         val serviceInjector = injection.broadcastReceiverInjector() | ||||||
|  |                 ?: throw NullPointerException("ApplicationlessInjection.broadcastReceiverInjector() returned null") | ||||||
|  | 
 | ||||||
|  |         serviceInjector.inject(this) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,32 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.content.ContentProvider; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| public abstract class CommonsDaggerContentProvider extends ContentProvider { |  | ||||||
| 
 |  | ||||||
|     public CommonsDaggerContentProvider() { |  | ||||||
|         super(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public boolean onCreate() { |  | ||||||
|         inject(); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void inject() { |  | ||||||
|         ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getContext()); |  | ||||||
| 
 |  | ||||||
|         AndroidInjector<ContentProvider> serviceInjector = injection.contentProviderInjector(); |  | ||||||
| 
 |  | ||||||
|         if (serviceInjector == null) { |  | ||||||
|             throw new NullPointerException("ApplicationlessInjection.contentProviderInjector() returned null"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         serviceInjector.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.content.ContentProvider | ||||||
|  | import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance | ||||||
|  | 
 | ||||||
|  | abstract class CommonsDaggerContentProvider : ContentProvider() { | ||||||
|  |     override fun onCreate(): Boolean { | ||||||
|  |         inject() | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun inject() { | ||||||
|  |         val injection = getInstance(context!!) | ||||||
|  | 
 | ||||||
|  |         val serviceInjector = injection.contentProviderInjector() | ||||||
|  |             ?: throw NullPointerException("ApplicationlessInjection.contentProviderInjector() returned null") | ||||||
|  | 
 | ||||||
|  |         serviceInjector.inject(this) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,32 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.app.IntentService; |  | ||||||
| import android.app.Service; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| 
 |  | ||||||
| public abstract class CommonsDaggerIntentService extends IntentService { |  | ||||||
| 
 |  | ||||||
|     public CommonsDaggerIntentService(String name) { |  | ||||||
|         super(name); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onCreate() { |  | ||||||
|         inject(); |  | ||||||
|         super.onCreate(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void inject() { |  | ||||||
|         ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext()); |  | ||||||
| 
 |  | ||||||
|         AndroidInjector<Service> serviceInjector = injection.serviceInjector(); |  | ||||||
| 
 |  | ||||||
|         if (serviceInjector == null) { |  | ||||||
|             throw new NullPointerException("ApplicationlessInjection.serviceInjector() returned null"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         serviceInjector.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.app.IntentService | ||||||
|  | import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance | ||||||
|  | 
 | ||||||
|  | abstract class CommonsDaggerIntentService(name: String?) : IntentService(name) { | ||||||
|  |     override fun onCreate() { | ||||||
|  |         inject() | ||||||
|  |         super.onCreate() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun inject() { | ||||||
|  |         val injection = getInstance(applicationContext) | ||||||
|  | 
 | ||||||
|  |         val serviceInjector = injection.serviceInjector() | ||||||
|  |             ?: throw NullPointerException("ApplicationlessInjection.serviceInjector() returned null") | ||||||
|  | 
 | ||||||
|  |         serviceInjector.inject(this) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.app.Service; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| 
 |  | ||||||
| public abstract class CommonsDaggerService extends Service { |  | ||||||
| 
 |  | ||||||
|     public CommonsDaggerService() { |  | ||||||
|         super(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onCreate() { |  | ||||||
|         inject(); |  | ||||||
|         super.onCreate(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void inject() { |  | ||||||
|         ApplicationlessInjection injection = ApplicationlessInjection.getInstance(getApplicationContext()); |  | ||||||
| 
 |  | ||||||
|         AndroidInjector<Service> serviceInjector = injection.serviceInjector(); |  | ||||||
| 
 |  | ||||||
|         if (serviceInjector == null) { |  | ||||||
|             throw new NullPointerException("ApplicationlessInjection.serviceInjector() returned null"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         serviceInjector.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,20 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.app.Service | ||||||
|  | import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance | ||||||
|  | 
 | ||||||
|  | abstract class CommonsDaggerService : Service() { | ||||||
|  |     override fun onCreate() { | ||||||
|  |         inject() | ||||||
|  |         super.onCreate() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun inject() { | ||||||
|  |         val injection = getInstance(applicationContext) | ||||||
|  | 
 | ||||||
|  |         val serviceInjector = injection.serviceInjector() | ||||||
|  |             ?: throw NullPointerException("ApplicationlessInjection.serviceInjector() returned null") | ||||||
|  | 
 | ||||||
|  |         serviceInjector.inject(this) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,75 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.Context; |  | ||||||
| 
 |  | ||||||
| import androidx.fragment.app.Fragment; |  | ||||||
| 
 |  | ||||||
| import javax.inject.Inject; |  | ||||||
| 
 |  | ||||||
| import dagger.android.AndroidInjector; |  | ||||||
| import dagger.android.DispatchingAndroidInjector; |  | ||||||
| import dagger.android.support.HasSupportFragmentInjector; |  | ||||||
| import io.reactivex.disposables.CompositeDisposable; |  | ||||||
| 
 |  | ||||||
| public abstract class CommonsDaggerSupportFragment extends Fragment implements HasSupportFragmentInjector { |  | ||||||
| 
 |  | ||||||
|     @Inject |  | ||||||
|     DispatchingAndroidInjector<Fragment> childFragmentInjector; |  | ||||||
| 
 |  | ||||||
|     protected CompositeDisposable compositeDisposable = new CompositeDisposable(); |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onAttach(Context context) { |  | ||||||
|         inject(); |  | ||||||
|         super.onAttach(context); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onDestroy() { |  | ||||||
|         super.onDestroy(); |  | ||||||
|         compositeDisposable.clear(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public AndroidInjector<Fragment> supportFragmentInjector() { |  | ||||||
|         return childFragmentInjector; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     public void inject() { |  | ||||||
|         HasSupportFragmentInjector hasSupportFragmentInjector = findHasFragmentInjector(); |  | ||||||
| 
 |  | ||||||
|         AndroidInjector<Fragment> fragmentInjector = hasSupportFragmentInjector.supportFragmentInjector(); |  | ||||||
| 
 |  | ||||||
|         if (fragmentInjector == null) { |  | ||||||
|             throw new NullPointerException(String.format("%s.supportFragmentInjector() returned null", hasSupportFragmentInjector.getClass().getCanonicalName())); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         fragmentInjector.inject(this); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private HasSupportFragmentInjector findHasFragmentInjector() { |  | ||||||
|         Fragment parentFragment = this; |  | ||||||
| 
 |  | ||||||
|         while ((parentFragment = parentFragment.getParentFragment()) != null) { |  | ||||||
|             if (parentFragment instanceof HasSupportFragmentInjector) { |  | ||||||
|                 return (HasSupportFragmentInjector) parentFragment; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         Activity activity = getActivity(); |  | ||||||
| 
 |  | ||||||
|         if (activity instanceof HasSupportFragmentInjector) { |  | ||||||
|             return (HasSupportFragmentInjector) activity; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         ApplicationlessInjection injection = ApplicationlessInjection.getInstance(activity.getApplicationContext()); |  | ||||||
|         if (injection != null) { |  | ||||||
|             return injection; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         throw new IllegalArgumentException(String.format("No injector was found for %s", getClass().getCanonicalName())); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,66 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.app.Activity | ||||||
|  | import android.content.Context | ||||||
|  | import androidx.fragment.app.Fragment | ||||||
|  | import dagger.android.AndroidInjector | ||||||
|  | import dagger.android.DispatchingAndroidInjector | ||||||
|  | import dagger.android.support.HasSupportFragmentInjector | ||||||
|  | import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance | ||||||
|  | import io.reactivex.disposables.CompositeDisposable | ||||||
|  | import javax.inject.Inject | ||||||
|  | 
 | ||||||
|  | abstract class CommonsDaggerSupportFragment : Fragment(), HasSupportFragmentInjector { | ||||||
|  | 
 | ||||||
|  |     @Inject @JvmField | ||||||
|  |     var childFragmentInjector: DispatchingAndroidInjector<Fragment>? = null | ||||||
|  | 
 | ||||||
|  |     @JvmField | ||||||
|  |     protected var compositeDisposable: CompositeDisposable = CompositeDisposable() | ||||||
|  | 
 | ||||||
|  |     override fun onAttach(context: Context) { | ||||||
|  |         inject() | ||||||
|  |         super.onAttach(context) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onDestroy() { | ||||||
|  |         super.onDestroy() | ||||||
|  |         compositeDisposable.clear() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun supportFragmentInjector(): AndroidInjector<Fragment> = | ||||||
|  |         childFragmentInjector!! | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     fun inject() { | ||||||
|  |         val hasSupportFragmentInjector = findHasFragmentInjector() | ||||||
|  | 
 | ||||||
|  |         val fragmentInjector = hasSupportFragmentInjector.supportFragmentInjector() | ||||||
|  |             ?: throw NullPointerException( | ||||||
|  |                 String.format( | ||||||
|  |                     "%s.supportFragmentInjector() returned null", | ||||||
|  |                     hasSupportFragmentInjector.javaClass.canonicalName | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  | 
 | ||||||
|  |         fragmentInjector.inject(this) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun findHasFragmentInjector(): HasSupportFragmentInjector { | ||||||
|  |         var parentFragment: Fragment? = this | ||||||
|  | 
 | ||||||
|  |         while ((parentFragment!!.parentFragment.also { parentFragment = it }) != null) { | ||||||
|  |             if (parentFragment is HasSupportFragmentInjector) { | ||||||
|  |                 return parentFragment as HasSupportFragmentInjector | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val activity: Activity = requireActivity() | ||||||
|  | 
 | ||||||
|  |         if (activity is HasSupportFragmentInjector) { | ||||||
|  |             return activity | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return getInstance(activity.applicationContext) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,38 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import dagger.Module; |  | ||||||
| import dagger.android.ContributesAndroidInjector; |  | ||||||
| import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider; |  | ||||||
| import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider; |  | ||||||
| import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider; |  | ||||||
| import fr.free.nrw.commons.category.CategoryContentProvider; |  | ||||||
| import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider; |  | ||||||
| import fr.free.nrw.commons.recentlanguages.RecentLanguagesContentProvider; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * This Class Represents the Module for dependency injection (using dagger) |  | ||||||
|  * so, if a developer needs to add a new ContentProvider to the commons app |  | ||||||
|  * then that must be mentioned here to inject the dependencies |  | ||||||
|  */ |  | ||||||
| @Module |  | ||||||
| @SuppressWarnings({ "WeakerAccess", "unused" }) |  | ||||||
| public abstract class ContentProviderBuilderModule { |  | ||||||
| 
 |  | ||||||
| 	@ContributesAndroidInjector |  | ||||||
| 	abstract CategoryContentProvider bindCategoryContentProvider(); |  | ||||||
| 
 |  | ||||||
| 	@ContributesAndroidInjector |  | ||||||
| 	abstract RecentSearchesContentProvider bindRecentSearchesContentProvider(); |  | ||||||
| 
 |  | ||||||
| 	@ContributesAndroidInjector |  | ||||||
| 	abstract BookmarkPicturesContentProvider bindBookmarkContentProvider(); |  | ||||||
| 
 |  | ||||||
| 	@ContributesAndroidInjector |  | ||||||
| 	abstract BookmarkLocationsContentProvider bindBookmarkLocationContentProvider(); |  | ||||||
| 
 |  | ||||||
| 	@ContributesAndroidInjector |  | ||||||
| 	abstract BookmarkItemsContentProvider bindBookmarkItemContentProvider(); |  | ||||||
| 
 |  | ||||||
| 	@ContributesAndroidInjector |  | ||||||
| 	abstract RecentLanguagesContentProvider bindRecentLanguagesContentProvider(); |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import dagger.Module | ||||||
|  | import dagger.android.ContributesAndroidInjector | ||||||
|  | import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider | ||||||
|  | import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider | ||||||
|  | import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider | ||||||
|  | import fr.free.nrw.commons.category.CategoryContentProvider | ||||||
|  | import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider | ||||||
|  | import fr.free.nrw.commons.recentlanguages.RecentLanguagesContentProvider | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This Class Represents the Module for dependency injection (using dagger) | ||||||
|  |  * so, if a developer needs to add a new ContentProvider to the commons app | ||||||
|  |  * then that must be mentioned here to inject the dependencies | ||||||
|  |  */ | ||||||
|  | @Module | ||||||
|  | @Suppress("unused") | ||||||
|  | abstract class ContentProviderBuilderModule { | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindCategoryContentProvider(): CategoryContentProvider | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindRecentSearchesContentProvider(): RecentSearchesContentProvider | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBookmarkContentProvider(): BookmarkPicturesContentProvider | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBookmarkLocationContentProvider(): BookmarkLocationsContentProvider | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBookmarkItemContentProvider(): BookmarkItemsContentProvider | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindRecentLanguagesContentProvider(): RecentLanguagesContentProvider | ||||||
|  | } | ||||||
|  | @ -1,166 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import dagger.Module; |  | ||||||
| import dagger.android.ContributesAndroidInjector; |  | ||||||
| import fr.free.nrw.commons.bookmarks.BookmarkFragment; |  | ||||||
| import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment; |  | ||||||
| import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment; |  | ||||||
| import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment; |  | ||||||
| import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment; |  | ||||||
| import fr.free.nrw.commons.contributions.ContributionsFragment; |  | ||||||
| import fr.free.nrw.commons.contributions.ContributionsListFragment; |  | ||||||
| import fr.free.nrw.commons.customselector.ui.selector.FolderFragment; |  | ||||||
| import fr.free.nrw.commons.customselector.ui.selector.ImageFragment; |  | ||||||
| import fr.free.nrw.commons.explore.ExploreFragment; |  | ||||||
| import fr.free.nrw.commons.explore.ExploreListRootFragment; |  | ||||||
| import fr.free.nrw.commons.explore.ExploreMapRootFragment; |  | ||||||
| import fr.free.nrw.commons.explore.map.ExploreMapFragment; |  | ||||||
| import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment; |  | ||||||
| import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment; |  | ||||||
| import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment; |  | ||||||
| import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment; |  | ||||||
| import fr.free.nrw.commons.explore.media.SearchMediaFragment; |  | ||||||
| import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment; |  | ||||||
| import fr.free.nrw.commons.media.MediaDetailFragment; |  | ||||||
| import fr.free.nrw.commons.media.MediaDetailPagerFragment; |  | ||||||
| import fr.free.nrw.commons.navtab.MoreBottomSheetFragment; |  | ||||||
| import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment; |  | ||||||
| import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment; |  | ||||||
| import fr.free.nrw.commons.profile.achievements.AchievementsFragment; |  | ||||||
| import fr.free.nrw.commons.profile.leaderboard.LeaderboardFragment; |  | ||||||
| import fr.free.nrw.commons.review.ReviewImageFragment; |  | ||||||
| import fr.free.nrw.commons.settings.SettingsFragment; |  | ||||||
| import fr.free.nrw.commons.upload.FailedUploadsFragment; |  | ||||||
| import fr.free.nrw.commons.upload.PendingUploadsFragment; |  | ||||||
| import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment; |  | ||||||
| import fr.free.nrw.commons.upload.depicts.DepictsFragment; |  | ||||||
| import fr.free.nrw.commons.upload.license.MediaLicenseFragment; |  | ||||||
| import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * This Class Represents the Module for dependency injection (using dagger) |  | ||||||
|  * so, if a developer needs to add a new Fragment to the commons app |  | ||||||
|  * then that must be mentioned here to inject the dependencies |  | ||||||
|  */ |  | ||||||
| @Module |  | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) |  | ||||||
| public abstract class FragmentBuilderModule { |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ContributionsListFragment bindContributionsListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract MediaDetailFragment bindMediaDetailFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract FolderFragment bindFolderFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ImageFragment bindImageFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract MediaDetailPagerFragment bindMediaDetailPagerFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SettingsFragment bindSettingsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract DepictedImagesFragment bindDepictedImagesFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SearchMediaFragment bindBrowseImagesListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SearchCategoryFragment bindSearchCategoryListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SearchDepictionsFragment bindSearchDepictionListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract RecentSearchesFragment bindRecentSearchesFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ContributionsFragment bindContributionsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector(modules = NearbyParentFragmentModule.class) |  | ||||||
|     abstract NearbyParentFragment bindNearbyParentFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract BookmarkPicturesFragment bindBookmarkPictureListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector(modules = BookmarkLocationsFragmentModule.class) |  | ||||||
|     abstract BookmarkLocationsFragment bindBookmarkLocationListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector(modules = BookmarkItemsFragmentModule.class) |  | ||||||
|     abstract BookmarkItemsFragment bindBookmarkItemListFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ReviewImageFragment bindReviewOutOfContextFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract UploadMediaDetailFragment bindUploadMediaDetailFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract UploadCategoriesFragment bindUploadCategoriesFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract DepictsFragment bindDepictsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract MediaLicenseFragment bindMediaLicenseFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ParentDepictionsFragment bindParentDepictionsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ChildDepictionsFragment bindChildDepictionsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract CategoriesMediaFragment bindCategoriesMediaFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract SubCategoriesFragment bindSubCategoriesFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ParentCategoriesFragment bindParentCategoriesFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ExploreFragment bindExploreFragmentFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ExploreListRootFragment bindExploreFeaturedRootFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector(modules = ExploreMapFragmentModule.class) |  | ||||||
|     abstract ExploreMapFragment bindExploreNearbyUploadsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract ExploreMapRootFragment bindExploreNearbyUploadsRootFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract BookmarkListRootFragment bindBookmarkListRootFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract BookmarkFragment bindBookmarkFragmentFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract MoreBottomSheetFragment bindMoreBottomSheetFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract MoreBottomSheetLoggedOutFragment bindMoreBottomSheetLoggedOutFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract AchievementsFragment bindAchievementsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract LeaderboardFragment bindLeaderboardFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract PendingUploadsFragment bindPendingUploadsFragment(); |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract FailedUploadsFragment bindFailedUploadsFragment(); |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,165 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import dagger.Module | ||||||
|  | import dagger.android.ContributesAndroidInjector | ||||||
|  | import fr.free.nrw.commons.bookmarks.BookmarkFragment | ||||||
|  | import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment | ||||||
|  | import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment | ||||||
|  | import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment | ||||||
|  | import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment | ||||||
|  | import fr.free.nrw.commons.contributions.ContributionsFragment | ||||||
|  | import fr.free.nrw.commons.contributions.ContributionsListFragment | ||||||
|  | import fr.free.nrw.commons.customselector.ui.selector.FolderFragment | ||||||
|  | import fr.free.nrw.commons.customselector.ui.selector.ImageFragment | ||||||
|  | import fr.free.nrw.commons.explore.ExploreFragment | ||||||
|  | import fr.free.nrw.commons.explore.ExploreListRootFragment | ||||||
|  | import fr.free.nrw.commons.explore.ExploreMapRootFragment | ||||||
|  | import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment | ||||||
|  | import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment | ||||||
|  | import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment | ||||||
|  | import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment | ||||||
|  | import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment | ||||||
|  | import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment | ||||||
|  | import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment | ||||||
|  | import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment | ||||||
|  | import fr.free.nrw.commons.explore.map.ExploreMapFragment | ||||||
|  | import fr.free.nrw.commons.explore.media.SearchMediaFragment | ||||||
|  | import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment | ||||||
|  | import fr.free.nrw.commons.media.MediaDetailFragment | ||||||
|  | import fr.free.nrw.commons.media.MediaDetailPagerFragment | ||||||
|  | import fr.free.nrw.commons.navtab.MoreBottomSheetFragment | ||||||
|  | import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment | ||||||
|  | import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment | ||||||
|  | import fr.free.nrw.commons.profile.achievements.AchievementsFragment | ||||||
|  | import fr.free.nrw.commons.profile.leaderboard.LeaderboardFragment | ||||||
|  | import fr.free.nrw.commons.review.ReviewImageFragment | ||||||
|  | import fr.free.nrw.commons.settings.SettingsFragment | ||||||
|  | import fr.free.nrw.commons.upload.FailedUploadsFragment | ||||||
|  | import fr.free.nrw.commons.upload.PendingUploadsFragment | ||||||
|  | import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment | ||||||
|  | import fr.free.nrw.commons.upload.depicts.DepictsFragment | ||||||
|  | import fr.free.nrw.commons.upload.license.MediaLicenseFragment | ||||||
|  | import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This Class Represents the Module for dependency injection (using dagger) | ||||||
|  |  * so, if a developer needs to add a new Fragment to the commons app | ||||||
|  |  * then that must be mentioned here to inject the dependencies | ||||||
|  |  */ | ||||||
|  | @Module | ||||||
|  | @Suppress("unused") | ||||||
|  | abstract class FragmentBuilderModule { | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindContributionsListFragment(): ContributionsListFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindMediaDetailFragment(): MediaDetailFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindFolderFragment(): FolderFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindImageFragment(): ImageFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindMediaDetailPagerFragment(): MediaDetailPagerFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSettingsFragment(): SettingsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindDepictedImagesFragment(): DepictedImagesFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBrowseImagesListFragment(): SearchMediaFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSearchCategoryListFragment(): SearchCategoryFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSearchDepictionListFragment(): SearchDepictionsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindRecentSearchesFragment(): RecentSearchesFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindContributionsFragment(): ContributionsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector(modules = [NearbyParentFragmentModule::class]) | ||||||
|  |     abstract fun bindNearbyParentFragment(): NearbyParentFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBookmarkPictureListFragment(): BookmarkPicturesFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector(modules = [BookmarkLocationsFragmentModule::class]) | ||||||
|  |     abstract fun bindBookmarkLocationListFragment(): BookmarkLocationsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector(modules = [BookmarkItemsFragmentModule::class]) | ||||||
|  |     abstract fun bindBookmarkItemListFragment(): BookmarkItemsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindReviewOutOfContextFragment(): ReviewImageFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindUploadMediaDetailFragment(): UploadMediaDetailFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindUploadCategoriesFragment(): UploadCategoriesFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindDepictsFragment(): DepictsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindMediaLicenseFragment(): MediaLicenseFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindParentDepictionsFragment(): ParentDepictionsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindChildDepictionsFragment(): ChildDepictionsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindCategoriesMediaFragment(): CategoriesMediaFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindSubCategoriesFragment(): SubCategoriesFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindParentCategoriesFragment(): ParentCategoriesFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindExploreFragmentFragment(): ExploreFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindExploreFeaturedRootFragment(): ExploreListRootFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector(modules = [ExploreMapFragmentModule::class]) | ||||||
|  |     abstract fun bindExploreNearbyUploadsFragment(): ExploreMapFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindExploreNearbyUploadsRootFragment(): ExploreMapRootFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBookmarkListRootFragment(): BookmarkListRootFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindBookmarkFragmentFragment(): BookmarkFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindMoreBottomSheetFragment(): MoreBottomSheetFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindMoreBottomSheetLoggedOutFragment(): MoreBottomSheetLoggedOutFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindAchievementsFragment(): AchievementsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindLeaderboardFragment(): LeaderboardFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindPendingUploadsFragment(): PendingUploadsFragment | ||||||
|  | 
 | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindFailedUploadsFragment(): FailedUploadsFragment | ||||||
|  | } | ||||||
|  | @ -1,350 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import android.content.Context; |  | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| import com.google.gson.Gson; |  | ||||||
| import dagger.Module; |  | ||||||
| import dagger.Provides; |  | ||||||
| import fr.free.nrw.commons.BetaConstants; |  | ||||||
| import fr.free.nrw.commons.BuildConfig; |  | ||||||
| import fr.free.nrw.commons.OkHttpConnectionFactory; |  | ||||||
| import fr.free.nrw.commons.actions.PageEditClient; |  | ||||||
| import fr.free.nrw.commons.actions.PageEditInterface; |  | ||||||
| import fr.free.nrw.commons.actions.ThanksInterface; |  | ||||||
| import fr.free.nrw.commons.auth.SessionManager; |  | ||||||
| import fr.free.nrw.commons.auth.csrf.CsrfTokenClient; |  | ||||||
| import fr.free.nrw.commons.auth.csrf.CsrfTokenInterface; |  | ||||||
| import fr.free.nrw.commons.auth.csrf.LogoutClient; |  | ||||||
| import fr.free.nrw.commons.auth.login.LoginClient; |  | ||||||
| import fr.free.nrw.commons.auth.login.LoginInterface; |  | ||||||
| import fr.free.nrw.commons.category.CategoryInterface; |  | ||||||
| import fr.free.nrw.commons.explore.depictions.DepictsClient; |  | ||||||
| import fr.free.nrw.commons.kvstore.JsonKvStore; |  | ||||||
| import fr.free.nrw.commons.media.MediaDetailInterface; |  | ||||||
| import fr.free.nrw.commons.media.MediaInterface; |  | ||||||
| import fr.free.nrw.commons.media.PageMediaInterface; |  | ||||||
| import fr.free.nrw.commons.media.WikidataMediaInterface; |  | ||||||
| import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; |  | ||||||
| import fr.free.nrw.commons.mwapi.UserInterface; |  | ||||||
| import fr.free.nrw.commons.notification.NotificationInterface; |  | ||||||
| import fr.free.nrw.commons.review.ReviewInterface; |  | ||||||
| import fr.free.nrw.commons.upload.UploadInterface; |  | ||||||
| import fr.free.nrw.commons.upload.WikiBaseInterface; |  | ||||||
| import fr.free.nrw.commons.upload.depicts.DepictsInterface; |  | ||||||
| import fr.free.nrw.commons.wikidata.CommonsServiceFactory; |  | ||||||
| import fr.free.nrw.commons.wikidata.WikidataInterface; |  | ||||||
| import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar; |  | ||||||
| import fr.free.nrw.commons.wikidata.cookies.CommonsCookieStorage; |  | ||||||
| import java.io.File; |  | ||||||
| import java.util.Locale; |  | ||||||
| import java.util.concurrent.TimeUnit; |  | ||||||
| import javax.inject.Named; |  | ||||||
| import javax.inject.Singleton; |  | ||||||
| import okhttp3.Cache; |  | ||||||
| import okhttp3.HttpUrl; |  | ||||||
| import okhttp3.OkHttpClient; |  | ||||||
| import okhttp3.logging.HttpLoggingInterceptor; |  | ||||||
| import okhttp3.logging.HttpLoggingInterceptor.Level; |  | ||||||
| import fr.free.nrw.commons.wikidata.model.WikiSite; |  | ||||||
| import fr.free.nrw.commons.wikidata.GsonUtil; |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 |  | ||||||
| @Module |  | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) |  | ||||||
| public class NetworkingModule { |  | ||||||
|     private static final String WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql"; |  | ||||||
|     private static final String TOOLS_FORGE_URL = "https://tools.wmflabs.org/commons-android-app/tool-commons-android-app"; |  | ||||||
| 
 |  | ||||||
|     public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024; |  | ||||||
| 
 |  | ||||||
|     private static final String NAMED_WIKI_DATA_WIKI_SITE = "wikidata-wikisite"; |  | ||||||
|     private static final String NAMED_WIKI_PEDIA_WIKI_SITE = "wikipedia-wikisite"; |  | ||||||
| 
 |  | ||||||
|     public static final String NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE = "language-wikipedia-wikisite"; |  | ||||||
| 
 |  | ||||||
|     public static final String NAMED_COMMONS_CSRF = "commons-csrf"; |  | ||||||
|     public static final String NAMED_WIKI_CSRF = "wiki-csrf"; |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public OkHttpClient provideOkHttpClient(Context context, |  | ||||||
|                                             HttpLoggingInterceptor httpLoggingInterceptor) { |  | ||||||
|         File dir = new File(context.getCacheDir(), "okHttpCache"); |  | ||||||
|         return new OkHttpClient.Builder() |  | ||||||
|             .connectTimeout(120, TimeUnit.SECONDS) |  | ||||||
|             .writeTimeout(120, TimeUnit.SECONDS) |  | ||||||
|                 .addInterceptor(httpLoggingInterceptor) |  | ||||||
|             .readTimeout(120, TimeUnit.SECONDS) |  | ||||||
|             .cache(new Cache(dir, OK_HTTP_CACHE_SIZE)) |  | ||||||
|             .build(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CommonsServiceFactory serviceFactory(CommonsCookieJar cookieJar) { |  | ||||||
|         return new CommonsServiceFactory(OkHttpConnectionFactory.getClient(cookieJar)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public HttpLoggingInterceptor provideHttpLoggingInterceptor() { |  | ||||||
|         HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(message -> { |  | ||||||
|             Timber.tag("OkHttp").v(message); |  | ||||||
|         }); |  | ||||||
|         httpLoggingInterceptor.setLevel(BuildConfig.DEBUG ? Level.BODY: Level.BASIC); |  | ||||||
|         return httpLoggingInterceptor; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient, |  | ||||||
|                                                           DepictsClient depictsClient, |  | ||||||
|                                                           @Named("tools_forge") HttpUrl toolsForgeUrl, |  | ||||||
|                                                           @Named("default_preferences") JsonKvStore defaultKvStore, |  | ||||||
|                                                           Gson gson) { |  | ||||||
|         return new OkHttpJsonApiClient(okHttpClient, |  | ||||||
|                 depictsClient, |  | ||||||
|                 toolsForgeUrl, |  | ||||||
|                 WIKIDATA_SPARQL_QUERY_URL, |  | ||||||
|                 BuildConfig.WIKIMEDIA_CAMPAIGNS_URL, |  | ||||||
|             gson); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CommonsCookieStorage provideCookieStorage( |  | ||||||
|         @Named("default_preferences") JsonKvStore preferences) { |  | ||||||
|         CommonsCookieStorage cookieStorage = new CommonsCookieStorage(preferences); |  | ||||||
|         cookieStorage.load(); |  | ||||||
|         return cookieStorage; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CommonsCookieJar provideCookieJar(CommonsCookieStorage storage) { |  | ||||||
|         return new CommonsCookieJar(storage); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named(NAMED_COMMONS_CSRF) |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CsrfTokenClient provideCommonsCsrfTokenClient(SessionManager sessionManager, |  | ||||||
|         @Named("commons-csrf-interface") CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) { |  | ||||||
|         return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides a singleton instance of CsrfTokenClient for Wikidata. |  | ||||||
|      * |  | ||||||
|      * @param sessionManager The session manager to manage user sessions. |  | ||||||
|      * @param tokenInterface The interface for obtaining CSRF tokens. |  | ||||||
|      * @param loginClient    The client for handling login operations. |  | ||||||
|      * @param logoutClient   The client for handling logout operations. |  | ||||||
|      * @return A singleton instance of CsrfTokenClient. |  | ||||||
|      */ |  | ||||||
|     @Named(NAMED_WIKI_CSRF) |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CsrfTokenClient provideWikiCsrfTokenClient(SessionManager sessionManager, |  | ||||||
|         @Named("wikidata-csrf-interface") CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) { |  | ||||||
|         return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides a singleton instance of CsrfTokenInterface for Wikidata. |  | ||||||
|      * |  | ||||||
|      * @param serviceFactory The factory used to create service interfaces. |  | ||||||
|      * @return A singleton instance of CsrfTokenInterface for Wikidata. |  | ||||||
|      */ |  | ||||||
|     @Named("wikidata-csrf-interface") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CsrfTokenInterface provideWikidataCsrfTokenInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.WIKIDATA_URL, CsrfTokenInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named("commons-csrf-interface") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CsrfTokenInterface provideCsrfTokenInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, CsrfTokenInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public LoginInterface provideLoginInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, LoginInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public LoginClient provideLoginClient(LoginInterface loginInterface) { |  | ||||||
|         return new LoginClient(loginInterface); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("wikimedia_api_host") |  | ||||||
|     @NonNull |  | ||||||
|     @SuppressWarnings("ConstantConditions") |  | ||||||
|     public String provideMwApiUrl() { |  | ||||||
|         return BuildConfig.WIKIMEDIA_API_HOST; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Named("tools_forge") |  | ||||||
|     @NonNull |  | ||||||
|     @SuppressWarnings("ConstantConditions") |  | ||||||
|     public HttpUrl provideToolsForgeUrl() { |  | ||||||
|         return HttpUrl.parse(TOOLS_FORGE_URL); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     @Named(NAMED_WIKI_DATA_WIKI_SITE) |  | ||||||
|     public WikiSite provideWikidataWikiSite() { |  | ||||||
|         return new WikiSite(BuildConfig.WIKIDATA_URL); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere. |  | ||||||
|      * @return returns a singleton Gson instance |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public Gson provideGson() { |  | ||||||
|         return GsonUtil.getDefaultGson(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public ReviewInterface provideReviewInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, ReviewInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public DepictsInterface provideDepictsInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.WIKIDATA_URL, DepictsInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public WikiBaseInterface provideWikiBaseInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, WikiBaseInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public UploadInterface provideUploadInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, UploadInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named("commons-page-edit-service") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public PageEditInterface providePageEditService(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, PageEditInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named("wikidata-page-edit-service") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public PageEditInterface provideWikiDataPageEditService(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.WIKIDATA_URL, PageEditInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Named("commons-page-edit") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public PageEditClient provideCommonsPageEditClient(@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient, |  | ||||||
|                                                        @Named("commons-page-edit-service") PageEditInterface pageEditInterface) { |  | ||||||
|         return new PageEditClient(csrfTokenClient, pageEditInterface); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Provides a singleton instance of PageEditClient for Wikidata. |  | ||||||
|      * |  | ||||||
|      * @param csrfTokenClient    The client used to manage CSRF tokens. |  | ||||||
|      * @param pageEditInterface  The interface for page edit operations. |  | ||||||
|      * @return A singleton instance of PageEditClient for Wikidata. |  | ||||||
|      */ |  | ||||||
|     @Named("wikidata-page-edit") |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public PageEditClient provideWikidataPageEditClient(@Named(NAMED_WIKI_CSRF) CsrfTokenClient csrfTokenClient, |  | ||||||
|         @Named("wikidata-page-edit-service") PageEditInterface pageEditInterface) { |  | ||||||
|         return new PageEditClient(csrfTokenClient, pageEditInterface); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public MediaInterface provideMediaInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, MediaInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Add provider for WikidataMediaInterface |  | ||||||
|      * It creates a retrofit service for the commons wiki site |  | ||||||
|      * @param commonsWikiSite commonsWikiSite |  | ||||||
|      * @return WikidataMediaInterface |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public WikidataMediaInterface provideWikidataMediaInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BetaConstants.COMMONS_URL, WikidataMediaInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public MediaDetailInterface providesMediaDetailInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, MediaDetailInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public CategoryInterface provideCategoryInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, CategoryInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public ThanksInterface provideThanksInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, ThanksInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public NotificationInterface provideNotificationInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, NotificationInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public UserInterface provideUserInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.COMMONS_URL, UserInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public WikidataInterface provideWikidataInterface(CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(BuildConfig.WIKIDATA_URL, WikidataInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Add provider for PageMediaInterface |  | ||||||
|      * It creates a retrofit service for the wiki site using device's current language |  | ||||||
|      */ |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     public PageMediaInterface providePageMediaInterface(@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) WikiSite wikiSite, CommonsServiceFactory serviceFactory) { |  | ||||||
|         return serviceFactory.create(wikiSite.url(), PageMediaInterface.class); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Provides |  | ||||||
|     @Singleton |  | ||||||
|     @Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) |  | ||||||
|     public WikiSite provideLanguageWikipediaSite() { |  | ||||||
|         return WikiSite.forLanguageCode(Locale.getDefault().getLanguage()); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										316
									
								
								app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,316 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import android.content.Context | ||||||
|  | import com.google.gson.Gson | ||||||
|  | import dagger.Module | ||||||
|  | import dagger.Provides | ||||||
|  | import fr.free.nrw.commons.BetaConstants | ||||||
|  | import fr.free.nrw.commons.BuildConfig | ||||||
|  | import fr.free.nrw.commons.OkHttpConnectionFactory | ||||||
|  | import fr.free.nrw.commons.actions.PageEditClient | ||||||
|  | import fr.free.nrw.commons.actions.PageEditInterface | ||||||
|  | import fr.free.nrw.commons.actions.ThanksInterface | ||||||
|  | import fr.free.nrw.commons.auth.SessionManager | ||||||
|  | import fr.free.nrw.commons.auth.csrf.CsrfTokenClient | ||||||
|  | import fr.free.nrw.commons.auth.csrf.CsrfTokenInterface | ||||||
|  | import fr.free.nrw.commons.auth.csrf.LogoutClient | ||||||
|  | import fr.free.nrw.commons.auth.login.LoginClient | ||||||
|  | import fr.free.nrw.commons.auth.login.LoginInterface | ||||||
|  | import fr.free.nrw.commons.category.CategoryInterface | ||||||
|  | import fr.free.nrw.commons.explore.depictions.DepictsClient | ||||||
|  | import fr.free.nrw.commons.kvstore.JsonKvStore | ||||||
|  | import fr.free.nrw.commons.media.MediaDetailInterface | ||||||
|  | import fr.free.nrw.commons.media.MediaInterface | ||||||
|  | import fr.free.nrw.commons.media.PageMediaInterface | ||||||
|  | import fr.free.nrw.commons.media.WikidataMediaInterface | ||||||
|  | import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient | ||||||
|  | import fr.free.nrw.commons.mwapi.UserInterface | ||||||
|  | import fr.free.nrw.commons.notification.NotificationInterface | ||||||
|  | import fr.free.nrw.commons.review.ReviewInterface | ||||||
|  | import fr.free.nrw.commons.upload.UploadInterface | ||||||
|  | import fr.free.nrw.commons.upload.WikiBaseInterface | ||||||
|  | import fr.free.nrw.commons.upload.depicts.DepictsInterface | ||||||
|  | import fr.free.nrw.commons.wikidata.CommonsServiceFactory | ||||||
|  | import fr.free.nrw.commons.wikidata.GsonUtil | ||||||
|  | import fr.free.nrw.commons.wikidata.WikidataInterface | ||||||
|  | import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar | ||||||
|  | import fr.free.nrw.commons.wikidata.cookies.CommonsCookieStorage | ||||||
|  | import fr.free.nrw.commons.wikidata.model.WikiSite | ||||||
|  | import okhttp3.Cache | ||||||
|  | import okhttp3.HttpUrl | ||||||
|  | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||||
|  | import okhttp3.OkHttpClient | ||||||
|  | import okhttp3.logging.HttpLoggingInterceptor | ||||||
|  | import okhttp3.logging.HttpLoggingInterceptor.Level | ||||||
|  | import timber.log.Timber | ||||||
|  | import java.io.File | ||||||
|  | import java.util.Locale | ||||||
|  | import java.util.concurrent.TimeUnit | ||||||
|  | import javax.inject.Named | ||||||
|  | import javax.inject.Singleton | ||||||
|  | 
 | ||||||
|  | @Module | ||||||
|  | @Suppress("unused") | ||||||
|  | class NetworkingModule { | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideOkHttpClient( | ||||||
|  |         context: Context, | ||||||
|  |         httpLoggingInterceptor: HttpLoggingInterceptor | ||||||
|  |     ): OkHttpClient = OkHttpClient.Builder() | ||||||
|  |         .connectTimeout(120, TimeUnit.SECONDS) | ||||||
|  |         .writeTimeout(120, TimeUnit.SECONDS) | ||||||
|  |         .addInterceptor(httpLoggingInterceptor) | ||||||
|  |         .readTimeout(120, TimeUnit.SECONDS) | ||||||
|  |         .cache(Cache(File(context.cacheDir, "okHttpCache"), OK_HTTP_CACHE_SIZE)) | ||||||
|  |         .build() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun serviceFactory(cookieJar: CommonsCookieJar): CommonsServiceFactory = | ||||||
|  |         CommonsServiceFactory(OkHttpConnectionFactory.getClient(cookieJar)) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor = | ||||||
|  |         HttpLoggingInterceptor { message: String? -> | ||||||
|  |             Timber.tag("OkHttp").v(message) | ||||||
|  |         }.apply { | ||||||
|  |             level = if (BuildConfig.DEBUG) Level.BODY else Level.BASIC | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideOkHttpJsonApiClient( | ||||||
|  |         okHttpClient: OkHttpClient, | ||||||
|  |         depictsClient: DepictsClient, | ||||||
|  |         @Named("tools_forge") toolsForgeUrl: HttpUrl, | ||||||
|  |         gson: Gson | ||||||
|  |     ): OkHttpJsonApiClient = OkHttpJsonApiClient( | ||||||
|  |         okHttpClient, depictsClient, toolsForgeUrl, WIKIDATA_SPARQL_QUERY_URL, | ||||||
|  |         BuildConfig.WIKIMEDIA_CAMPAIGNS_URL, gson | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideCookieStorage( | ||||||
|  |         @Named("default_preferences") preferences: JsonKvStore | ||||||
|  |     ): CommonsCookieStorage = CommonsCookieStorage(preferences).also { | ||||||
|  |         it.load() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideCookieJar(storage: CommonsCookieStorage): CommonsCookieJar = | ||||||
|  |         CommonsCookieJar(storage) | ||||||
|  | 
 | ||||||
|  |     @Named(NAMED_COMMONS_CSRF) | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideCommonsCsrfTokenClient( | ||||||
|  |         sessionManager: SessionManager, | ||||||
|  |         @Named("commons-csrf-interface") tokenInterface: CsrfTokenInterface, | ||||||
|  |         loginClient: LoginClient, | ||||||
|  |         logoutClient: LogoutClient | ||||||
|  |     ): CsrfTokenClient = CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Provides a singleton instance of CsrfTokenClient for Wikidata. | ||||||
|  |      * | ||||||
|  |      * @param sessionManager The session manager to manage user sessions. | ||||||
|  |      * @param tokenInterface The interface for obtaining CSRF tokens. | ||||||
|  |      * @param loginClient    The client for handling login operations. | ||||||
|  |      * @param logoutClient   The client for handling logout operations. | ||||||
|  |      * @return A singleton instance of CsrfTokenClient. | ||||||
|  |      */ | ||||||
|  |     @Named(NAMED_WIKI_CSRF) | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikiCsrfTokenClient( | ||||||
|  |         sessionManager: SessionManager, | ||||||
|  |         @Named("wikidata-csrf-interface") tokenInterface: CsrfTokenInterface, | ||||||
|  |         loginClient: LoginClient, | ||||||
|  |         logoutClient: LogoutClient | ||||||
|  |     ): CsrfTokenClient = CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Provides a singleton instance of CsrfTokenInterface for Wikidata. | ||||||
|  |      * | ||||||
|  |      * @param factory The factory used to create service interfaces. | ||||||
|  |      * @return A singleton instance of CsrfTokenInterface for Wikidata. | ||||||
|  |      */ | ||||||
|  |     @Named("wikidata-csrf-interface") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikidataCsrfTokenInterface(factory: CommonsServiceFactory): CsrfTokenInterface = | ||||||
|  |         factory.create(BuildConfig.WIKIDATA_URL) | ||||||
|  | 
 | ||||||
|  |     @Named("commons-csrf-interface") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideCsrfTokenInterface(factory: CommonsServiceFactory): CsrfTokenInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideLoginInterface(factory: CommonsServiceFactory): LoginInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideLoginClient(loginInterface: LoginInterface): LoginClient = | ||||||
|  |         LoginClient(loginInterface) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Named("tools_forge") | ||||||
|  |     fun provideToolsForgeUrl(): HttpUrl = TOOLS_FORGE_URL.toHttpUrlOrNull()!! | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     @Named(NAMED_WIKI_DATA_WIKI_SITE) | ||||||
|  |     fun provideWikidataWikiSite(): WikiSite = WikiSite(BuildConfig.WIKIDATA_URL) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere. | ||||||
|  |      * @return returns a singleton Gson instance | ||||||
|  |      */ | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideGson(): Gson = GsonUtil.getDefaultGson() | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideReviewInterface(factory: CommonsServiceFactory): ReviewInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideDepictsInterface(factory: CommonsServiceFactory): DepictsInterface = | ||||||
|  |         factory.create(BuildConfig.WIKIDATA_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikiBaseInterface(factory: CommonsServiceFactory): WikiBaseInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideUploadInterface(factory: CommonsServiceFactory): UploadInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Named("commons-page-edit-service") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun providePageEditService(factory: CommonsServiceFactory): PageEditInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Named("wikidata-page-edit-service") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikiDataPageEditService(factory: CommonsServiceFactory): PageEditInterface = | ||||||
|  |         factory.create(BuildConfig.WIKIDATA_URL) | ||||||
|  | 
 | ||||||
|  |     @Named("commons-page-edit") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideCommonsPageEditClient( | ||||||
|  |         @Named(NAMED_COMMONS_CSRF) csrfTokenClient: CsrfTokenClient, | ||||||
|  |         @Named("commons-page-edit-service") pageEditInterface: PageEditInterface | ||||||
|  |     ): PageEditClient = PageEditClient(csrfTokenClient, pageEditInterface) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Provides a singleton instance of PageEditClient for Wikidata. | ||||||
|  |      * | ||||||
|  |      * @param csrfTokenClient    The client used to manage CSRF tokens. | ||||||
|  |      * @param pageEditInterface  The interface for page edit operations. | ||||||
|  |      * @return A singleton instance of PageEditClient for Wikidata. | ||||||
|  |      */ | ||||||
|  |     @Named("wikidata-page-edit") | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikidataPageEditClient( | ||||||
|  |         @Named(NAMED_WIKI_CSRF) csrfTokenClient: CsrfTokenClient, | ||||||
|  |         @Named("wikidata-page-edit-service") pageEditInterface: PageEditInterface | ||||||
|  |     ): PageEditClient = PageEditClient(csrfTokenClient, pageEditInterface) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideMediaInterface(factory: CommonsServiceFactory): MediaInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Add provider for WikidataMediaInterface | ||||||
|  |      * It creates a retrofit service for the commons wiki site | ||||||
|  |      * @param commonsWikiSite commonsWikiSite | ||||||
|  |      * @return WikidataMediaInterface | ||||||
|  |      */ | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikidataMediaInterface(factory: CommonsServiceFactory): WikidataMediaInterface = | ||||||
|  |         factory.create(BetaConstants.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun providesMediaDetailInterface(factory: CommonsServiceFactory): MediaDetailInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideCategoryInterface(factory: CommonsServiceFactory): CategoryInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideThanksInterface(factory: CommonsServiceFactory): ThanksInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideNotificationInterface(factory: CommonsServiceFactory): NotificationInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideUserInterface(factory: CommonsServiceFactory): UserInterface = | ||||||
|  |         factory.create(BuildConfig.COMMONS_URL) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun provideWikidataInterface(factory: CommonsServiceFactory): WikidataInterface = | ||||||
|  |         factory.create(BuildConfig.WIKIDATA_URL) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Add provider for PageMediaInterface | ||||||
|  |      * It creates a retrofit service for the wiki site using device's current language | ||||||
|  |      */ | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     fun providePageMediaInterface( | ||||||
|  |         @Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) wikiSite: WikiSite, | ||||||
|  |         factory: CommonsServiceFactory | ||||||
|  |     ): PageMediaInterface = factory.create(wikiSite.url()) | ||||||
|  | 
 | ||||||
|  |     @Provides | ||||||
|  |     @Singleton | ||||||
|  |     @Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE) | ||||||
|  |     fun provideLanguageWikipediaSite(): WikiSite { | ||||||
|  |         return WikiSite.forLanguageCode(Locale.getDefault().language) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         private const val WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql" | ||||||
|  |         private const val TOOLS_FORGE_URL = | ||||||
|  |             "https://tools.wmflabs.org/commons-android-app/tool-commons-android-app" | ||||||
|  | 
 | ||||||
|  |         const val OK_HTTP_CACHE_SIZE: Long = (10 * 1024 * 1024).toLong() | ||||||
|  | 
 | ||||||
|  |         private const val NAMED_WIKI_DATA_WIKI_SITE = "wikidata-wikisite" | ||||||
|  |         private const val NAMED_WIKI_PEDIA_WIKI_SITE = "wikipedia-wikisite" | ||||||
|  | 
 | ||||||
|  |         const val NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE: String = "language-wikipedia-wikisite" | ||||||
|  | 
 | ||||||
|  |         const val NAMED_COMMONS_CSRF: String = "commons-csrf" | ||||||
|  |         const val NAMED_WIKI_CSRF: String = "wiki-csrf" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,19 +0,0 @@ | ||||||
| package fr.free.nrw.commons.di; |  | ||||||
| 
 |  | ||||||
| import dagger.Module; |  | ||||||
| import dagger.android.ContributesAndroidInjector; |  | ||||||
| import fr.free.nrw.commons.auth.WikiAccountAuthenticatorService; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * This Class Represents the Module for dependency injection (using dagger) |  | ||||||
|  * so, if a developer needs to add a new Service to the commons app |  | ||||||
|  * then that must be mentioned here to inject the dependencies  |  | ||||||
|  */ |  | ||||||
| @Module |  | ||||||
| @SuppressWarnings({"WeakerAccess", "unused"}) |  | ||||||
| public abstract class ServiceBuilderModule { |  | ||||||
| 
 |  | ||||||
|     @ContributesAndroidInjector |  | ||||||
|     abstract WikiAccountAuthenticatorService bindWikiAccountAuthenticatorService(); |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,17 @@ | ||||||
|  | package fr.free.nrw.commons.di | ||||||
|  | 
 | ||||||
|  | import dagger.Module | ||||||
|  | import dagger.android.ContributesAndroidInjector | ||||||
|  | import fr.free.nrw.commons.auth.WikiAccountAuthenticatorService | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * This Class Represents the Module for dependency injection (using dagger) | ||||||
|  |  * so, if a developer needs to add a new Service to the commons app | ||||||
|  |  * then that must be mentioned here to inject the dependencies | ||||||
|  |  */ | ||||||
|  | @Module | ||||||
|  | @Suppress("unused") | ||||||
|  | abstract class ServiceBuilderModule { | ||||||
|  |     @ContributesAndroidInjector | ||||||
|  |     abstract fun bindWikiAccountAuthenticatorService(): WikiAccountAuthenticatorService | ||||||
|  | } | ||||||
|  | @ -1,141 +0,0 @@ | ||||||
| package fr.free.nrw.commons.language; |  | ||||||
| 
 |  | ||||||
| import android.content.Context; |  | ||||||
| import android.content.res.Resources; |  | ||||||
| import android.text.TextUtils; |  | ||||||
| 
 |  | ||||||
| import androidx.annotation.ArrayRes; |  | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| import androidx.annotation.Nullable; |  | ||||||
| 
 |  | ||||||
| import fr.free.nrw.commons.R; |  | ||||||
| import java.lang.ref.SoftReference; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Locale; |  | ||||||
| 
 |  | ||||||
| /** Immutable look up table for all app supported languages. All article languages may not be |  | ||||||
|   * present in this table as it is statically bundled with the app. */ |  | ||||||
| public class AppLanguageLookUpTable { |  | ||||||
|     public static final String SIMPLIFIED_CHINESE_LANGUAGE_CODE = "zh-hans"; |  | ||||||
|     public static final String TRADITIONAL_CHINESE_LANGUAGE_CODE = "zh-hant"; |  | ||||||
|     public static final String CHINESE_CN_LANGUAGE_CODE = "zh-cn"; |  | ||||||
|     public static final String CHINESE_HK_LANGUAGE_CODE = "zh-hk"; |  | ||||||
|     public static final String CHINESE_MO_LANGUAGE_CODE = "zh-mo"; |  | ||||||
|     public static final String CHINESE_SG_LANGUAGE_CODE = "zh-sg"; |  | ||||||
|     public static final String CHINESE_TW_LANGUAGE_CODE = "zh-tw"; |  | ||||||
|     public static final String CHINESE_YUE_LANGUAGE_CODE = "zh-yue"; |  | ||||||
|     public static final String CHINESE_LANGUAGE_CODE = "zh"; |  | ||||||
|     public static final String NORWEGIAN_LEGACY_LANGUAGE_CODE = "no"; |  | ||||||
|     public static final String NORWEGIAN_BOKMAL_LANGUAGE_CODE = "nb"; |  | ||||||
|     public static final String TEST_LANGUAGE_CODE = "test"; |  | ||||||
|     public static final String FALLBACK_LANGUAGE_CODE = "en"; // Must exist in preference_language_keys. |  | ||||||
| 
 |  | ||||||
|     @NonNull private final Resources resources; |  | ||||||
| 
 |  | ||||||
|     // Language codes for all app supported languages in fixed order. The special code representing |  | ||||||
|     // the dynamic system language is null. |  | ||||||
|     @NonNull private SoftReference<List<String>> codesRef = new SoftReference<>(null); |  | ||||||
| 
 |  | ||||||
|     // English names for all app supported languages in fixed order. |  | ||||||
|     @NonNull private SoftReference<List<String>> canonicalNamesRef = new SoftReference<>(null); |  | ||||||
| 
 |  | ||||||
|     // Native names for all app supported languages in fixed order. |  | ||||||
|     @NonNull private SoftReference<List<String>> localizedNamesRef = new SoftReference<>(null); |  | ||||||
| 
 |  | ||||||
|     public AppLanguageLookUpTable(@NonNull Context context) { |  | ||||||
|         resources = context.getResources(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * @return Nonnull immutable list. The special code representing the dynamic system language is |  | ||||||
|      *         null. |  | ||||||
|      */ |  | ||||||
|     @NonNull |  | ||||||
|     public List<String> getCodes() { |  | ||||||
|         List<String> codes = codesRef.get(); |  | ||||||
|         if (codes == null) { |  | ||||||
|             codes = getStringList(R.array.preference_language_keys); |  | ||||||
|             codesRef = new SoftReference<>(codes); |  | ||||||
|         } |  | ||||||
|         return codes; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Nullable |  | ||||||
|     public String getCanonicalName(@Nullable String code) { |  | ||||||
|         String name = defaultIndex(getCanonicalNames(), indexOfCode(code), null); |  | ||||||
|         if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(code)) { |  | ||||||
|             if (code.equals(Locale.CHINESE.getLanguage())) { |  | ||||||
|                 name = Locale.CHINESE.getDisplayName(Locale.ENGLISH); |  | ||||||
|             } else if (code.equals(NORWEGIAN_LEGACY_LANGUAGE_CODE)) { |  | ||||||
|                 name = defaultIndex(getCanonicalNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return name; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Nullable |  | ||||||
|     public String getLocalizedName(@Nullable String code) { |  | ||||||
|         String name = defaultIndex(getLocalizedNames(), indexOfCode(code), null); |  | ||||||
|         if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(code)) { |  | ||||||
|             if (code.equals(Locale.CHINESE.getLanguage())) { |  | ||||||
|                 name = Locale.CHINESE.getDisplayName(Locale.CHINESE); |  | ||||||
|             } else if (code.equals(NORWEGIAN_LEGACY_LANGUAGE_CODE)) { |  | ||||||
|                 name = defaultIndex(getLocalizedNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return name; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public List<String> getCanonicalNames() { |  | ||||||
|         List<String> names = canonicalNamesRef.get(); |  | ||||||
|         if (names == null) { |  | ||||||
|             names = getStringList(R.array.preference_language_canonical_names); |  | ||||||
|             canonicalNamesRef = new SoftReference<>(names); |  | ||||||
|         } |  | ||||||
|         return names; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public List<String> getLocalizedNames() { |  | ||||||
|         List<String> names = localizedNamesRef.get(); |  | ||||||
|         if (names == null) { |  | ||||||
|             names = getStringList(R.array.preference_language_local_names); |  | ||||||
|             localizedNamesRef = new SoftReference<>(names); |  | ||||||
|         } |  | ||||||
|         return names; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public boolean isSupportedCode(@Nullable String code) { |  | ||||||
|         return getCodes().contains(code); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private <T> T defaultIndex(List<T> list, int index, T defaultValue) { |  | ||||||
|         return inBounds(list, index) ? list.get(index) : defaultValue; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Searches #codes for the specified language code and returns the index for use in |  | ||||||
|      * #canonicalNames and #localizedNames. |  | ||||||
|      * |  | ||||||
|      * @param code The language code to search for. The special code representing the dynamic system |  | ||||||
|      *             language is null. |  | ||||||
|      * @return The index of the language code or -1 if the code is not supported. |  | ||||||
|      */ |  | ||||||
|     private int indexOfCode(@Nullable String code) { |  | ||||||
|         return getCodes().indexOf(code); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** @return Nonnull immutable list. */ |  | ||||||
|     @NonNull |  | ||||||
|     private List<String> getStringList(int id) { |  | ||||||
|         return Arrays.asList(getStringArray(id)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private boolean inBounds(List<?> list, int index) { |  | ||||||
|         return index >= 0 && index < list.size(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public String[] getStringArray(@ArrayRes int id) { |  | ||||||
|         return resources.getStringArray(id); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,135 @@ | ||||||
|  | package fr.free.nrw.commons.language | ||||||
|  | 
 | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.res.Resources | ||||||
|  | import android.text.TextUtils | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.ArrayRes | ||||||
|  | import fr.free.nrw.commons.R | ||||||
|  | import java.lang.ref.SoftReference | ||||||
|  | import java.util.Arrays | ||||||
|  | import java.util.Locale | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** Immutable look up table for all app supported languages. All article languages may not be | ||||||
|  |  * present in this table as it is statically bundled with the app. */ | ||||||
|  | class AppLanguageLookUpTable(context: Context) { | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         const val SIMPLIFIED_CHINESE_LANGUAGE_CODE = "zh-hans" | ||||||
|  |         const val TRADITIONAL_CHINESE_LANGUAGE_CODE = "zh-hant" | ||||||
|  |         const val CHINESE_CN_LANGUAGE_CODE = "zh-cn" | ||||||
|  |         const val CHINESE_HK_LANGUAGE_CODE = "zh-hk" | ||||||
|  |         const val CHINESE_MO_LANGUAGE_CODE = "zh-mo" | ||||||
|  |         const val CHINESE_SG_LANGUAGE_CODE = "zh-sg" | ||||||
|  |         const val CHINESE_TW_LANGUAGE_CODE = "zh-tw" | ||||||
|  |         const val CHINESE_YUE_LANGUAGE_CODE = "zh-yue" | ||||||
|  |         const val CHINESE_LANGUAGE_CODE = "zh" | ||||||
|  |         const val NORWEGIAN_LEGACY_LANGUAGE_CODE = "no" | ||||||
|  |         const val NORWEGIAN_BOKMAL_LANGUAGE_CODE = "nb" | ||||||
|  |         const val TEST_LANGUAGE_CODE = "test" | ||||||
|  |         const val FALLBACK_LANGUAGE_CODE = "en" // Must exist in preference_language_keys. | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private val resources: Resources = context.resources | ||||||
|  | 
 | ||||||
|  |     // Language codes for all app supported languages in fixed order. The special code representing | ||||||
|  |     // the dynamic system language is null. | ||||||
|  |     private var codesRef = SoftReference<List<String>>(null) | ||||||
|  | 
 | ||||||
|  |     // English names for all app supported languages in fixed order. | ||||||
|  |     private var canonicalNamesRef = SoftReference<List<String>>(null) | ||||||
|  | 
 | ||||||
|  |     // Native names for all app supported languages in fixed order. | ||||||
|  |     private var localizedNamesRef = SoftReference<List<String>>(null) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @return Nonnull immutable list. The special code representing the dynamic system language is | ||||||
|  |      *         null. | ||||||
|  |      */ | ||||||
|  |     fun getCodes(): List<String> { | ||||||
|  |         var codes = codesRef.get() | ||||||
|  |         if (codes == null) { | ||||||
|  |             codes = getStringList(R.array.preference_language_keys) | ||||||
|  |             codesRef = SoftReference(codes) | ||||||
|  |         } | ||||||
|  |         return codes | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getCanonicalName(code: String?): String? { | ||||||
|  |         var name = defaultIndex(getCanonicalNames(), indexOfCode(code), null) | ||||||
|  |         if (name.isNullOrEmpty() && !code.isNullOrEmpty()) { | ||||||
|  |             name = when (code) { | ||||||
|  |                 Locale.CHINESE.language -> Locale.CHINESE.getDisplayName(Locale.ENGLISH) | ||||||
|  |                 NORWEGIAN_LEGACY_LANGUAGE_CODE -> | ||||||
|  |                     defaultIndex(getCanonicalNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null) | ||||||
|  |                 else -> null | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return name | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getLocalizedName(code: String?): String? { | ||||||
|  |         var name = defaultIndex(getLocalizedNames(), indexOfCode(code), null) | ||||||
|  |         if (name.isNullOrEmpty() && !code.isNullOrEmpty()) { | ||||||
|  |             name = when (code) { | ||||||
|  |                 Locale.CHINESE.language -> Locale.CHINESE.getDisplayName(Locale.CHINESE) | ||||||
|  |                 NORWEGIAN_LEGACY_LANGUAGE_CODE -> | ||||||
|  |                     defaultIndex(getLocalizedNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null) | ||||||
|  |                 else -> null | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return name | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getCanonicalNames(): List<String> { | ||||||
|  |         var names = canonicalNamesRef.get() | ||||||
|  |         if (names == null) { | ||||||
|  |             names = getStringList(R.array.preference_language_canonical_names) | ||||||
|  |             canonicalNamesRef = SoftReference(names) | ||||||
|  |         } | ||||||
|  |         return names | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getLocalizedNames(): List<String> { | ||||||
|  |         var names = localizedNamesRef.get() | ||||||
|  |         if (names == null) { | ||||||
|  |             names = getStringList(R.array.preference_language_local_names) | ||||||
|  |             localizedNamesRef = SoftReference(names) | ||||||
|  |         } | ||||||
|  |         return names | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun isSupportedCode(code: String?): Boolean { | ||||||
|  |         return getCodes().contains(code) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun <T> defaultIndex(list: List<T>, index: Int, defaultValue: T?): T? { | ||||||
|  |         return if (inBounds(list, index)) list[index] else defaultValue | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Searches #codes for the specified language code and returns the index for use in | ||||||
|  |      * #canonicalNames and #localizedNames. | ||||||
|  |      * | ||||||
|  |      * @param code The language code to search for. The special code representing the dynamic system | ||||||
|  |      *             language is null. | ||||||
|  |      * @return The index of the language code or -1 if the code is not supported. | ||||||
|  |      */ | ||||||
|  |     private fun indexOfCode(code: String?): Int { | ||||||
|  |         return getCodes().indexOf(code) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** @return Nonnull immutable list. */ | ||||||
|  |     private fun getStringList(id: Int): List<String> { | ||||||
|  |         return getStringArray(id).toList() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun inBounds(list: List<*>, index: Int): Boolean { | ||||||
|  |         return index in list.indices | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun getStringArray(@ArrayRes id: Int): Array<String> { | ||||||
|  |         return resources.getStringArray(id) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,198 +0,0 @@ | ||||||
| package fr.free.nrw.commons.location; |  | ||||||
| 
 |  | ||||||
| import android.location.Location; |  | ||||||
| import android.net.Uri; |  | ||||||
| import android.os.Parcel; |  | ||||||
| import android.os.Parcelable; |  | ||||||
| 
 |  | ||||||
| import androidx.annotation.NonNull; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * a latitude and longitude point with accuracy information, often of a picture |  | ||||||
|  */ |  | ||||||
| public class LatLng implements Parcelable { |  | ||||||
| 
 |  | ||||||
|     private final double latitude; |  | ||||||
|     private final double longitude; |  | ||||||
|     private final float accuracy; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Accepts latitude and longitude. |  | ||||||
|      * North and South values are cut off at 90° |  | ||||||
|      * |  | ||||||
|      * @param latitude the latitude |  | ||||||
|      * @param longitude the longitude |  | ||||||
|      * @param accuracy the accuracy |  | ||||||
|      * |  | ||||||
|      * Examples: |  | ||||||
|      * the Statue of Liberty is located at 40.69° N, 74.04° W |  | ||||||
|      * The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0) |  | ||||||
|      * where positive signifies north, east and negative signifies south, west. |  | ||||||
|      */ |  | ||||||
|     public LatLng(double latitude, double longitude, float accuracy) { |  | ||||||
|         if (-180.0D <= longitude && longitude < 180.0D) { |  | ||||||
|             this.longitude = longitude; |  | ||||||
|         } else { |  | ||||||
|             this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D; |  | ||||||
|         } |  | ||||||
|         this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude)); |  | ||||||
|         this.accuracy = accuracy; |  | ||||||
|     } |  | ||||||
|     /** |  | ||||||
|      * An alternate constructor for this class. |  | ||||||
|      * @param in A parcelable which contains the latitude, longitude, and accuracy |  | ||||||
|      */ |  | ||||||
|     public LatLng(Parcel in) { |  | ||||||
|         latitude = in.readDouble(); |  | ||||||
|         longitude = in.readDouble(); |  | ||||||
|         accuracy = in.readFloat(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * gets the latitude and longitude of a given non-null location |  | ||||||
|      * @param location the non-null location of the user |  | ||||||
|      * @return LatLng the Latitude and Longitude of a given location |  | ||||||
|      */ |  | ||||||
|     public static LatLng from(@NonNull Location location) { |  | ||||||
|         return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * creates a hash code for the longitude and longitude |  | ||||||
|      */ |  | ||||||
|     public int hashCode() { |  | ||||||
|         byte var1 = 1; |  | ||||||
|         long var2 = Double.doubleToLongBits(this.latitude); |  | ||||||
|         int var3 = 31 * var1 + (int)(var2 ^ var2 >>> 32); |  | ||||||
|         var2 = Double.doubleToLongBits(this.longitude); |  | ||||||
|         var3 = 31 * var3 + (int)(var2 ^ var2 >>> 32); |  | ||||||
|         return var3; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * checks for equality of two LatLng objects |  | ||||||
|      * @param o the second LatLng object |  | ||||||
|      */ |  | ||||||
|     public boolean equals(Object o) { |  | ||||||
|         if (this == o) { |  | ||||||
|             return true; |  | ||||||
|         } else if (!(o instanceof LatLng)) { |  | ||||||
|             return false; |  | ||||||
|         } else { |  | ||||||
|             LatLng var2 = (LatLng)o; |  | ||||||
|             return Double.doubleToLongBits(this.latitude) == Double.doubleToLongBits(var2.latitude) && Double.doubleToLongBits(this.longitude) == Double.doubleToLongBits(var2.longitude); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * returns a string representation of the latitude and longitude |  | ||||||
|      */ |  | ||||||
|     public String toString() { |  | ||||||
|         return "lat/lng: (" + this.latitude + "," + this.longitude + ")"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Rounds the float to 4 digits and returns absolute value. |  | ||||||
|      * |  | ||||||
|      * @param coordinate A coordinate value as string. |  | ||||||
|      * @return String of the rounded number. |  | ||||||
|      */ |  | ||||||
|     private String formatCoordinate(double coordinate) { |  | ||||||
|         double roundedNumber = Math.round(coordinate * 10000d) / 10000d; |  | ||||||
|         double absoluteNumber = Math.abs(roundedNumber); |  | ||||||
|         return String.valueOf(absoluteNumber); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns "N" or "S" depending on the latitude. |  | ||||||
|      * |  | ||||||
|      * @return "N" or "S". |  | ||||||
|      */ |  | ||||||
|     private String getNorthSouth() { |  | ||||||
|         if (this.latitude < 0) { |  | ||||||
|             return "S"; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return "N"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns "E" or "W" depending on the longitude. |  | ||||||
|      * |  | ||||||
|      * @return "E" or "W". |  | ||||||
|      */ |  | ||||||
|     private String getEastWest() { |  | ||||||
|         if (this.longitude >= 0 && this.longitude < 180) { |  | ||||||
|             return "E"; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return "W"; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns a nicely formatted coordinate string. Used e.g. in |  | ||||||
|      * the detail view. |  | ||||||
|      * |  | ||||||
|      * @return The formatted string. |  | ||||||
|      */ |  | ||||||
|     public String getPrettyCoordinateString() { |  | ||||||
|         return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", " |  | ||||||
|                + formatCoordinate(this.longitude) + " " + this.getEastWest(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the location accuracy in meter. |  | ||||||
|      * |  | ||||||
|      * @return float |  | ||||||
|      */ |  | ||||||
|     public float getAccuracy() { |  | ||||||
|         return accuracy; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the longitude in degrees. |  | ||||||
|      * |  | ||||||
|      * @return double |  | ||||||
|      */ |  | ||||||
|     public double getLongitude() { |  | ||||||
|         return longitude; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Return the latitude in degrees. |  | ||||||
|      * |  | ||||||
|      * @return double |  | ||||||
|      */ |  | ||||||
|     public double getLatitude() { |  | ||||||
|         return latitude; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public Uri getGmmIntentUri() { |  | ||||||
|         return Uri.parse("geo:" + latitude + "," + longitude + "?z=16"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public int describeContents() { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void writeToParcel(Parcel dest, int flags) { |  | ||||||
|         dest.writeDouble(latitude); |  | ||||||
|         dest.writeDouble(longitude); |  | ||||||
|         dest.writeFloat(accuracy); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static final Creator<LatLng> CREATOR = new Creator<LatLng>() { |  | ||||||
|         @Override |  | ||||||
|         public LatLng createFromParcel(Parcel in) { |  | ||||||
|             return new LatLng(in); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @Override |  | ||||||
|         public LatLng[] newArray(int size) { |  | ||||||
|             return new LatLng[size]; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
							
								
								
									
										150
									
								
								app/src/main/java/fr/free/nrw/commons/location/LatLng.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								app/src/main/java/fr/free/nrw/commons/location/LatLng.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,150 @@ | ||||||
|  | package fr.free.nrw.commons.location | ||||||
|  | 
 | ||||||
|  | import android.location.Location | ||||||
|  | import android.net.Uri | ||||||
|  | import android.os.Parcel | ||||||
|  | import android.os.Parcelable | ||||||
|  | import kotlin.math.abs | ||||||
|  | import kotlin.math.max | ||||||
|  | import kotlin.math.min | ||||||
|  | import kotlin.math.round | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A latitude and longitude point with accuracy information, often of a picture. | ||||||
|  |  */ | ||||||
|  | data class LatLng( | ||||||
|  |     var latitude: Double, | ||||||
|  |     var longitude: Double, | ||||||
|  |     val accuracy: Float | ||||||
|  | ) : Parcelable { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Accepts latitude and longitude. | ||||||
|  |      * North and South values are cut off at 90° | ||||||
|  |      * | ||||||
|  |      * Examples: | ||||||
|  |      * the Statue of Liberty is located at 40.69° N, 74.04° W | ||||||
|  |      * The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0) | ||||||
|  |      * where positive signifies north, east and negative signifies south, west. | ||||||
|  |      */ | ||||||
|  |     init { | ||||||
|  |         val adjustedLongitude = when { | ||||||
|  |             longitude in -180.0..180.0 -> longitude | ||||||
|  |             else -> ((longitude - 180.0) % 360.0 + 360.0) % 360.0 - 180.0 | ||||||
|  |         } | ||||||
|  |         latitude = max(-90.0, min(90.0, latitude)) | ||||||
|  |         longitude = adjustedLongitude | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Accepts a non-null [Location] and converts it to a [LatLng]. | ||||||
|  |      */ | ||||||
|  |     companion object { | ||||||
|  |         /** | ||||||
|  |          * gets the latitude and longitude of a given non-null location | ||||||
|  |          * @param location the non-null location of the user | ||||||
|  |          * @return LatLng the Latitude and Longitude of a given location | ||||||
|  |          */ | ||||||
|  |         @JvmStatic | ||||||
|  |         fun from(location: Location): LatLng { | ||||||
|  |             return LatLng(location.latitude, location.longitude, location.accuracy) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @JvmField | ||||||
|  |         val CREATOR: Parcelable.Creator<LatLng> = object : Parcelable.Creator<LatLng> { | ||||||
|  |             override fun createFromParcel(parcel: Parcel): LatLng { | ||||||
|  |                 return LatLng(parcel) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             override fun newArray(size: Int): Array<LatLng?> { | ||||||
|  |                 return arrayOfNulls(size) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * An alternate constructor for this class. | ||||||
|  |      * @param parcel A parcelable which contains the latitude, longitude, and accuracy | ||||||
|  |      */ | ||||||
|  |     private constructor(parcel: Parcel) : this( | ||||||
|  |         latitude = parcel.readDouble(), | ||||||
|  |         longitude = parcel.readDouble(), | ||||||
|  |         accuracy = parcel.readFloat() | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Creates a hash code for the latitude and longitude. | ||||||
|  |      */ | ||||||
|  |     override fun hashCode(): Int { | ||||||
|  |         var result = 1 | ||||||
|  |         val latitudeBits = latitude.toBits() | ||||||
|  |         result = 31 * result + (latitudeBits xor (latitudeBits ushr 32)).toInt() | ||||||
|  |         val longitudeBits = longitude.toBits() | ||||||
|  |         result = 31 * result + (longitudeBits xor (longitudeBits ushr 32)).toInt() | ||||||
|  |         return result | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks for equality of two LatLng objects. | ||||||
|  |      * @param other the second LatLng object | ||||||
|  |      */ | ||||||
|  |     override fun equals(other: Any?): Boolean { | ||||||
|  |         if (this === other) return true | ||||||
|  |         if (other !is LatLng) return false | ||||||
|  |         return latitude.toBits() == other.latitude.toBits() && | ||||||
|  |                 longitude.toBits() == other.longitude.toBits() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns a string representation of the latitude and longitude. | ||||||
|  |      */ | ||||||
|  |     override fun toString(): String { | ||||||
|  |         return "lat/lng: ($latitude,$longitude)" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns a nicely formatted coordinate string. Used e.g. in | ||||||
|  |      * the detail view. | ||||||
|  |      * | ||||||
|  |      * @return The formatted string. | ||||||
|  |      */ | ||||||
|  |     fun getPrettyCoordinateString(): String { | ||||||
|  |         return "${formatCoordinate(latitude)} ${getNorthSouth()}, " + | ||||||
|  |                 "${formatCoordinate(longitude)} ${getEastWest()}" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Gets a URI for a Google Maps intent at the location. | ||||||
|  |      */ | ||||||
|  |     fun getGmmIntentUri(): Uri { | ||||||
|  |         return Uri.parse("geo:$latitude,$longitude?z=16") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun writeToParcel(parcel: Parcel, flags: Int) { | ||||||
|  |         parcel.writeDouble(latitude) | ||||||
|  |         parcel.writeDouble(longitude) | ||||||
|  |         parcel.writeFloat(accuracy) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun describeContents(): Int = 0 | ||||||
|  | 
 | ||||||
|  |     private fun formatCoordinate(coordinate: Double): String { | ||||||
|  |         val roundedNumber = round(coordinate * 10000) / 10000 | ||||||
|  |         return abs(roundedNumber).toString() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns "N" or "S" depending on the latitude. | ||||||
|  |      * | ||||||
|  |      * @return "N" or "S". | ||||||
|  |      */ | ||||||
|  |     private fun getNorthSouth(): String = if (latitude < 0) "S" else "N" | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns "E" or "W" depending on the longitude. | ||||||
|  |      * | ||||||
|  |      * @return "E" or "W". | ||||||
|  |      */ | ||||||
|  |     private fun getEastWest(): String = if (longitude in 0.0..179.999) "E" else "W" | ||||||
|  | } | ||||||
|  | @ -1,186 +0,0 @@ | ||||||
| package fr.free.nrw.commons.location; |  | ||||||
| 
 |  | ||||||
| import android.Manifest; |  | ||||||
| import android.Manifest.permission; |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.Intent; |  | ||||||
| import android.content.pm.PackageManager; |  | ||||||
| import android.net.Uri; |  | ||||||
| import android.provider.Settings; |  | ||||||
| import android.widget.Toast; |  | ||||||
| import androidx.core.app.ActivityCompat; |  | ||||||
| import fr.free.nrw.commons.R; |  | ||||||
| import fr.free.nrw.commons.filepicker.Constants.RequestCodes; |  | ||||||
| import fr.free.nrw.commons.utils.DialogUtil; |  | ||||||
| import fr.free.nrw.commons.utils.PermissionUtils; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Helper class to handle location permissions. |  | ||||||
|  * |  | ||||||
|  * Location flow for fragments containing a map is as follows: |  | ||||||
|  * Case 1: When location permission has never been asked for or denied before |  | ||||||
|  * Check if permission is already granted or not. |  | ||||||
|  * If not already granted, ask for it (if it isn't denied twice before). |  | ||||||
|  * If now user grants permission, go to Case 3/4, else go to Case 2. |  | ||||||
|  * |  | ||||||
|  * Case 2: When location permission is just asked but has been denied |  | ||||||
|  * Shows a toast to tell the user why location permission is needed. |  | ||||||
|  * Also shows a rationale to the user, on agreeing to which, we go back to Case 1. |  | ||||||
|  * Show current location / nearby pins / nearby images according to the default location. |  | ||||||
|  * |  | ||||||
|  * Case 3: When location permission are already granted, but location services are off |  | ||||||
|  * Asks the user to turn on the location service, using a dialog. |  | ||||||
|  * If the user rejects, checks for the last known location and shows stuff using that location. |  | ||||||
|  * Also displays a toast telling the user why location should be turned on. |  | ||||||
|  * |  | ||||||
|  * Case 4: When location permission has been granted and location services are also on |  | ||||||
|  * Do whatever is required by that particular activity / fragment using current location. |  | ||||||
|  * |  | ||||||
|  */ |  | ||||||
| public class LocationPermissionsHelper { |  | ||||||
| 
 |  | ||||||
|     Activity activity; |  | ||||||
|     LocationServiceManager locationManager; |  | ||||||
|     LocationPermissionCallback callback; |  | ||||||
| 
 |  | ||||||
|     public LocationPermissionsHelper(Activity activity, LocationServiceManager locationManager, |  | ||||||
|         LocationPermissionCallback callback) { |  | ||||||
|         this.activity = activity; |  | ||||||
|         this.locationManager = locationManager; |  | ||||||
|         this.callback = callback; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Ask for location permission if the user agrees on attaching location with pictures and the |  | ||||||
|      * app does not have the access to location |  | ||||||
|      * |  | ||||||
|      * @param dialogTitleResource Resource id of the title of the dialog  |  | ||||||
|      * @param dialogTextResource Resource id of the text of the dialog  |  | ||||||
|      */ |  | ||||||
|     public void requestForLocationAccess( |  | ||||||
|         int dialogTitleResource, |  | ||||||
|         int dialogTextResource |  | ||||||
|     ) { |  | ||||||
|         if (checkLocationPermission(activity)) { |  | ||||||
|             callback.onLocationPermissionGranted(); |  | ||||||
|         } else { |  | ||||||
|             if (ActivityCompat.shouldShowRequestPermissionRationale(activity, |  | ||||||
|                 permission.ACCESS_FINE_LOCATION)) { |  | ||||||
|                 DialogUtil.showAlertDialog(activity, activity.getString(dialogTitleResource), |  | ||||||
|                     activity.getString(dialogTextResource), |  | ||||||
|                     activity.getString(android.R.string.ok), |  | ||||||
|                     activity.getString(android.R.string.cancel), |  | ||||||
|                     () -> { |  | ||||||
|                         ActivityCompat.requestPermissions(activity, |  | ||||||
|                             new String[]{permission.ACCESS_FINE_LOCATION}, 1); |  | ||||||
|                     }, |  | ||||||
|                     () -> callback.onLocationPermissionDenied( |  | ||||||
|                         activity.getString(R.string.upload_map_location_access)), |  | ||||||
|                     null, |  | ||||||
|                     false); |  | ||||||
|             } else { |  | ||||||
|                 ActivityCompat.requestPermissions(activity, |  | ||||||
|                     new String[]{permission.ACCESS_FINE_LOCATION}, |  | ||||||
|                     RequestCodes.LOCATION); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Shows a dialog for user to open the settings page and turn on location services |  | ||||||
|      * |  | ||||||
|      * @param activity Activity object |  | ||||||
|      * @param dialogTextResource int id of the required string resource |  | ||||||
|      */ |  | ||||||
|     public void showLocationOffDialog(Activity activity, int dialogTextResource) { |  | ||||||
|         DialogUtil |  | ||||||
|             .showAlertDialog(activity, |  | ||||||
|                 activity.getString(R.string.ask_to_turn_location_on), |  | ||||||
|                 activity.getString(dialogTextResource), |  | ||||||
|                 activity.getString(R.string.title_app_shortcut_setting), |  | ||||||
|                 activity.getString(R.string.cancel), |  | ||||||
|                 () -> openLocationSettings(activity), |  | ||||||
|                 () -> Toast.makeText(activity, activity.getString(dialogTextResource), |  | ||||||
|                     Toast.LENGTH_LONG).show() |  | ||||||
|             ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Opens the location access page in settings, for user to turn on location services |  | ||||||
|      * |  | ||||||
|      * @param activity Activtiy object |  | ||||||
|      */ |  | ||||||
|     public void openLocationSettings(Activity activity) { |  | ||||||
|         final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); |  | ||||||
|         final PackageManager packageManager = activity.getPackageManager(); |  | ||||||
| 
 |  | ||||||
|         if (intent.resolveActivity(packageManager) != null) { |  | ||||||
|             activity.startActivity(intent); |  | ||||||
|         } else { |  | ||||||
|             Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG) |  | ||||||
|                 .show(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Shows a dialog for user to open the app's settings page and give location permission |  | ||||||
|      * |  | ||||||
|      * @param activity Activity object |  | ||||||
|      * @param dialogTextResource int id of the required string resource |  | ||||||
|      */ |  | ||||||
|     public void showAppSettingsDialog(Activity activity, int dialogTextResource) { |  | ||||||
|         DialogUtil |  | ||||||
|             .showAlertDialog(activity, activity.getString(R.string.location_permission_title), |  | ||||||
|                 activity.getString(dialogTextResource), |  | ||||||
|                 activity.getString(R.string.title_app_shortcut_setting), |  | ||||||
|                 activity.getString(R.string.cancel), |  | ||||||
|                 () -> openAppSettings(activity), |  | ||||||
|                 () -> Toast.makeText(activity, activity.getString(dialogTextResource), |  | ||||||
|                     Toast.LENGTH_LONG).show() |  | ||||||
|             ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Opens detailed settings page of the app for the user to turn on location services |  | ||||||
|      * |  | ||||||
|      * @param activity Activity object |  | ||||||
|      */ |  | ||||||
|     public void openAppSettings(Activity activity) { |  | ||||||
|         Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); |  | ||||||
|         Uri uri = Uri.fromParts("package", activity.getPackageName(), null); |  | ||||||
|         intent.setData(uri); |  | ||||||
|         activity.startActivity(intent); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Check if apps have access to location even after having individual access |  | ||||||
|      * |  | ||||||
|      * @return Returns true if location services are on and false otherwise |  | ||||||
|      */ |  | ||||||
|     public boolean isLocationAccessToAppsTurnedOn() { |  | ||||||
|         return (locationManager.isNetworkProviderEnabled() |  | ||||||
|             || locationManager.isGPSProviderEnabled()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Checks if location permission is already granted or not |  | ||||||
|      * |  | ||||||
|      * @param activity Activity object |  | ||||||
|      * @return Returns true if location permission is granted and false otherwise |  | ||||||
|      */ |  | ||||||
|     public boolean checkLocationPermission(Activity activity) { |  | ||||||
|         return PermissionUtils.hasPermission(activity, |  | ||||||
|             new String[]{Manifest.permission.ACCESS_FINE_LOCATION}); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Handle onPermissionDenied within individual classes based on the requirements |  | ||||||
|      */ |  | ||||||
|     public interface LocationPermissionCallback { |  | ||||||
| 
 |  | ||||||
|         void onLocationPermissionDenied(String toastMessage); |  | ||||||
| 
 |  | ||||||
|         void onLocationPermissionGranted(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,200 @@ | ||||||
|  | package fr.free.nrw.commons.location | ||||||
|  | 
 | ||||||
|  | import android.Manifest.permission | ||||||
|  | import android.app.Activity | ||||||
|  | import android.content.Intent | ||||||
|  | import android.net.Uri | ||||||
|  | import android.provider.Settings | ||||||
|  | import android.widget.Toast | ||||||
|  | import androidx.core.app.ActivityCompat | ||||||
|  | import fr.free.nrw.commons.R | ||||||
|  | import fr.free.nrw.commons.filepicker.Constants.RequestCodes | ||||||
|  | import fr.free.nrw.commons.utils.DialogUtil | ||||||
|  | import fr.free.nrw.commons.utils.PermissionUtils | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Helper class to handle location permissions. | ||||||
|  |  * | ||||||
|  |  * Location flow for fragments containing a map is as follows: | ||||||
|  |  * Case 1: When location permission has never been asked for or denied before | ||||||
|  |  * Check if permission is already granted or not. | ||||||
|  |  * If not already granted, ask for it (if it isn't denied twice before). | ||||||
|  |  * If now user grants permission, go to Case 3/4, else go to Case 2. | ||||||
|  |  * | ||||||
|  |  * Case 2: When location permission is just asked but has been denied | ||||||
|  |  * Shows a toast to tell the user why location permission is needed. | ||||||
|  |  * Also shows a rationale to the user, on agreeing to which, we go back to Case 1. | ||||||
|  |  * Show current location / nearby pins / nearby images according to the default location. | ||||||
|  |  * | ||||||
|  |  * Case 3: When location permission are already granted, but location services are off | ||||||
|  |  * Asks the user to turn on the location service, using a dialog. | ||||||
|  |  * If the user rejects, checks for the last known location and shows stuff using that location. | ||||||
|  |  * Also displays a toast telling the user why location should be turned on. | ||||||
|  |  * | ||||||
|  |  * Case 4: When location permission has been granted and location services are also on | ||||||
|  |  * Do whatever is required by that particular activity / fragment using current location. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | class LocationPermissionsHelper( | ||||||
|  |     private val activity: Activity, | ||||||
|  |     private val locationManager: LocationServiceManager, | ||||||
|  |     private val callback: LocationPermissionCallback? | ||||||
|  | ) { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Ask for location permission if the user agrees on attaching location with pictures and the | ||||||
|  |      * app does not have the access to location | ||||||
|  |      * | ||||||
|  |      * @param dialogTitleResource Resource id of the title of the dialog | ||||||
|  |      * @param dialogTextResource Resource id of the text of the dialog | ||||||
|  |      */ | ||||||
|  |     fun requestForLocationAccess( | ||||||
|  |         dialogTitleResource: Int, | ||||||
|  |         dialogTextResource: Int | ||||||
|  |     ) { | ||||||
|  |         if (checkLocationPermission(activity)) { | ||||||
|  |             callback?.onLocationPermissionGranted() | ||||||
|  |         } else { | ||||||
|  |             if (ActivityCompat.shouldShowRequestPermissionRationale( | ||||||
|  |                     activity, | ||||||
|  |                     permission.ACCESS_FINE_LOCATION | ||||||
|  |                 ) | ||||||
|  |             ) { | ||||||
|  |                 DialogUtil.showAlertDialog( | ||||||
|  |                     activity, | ||||||
|  |                     activity.getString(dialogTitleResource), | ||||||
|  |                     activity.getString(dialogTextResource), | ||||||
|  |                     activity.getString(android.R.string.ok), | ||||||
|  |                     activity.getString(android.R.string.cancel), | ||||||
|  |                     { | ||||||
|  |                         ActivityCompat.requestPermissions( | ||||||
|  |                             activity, | ||||||
|  |                             arrayOf(permission.ACCESS_FINE_LOCATION), | ||||||
|  |                             1 | ||||||
|  |                         ) | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         callback?.onLocationPermissionDenied( | ||||||
|  |                             activity.getString(R.string.upload_map_location_access) | ||||||
|  |                         ) | ||||||
|  |                     }, | ||||||
|  |                     null, | ||||||
|  |                     false | ||||||
|  |                 ) | ||||||
|  |             } else { | ||||||
|  |                 ActivityCompat.requestPermissions( | ||||||
|  |                     activity, | ||||||
|  |                     arrayOf(permission.ACCESS_FINE_LOCATION), | ||||||
|  |                     RequestCodes.LOCATION | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Shows a dialog for user to open the settings page and turn on location services | ||||||
|  |      * | ||||||
|  |      * @param activity Activity object | ||||||
|  |      * @param dialogTextResource int id of the required string resource | ||||||
|  |      */ | ||||||
|  |     fun showLocationOffDialog(activity: Activity, dialogTextResource: Int) { | ||||||
|  |         DialogUtil.showAlertDialog( | ||||||
|  |             activity, | ||||||
|  |             activity.getString(R.string.ask_to_turn_location_on), | ||||||
|  |             activity.getString(dialogTextResource), | ||||||
|  |             activity.getString(R.string.title_app_shortcut_setting), | ||||||
|  |             activity.getString(R.string.cancel), | ||||||
|  |             { openLocationSettings(activity) }, | ||||||
|  |             { | ||||||
|  |                 Toast.makeText( | ||||||
|  |                     activity, | ||||||
|  |                     activity.getString(dialogTextResource), | ||||||
|  |                     Toast.LENGTH_LONG | ||||||
|  |                 ).show() | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Opens the location access page in settings, for user to turn on location services | ||||||
|  |      * | ||||||
|  |      * @param activity Activity object | ||||||
|  |      */ | ||||||
|  |     fun openLocationSettings(activity: Activity) { | ||||||
|  |         val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) | ||||||
|  |         val packageManager = activity.packageManager | ||||||
|  | 
 | ||||||
|  |         if (intent.resolveActivity(packageManager) != null) { | ||||||
|  |             activity.startActivity(intent) | ||||||
|  |         } else { | ||||||
|  |             Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG) | ||||||
|  |                 .show() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Shows a dialog for user to open the app's settings page and give location permission | ||||||
|  |      * | ||||||
|  |      * @param activity Activity object | ||||||
|  |      * @param dialogTextResource int id of the required string resource | ||||||
|  |      */ | ||||||
|  |     fun showAppSettingsDialog(activity: Activity, dialogTextResource: Int) { | ||||||
|  |         DialogUtil.showAlertDialog( | ||||||
|  |             activity, | ||||||
|  |             activity.getString(R.string.location_permission_title), | ||||||
|  |             activity.getString(dialogTextResource), | ||||||
|  |             activity.getString(R.string.title_app_shortcut_setting), | ||||||
|  |             activity.getString(R.string.cancel), | ||||||
|  |             { openAppSettings(activity) }, | ||||||
|  |             { | ||||||
|  |                 Toast.makeText( | ||||||
|  |                     activity, | ||||||
|  |                     activity.getString(dialogTextResource), | ||||||
|  |                     Toast.LENGTH_LONG | ||||||
|  |                 ).show() | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Opens detailed settings page of the app for the user to turn on location services | ||||||
|  |      * | ||||||
|  |      * @param activity Activity object | ||||||
|  |      */ | ||||||
|  |     private fun openAppSettings(activity: Activity) { | ||||||
|  |         val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) | ||||||
|  |         val uri = Uri.fromParts("package", activity.packageName, null) | ||||||
|  |         intent.data = uri | ||||||
|  |         activity.startActivity(intent) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if apps have access to location even after having individual access | ||||||
|  |      * | ||||||
|  |      * @return Returns true if location services are on and false otherwise | ||||||
|  |      */ | ||||||
|  |     fun isLocationAccessToAppsTurnedOn(): Boolean { | ||||||
|  |         return locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks if location permission is already granted or not | ||||||
|  |      * | ||||||
|  |      * @param activity Activity object | ||||||
|  |      * @return Returns true if location permission is granted and false otherwise | ||||||
|  |      */ | ||||||
|  |     fun checkLocationPermission(activity: Activity): Boolean { | ||||||
|  |         return PermissionUtils.hasPermission( | ||||||
|  |             activity, | ||||||
|  |             arrayOf(permission.ACCESS_FINE_LOCATION) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Handle onPermissionDenied within individual classes based on the requirements | ||||||
|  |      */ | ||||||
|  |     interface LocationPermissionCallback { | ||||||
|  |         fun onLocationPermissionDenied(toastMessage: String) | ||||||
|  |         fun onLocationPermissionGranted() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,274 +0,0 @@ | ||||||
| package fr.free.nrw.commons.location; |  | ||||||
| 
 |  | ||||||
| import android.Manifest.permission; |  | ||||||
| import android.app.Activity; |  | ||||||
| import android.content.Context; |  | ||||||
| import android.content.pm.PackageManager; |  | ||||||
| import android.location.Location; |  | ||||||
| import android.location.LocationListener; |  | ||||||
| import android.location.LocationManager; |  | ||||||
| import android.os.Bundle; |  | ||||||
| import androidx.core.app.ActivityCompat; |  | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Set; |  | ||||||
| import java.util.concurrent.CopyOnWriteArrayList; |  | ||||||
| 
 |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 |  | ||||||
| public class LocationServiceManager implements LocationListener { |  | ||||||
| 
 |  | ||||||
|     // Maybe these values can be improved for efficiency |  | ||||||
|     private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 10 * 100; |  | ||||||
|     private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 1; |  | ||||||
| 
 |  | ||||||
|     private LocationManager locationManager; |  | ||||||
|     private Location lastLocation; |  | ||||||
|     //private Location lastLocationDuplicate; // Will be used for nearby card view on contributions activity |  | ||||||
|     private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>(); |  | ||||||
|     private boolean isLocationManagerRegistered = false; |  | ||||||
|     private Set<Activity> locationExplanationDisplayed = new HashSet<>(); |  | ||||||
|     private Context context; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Constructs a new instance of LocationServiceManager. |  | ||||||
|      * |  | ||||||
|      * @param context the context |  | ||||||
|      */ |  | ||||||
|     public LocationServiceManager(Context context) { |  | ||||||
|         this.context = context; |  | ||||||
|         this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public LatLng getLastLocation() { |  | ||||||
|         if (lastLocation == null) { |  | ||||||
|                 lastLocation = getLastKnownLocation(); |  | ||||||
|                 if(lastLocation != null) { |  | ||||||
|                     return LatLng.from(lastLocation); |  | ||||||
|                 } |  | ||||||
|                 else { |  | ||||||
|                     return null; |  | ||||||
|                 } |  | ||||||
|         } |  | ||||||
|         return LatLng.from(lastLocation); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private Location getLastKnownLocation() { |  | ||||||
|         List<String> providers = locationManager.getProviders(true); |  | ||||||
|         Location bestLocation = null; |  | ||||||
|         for (String provider : providers) { |  | ||||||
|             Location l=null; |  | ||||||
|             if (ActivityCompat.checkSelfPermission(context, permission.ACCESS_FINE_LOCATION) |  | ||||||
|                 == PackageManager.PERMISSION_GRANTED |  | ||||||
|                 && ActivityCompat.checkSelfPermission(context, permission.ACCESS_COARSE_LOCATION) |  | ||||||
|                 == PackageManager.PERMISSION_GRANTED) { |  | ||||||
|                 l = locationManager.getLastKnownLocation(provider); |  | ||||||
|             } |  | ||||||
|             if (l == null) { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|             if (bestLocation == null |  | ||||||
|                 || l.getAccuracy() < bestLocation.getAccuracy()) { |  | ||||||
|                 bestLocation = l; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if (bestLocation == null) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return bestLocation; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Registers a LocationManager to listen for current location. |  | ||||||
|      */ |  | ||||||
|     public void registerLocationManager() { |  | ||||||
|         if (!isLocationManagerRegistered) { |  | ||||||
|             isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) |  | ||||||
|                     && requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Requests location updates from the specified provider. |  | ||||||
|      * |  | ||||||
|      * @param locationProvider the location provider |  | ||||||
|      * @return true if successful |  | ||||||
|      */ |  | ||||||
|     public boolean requestLocationUpdatesFromProvider(String locationProvider) { |  | ||||||
|         try { |  | ||||||
|             // If both providers are not available |  | ||||||
|             if (locationManager == null || !(locationManager.getAllProviders().contains(locationProvider))) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|             locationManager.requestLocationUpdates(locationProvider, |  | ||||||
|                     MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS, |  | ||||||
|                     MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS, |  | ||||||
|                     this); |  | ||||||
|             return true; |  | ||||||
|         } catch (IllegalArgumentException e) { |  | ||||||
|             Timber.e(e, "Illegal argument exception"); |  | ||||||
|             return false; |  | ||||||
|         } catch (SecurityException e) { |  | ||||||
|             Timber.e(e, "Security exception"); |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Returns whether a given location is better than the current best location. |  | ||||||
|      * |  | ||||||
|      * @param location            the location to be tested |  | ||||||
|      * @param currentBestLocation the current best location |  | ||||||
|      * @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly |  | ||||||
|      * LOCATION_SLIGHTLY_CHANGED if location changed slightly |  | ||||||
|      */ |  | ||||||
|     private LocationChangeType isBetterLocation(Location location, Location currentBestLocation) { |  | ||||||
| 
 |  | ||||||
|         if (currentBestLocation == null) { |  | ||||||
|             // A new location is always better than no location |  | ||||||
|             return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Check whether the new location fix is newer or older |  | ||||||
|         long timeDelta = location.getTime() - currentBestLocation.getTime(); |  | ||||||
|         boolean isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS; |  | ||||||
|         boolean isNewer = timeDelta > 0; |  | ||||||
| 
 |  | ||||||
|         // Check whether the new location fix is more or less accurate |  | ||||||
|         int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy()); |  | ||||||
|         boolean isLessAccurate = accuracyDelta > 0; |  | ||||||
|         boolean isMoreAccurate = accuracyDelta < 0; |  | ||||||
|         boolean isSignificantlyLessAccurate = accuracyDelta > 200; |  | ||||||
| 
 |  | ||||||
|         // Check if the old and new location are from the same provider |  | ||||||
|         boolean isFromSameProvider = isSameProvider(location.getProvider(), |  | ||||||
|                 currentBestLocation.getProvider()); |  | ||||||
| 
 |  | ||||||
|         float[] results = new float[5]; |  | ||||||
|         Location.distanceBetween( |  | ||||||
|                         currentBestLocation.getLatitude(), |  | ||||||
|                         currentBestLocation.getLongitude(), |  | ||||||
|                         location.getLatitude(), |  | ||||||
|                         location.getLongitude(), |  | ||||||
|                         results); |  | ||||||
| 
 |  | ||||||
|         // If it's been more than two minutes since the current location, use the new location |  | ||||||
|         // because the user has likely moved |  | ||||||
|         if (isSignificantlyNewer |  | ||||||
|                 || isMoreAccurate |  | ||||||
|                 || (isNewer && !isLessAccurate) |  | ||||||
|                 || (isNewer && !isSignificantlyLessAccurate && isFromSameProvider)) { |  | ||||||
|             if (results[0] < 1000) { // Means change is smaller than 1000 meter |  | ||||||
|                 return LocationChangeType.LOCATION_SLIGHTLY_CHANGED; |  | ||||||
|             } else { |  | ||||||
|                 return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; |  | ||||||
|             } |  | ||||||
|         } else{ |  | ||||||
|             return LocationChangeType.LOCATION_NOT_CHANGED; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Checks whether two providers are the same |  | ||||||
|      */ |  | ||||||
|     private boolean isSameProvider(String provider1, String provider2) { |  | ||||||
|         if (provider1 == null) { |  | ||||||
|             return provider2 == null; |  | ||||||
|         } |  | ||||||
|         return provider1.equals(provider2); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Unregisters location manager. |  | ||||||
|      */ |  | ||||||
|     public void unregisterLocationManager() { |  | ||||||
|         isLocationManagerRegistered = false; |  | ||||||
|         locationExplanationDisplayed.clear(); |  | ||||||
|         try { |  | ||||||
|             locationManager.removeUpdates(this); |  | ||||||
|         } catch (SecurityException e) { |  | ||||||
|             Timber.e(e, "Security exception"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Adds a new listener to the list of location listeners. |  | ||||||
|      * |  | ||||||
|      * @param listener the new listener |  | ||||||
|      */ |  | ||||||
|     public void addLocationListener(LocationUpdateListener listener) { |  | ||||||
|         if (!locationListeners.contains(listener)) { |  | ||||||
|             locationListeners.add(listener); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Removes a listener from the list of location listeners. |  | ||||||
|      * |  | ||||||
|      * @param listener the listener to be removed |  | ||||||
|      */ |  | ||||||
|     public void removeLocationListener(LocationUpdateListener listener) { |  | ||||||
|         locationListeners.remove(listener); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onLocationChanged(Location location) { |  | ||||||
|         Timber.d("on location changed"); |  | ||||||
|             if (isBetterLocation(location, lastLocation) |  | ||||||
|                     .equals(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) { |  | ||||||
|                 lastLocation = location; |  | ||||||
|                 //lastLocationDuplicate = location; |  | ||||||
|                 for (LocationUpdateListener listener : locationListeners) { |  | ||||||
|                     listener.onLocationChangedSignificantly(LatLng.from(lastLocation)); |  | ||||||
|                 } |  | ||||||
|             } else if (location.distanceTo(lastLocation) >= 500) { |  | ||||||
|                 // Update nearby notification card at every 500 meters. |  | ||||||
|                 for (LocationUpdateListener listener : locationListeners) { |  | ||||||
|                     listener.onLocationChangedMedium(LatLng.from(lastLocation)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             else if (isBetterLocation(location, lastLocation) |  | ||||||
|                     .equals(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) { |  | ||||||
|                 lastLocation = location; |  | ||||||
|                 //lastLocationDuplicate = location; |  | ||||||
|                 for (LocationUpdateListener listener : locationListeners) { |  | ||||||
|                     listener.onLocationChangedSlightly(LatLng.from(lastLocation)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onStatusChanged(String provider, int status, Bundle extras) { |  | ||||||
|         Timber.d("%s's status changed to %d", provider, status); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onProviderEnabled(String provider) { |  | ||||||
|         Timber.d("Provider %s enabled", provider); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void onProviderDisabled(String provider) { |  | ||||||
|         Timber.d("Provider %s disabled", provider); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public boolean isNetworkProviderEnabled() { |  | ||||||
|         return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public boolean isGPSProviderEnabled() { |  | ||||||
|         return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public enum LocationChangeType{ |  | ||||||
|         LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers |  | ||||||
|         LOCATION_SLIGHTLY_CHANGED,      //User might be walking or driving |  | ||||||
|         LOCATION_MEDIUM_CHANGED,      //Between slight and significant changes, will be used for nearby card view updates. |  | ||||||
|         LOCATION_NOT_CHANGED, |  | ||||||
|         PERMISSION_JUST_GRANTED, |  | ||||||
|         MAP_UPDATED, |  | ||||||
|         SEARCH_CUSTOM_AREA, |  | ||||||
|         CUSTOM_QUERY |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,255 @@ | ||||||
|  | package fr.free.nrw.commons.location | ||||||
|  | 
 | ||||||
|  | import android.Manifest | ||||||
|  | import android.app.Activity | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.pm.PackageManager | ||||||
|  | import android.location.Location | ||||||
|  | import android.location.LocationListener | ||||||
|  | import android.location.LocationManager | ||||||
|  | import android.os.Bundle | ||||||
|  | import androidx.core.app.ActivityCompat | ||||||
|  | import timber.log.Timber | ||||||
|  | import java.util.concurrent.CopyOnWriteArrayList | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LocationServiceManager(private val context: Context) : LocationListener { | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  |         // Maybe these values can be improved for efficiency | ||||||
|  |         private const val MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 10 * 100L | ||||||
|  |         private const val MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 1f | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private val locationManager: LocationManager = | ||||||
|  |         context.getSystemService(Context.LOCATION_SERVICE) as LocationManager | ||||||
|  |     private var lastLocationVar: Location? = null | ||||||
|  |     private val locationListeners = CopyOnWriteArrayList<LocationUpdateListener>() | ||||||
|  |     private var isLocationManagerRegistered = false | ||||||
|  |     private val locationExplanationDisplayed = mutableSetOf<Activity>() | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Constructs a new instance of LocationServiceManager. | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     fun getLastLocation(): LatLng? { | ||||||
|  |         if (lastLocationVar == null) { | ||||||
|  |             lastLocationVar = getLastKnownLocation() | ||||||
|  |             return lastLocationVar?.let { LatLng.from(it) } | ||||||
|  |         } | ||||||
|  |         return LatLng.from(lastLocationVar!!) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun getLastKnownLocation(): Location? { | ||||||
|  |         val providers = locationManager.getProviders(true) | ||||||
|  |         var bestLocation: Location? = null | ||||||
|  |         for (provider in providers) { | ||||||
|  |             val location: Location? = if ( | ||||||
|  |                 ActivityCompat.checkSelfPermission( | ||||||
|  |                     context, | ||||||
|  |                     Manifest.permission.ACCESS_FINE_LOCATION) | ||||||
|  |                 == | ||||||
|  |                 PackageManager.PERMISSION_GRANTED && | ||||||
|  |                 ActivityCompat.checkSelfPermission( | ||||||
|  |                     context, | ||||||
|  |                     Manifest.permission.ACCESS_COARSE_LOCATION) | ||||||
|  |                 == | ||||||
|  |                 PackageManager.PERMISSION_GRANTED | ||||||
|  |             ) { | ||||||
|  |                 locationManager.getLastKnownLocation(provider) | ||||||
|  |             } else { | ||||||
|  |                 null | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if ( | ||||||
|  |                 location != null | ||||||
|  |                 && | ||||||
|  |                 (bestLocation == null || location.accuracy < bestLocation.accuracy) | ||||||
|  |             ) { | ||||||
|  |                 bestLocation = location | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return bestLocation | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Registers a LocationManager to listen for current location. | ||||||
|  |      */ | ||||||
|  |     fun registerLocationManager() { | ||||||
|  |         if (!isLocationManagerRegistered) { | ||||||
|  |             isLocationManagerRegistered = | ||||||
|  |                 requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) && | ||||||
|  |                     requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Requests location updates from the specified provider. | ||||||
|  |      * | ||||||
|  |      * @param locationProvider the location provider | ||||||
|  |      * @return true if successful | ||||||
|  |      */ | ||||||
|  |     fun requestLocationUpdatesFromProvider(locationProvider: String): Boolean { | ||||||
|  |         return try { | ||||||
|  |             if (locationManager.allProviders.contains(locationProvider)) { | ||||||
|  |                 locationManager.requestLocationUpdates( | ||||||
|  |                     locationProvider, | ||||||
|  |                     MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS, | ||||||
|  |                     MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS, | ||||||
|  |                     this | ||||||
|  |                 ) | ||||||
|  |                 true | ||||||
|  |             } else { | ||||||
|  |                 false | ||||||
|  |             } | ||||||
|  |         } catch (e: IllegalArgumentException) { | ||||||
|  |             Timber.e(e, "Illegal argument exception") | ||||||
|  |             false | ||||||
|  |         } catch (e: SecurityException) { | ||||||
|  |             Timber.e(e, "Security exception") | ||||||
|  |             false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns whether a given location is better than the current best location. | ||||||
|  |      * | ||||||
|  |      * @param location the location to be tested | ||||||
|  |      * @param currentBestLocation the current best location | ||||||
|  |      * @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly | ||||||
|  |      * LOCATION_SLIGHTLY_CHANGED if location changed slightly | ||||||
|  |      */ | ||||||
|  |     private fun isBetterLocation(location: Location, currentBestLocation: Location?): LocationChangeType { | ||||||
|  |         if (currentBestLocation == null) { | ||||||
|  |             return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         val timeDelta = location.time - currentBestLocation.time | ||||||
|  |         val isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS | ||||||
|  |         val isNewer = timeDelta > 0 | ||||||
|  |         val accuracyDelta = (location.accuracy - currentBestLocation.accuracy).toInt() | ||||||
|  |         val isMoreAccurate = accuracyDelta < 0 | ||||||
|  |         val isSignificantlyLessAccurate = accuracyDelta > 200 | ||||||
|  |         val isFromSameProvider = isSameProvider(location.provider, currentBestLocation.provider) | ||||||
|  | 
 | ||||||
|  |         val results = FloatArray(5) | ||||||
|  |         Location.distanceBetween( | ||||||
|  |             currentBestLocation.latitude, currentBestLocation.longitude, | ||||||
|  |             location.latitude, location.longitude, | ||||||
|  |             results | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         return when { | ||||||
|  |             isSignificantlyNewer | ||||||
|  |                     || | ||||||
|  |                     isMoreAccurate | ||||||
|  |                     || | ||||||
|  |                     (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) -> { | ||||||
|  |                 if (results[0] < 1000) LocationChangeType.LOCATION_SLIGHTLY_CHANGED | ||||||
|  |                 else LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED | ||||||
|  |             } | ||||||
|  |             else -> LocationChangeType.LOCATION_NOT_CHANGED | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Checks whether two providers are the same | ||||||
|  |      */ | ||||||
|  |     private fun isSameProvider(provider1: String?, provider2: String?): Boolean { | ||||||
|  |         return provider1 == provider2 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Unregisters location manager. | ||||||
|  |      */ | ||||||
|  |     fun unregisterLocationManager() { | ||||||
|  |         isLocationManagerRegistered = false | ||||||
|  |         locationExplanationDisplayed.clear() | ||||||
|  |         try { | ||||||
|  |             locationManager.removeUpdates(this) | ||||||
|  |         } catch (e: SecurityException) { | ||||||
|  |             Timber.e(e, "Security exception") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds a new listener to the list of location listeners. | ||||||
|  |      * | ||||||
|  |      * @param listener the new listener | ||||||
|  |      */ | ||||||
|  |     fun addLocationListener(listener: LocationUpdateListener) { | ||||||
|  |         if (!locationListeners.contains(listener)) { | ||||||
|  |             locationListeners.add(listener) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Removes a listener from the list of location listeners. | ||||||
|  |      * | ||||||
|  |      * @param listener the listener to be removed | ||||||
|  |      */ | ||||||
|  |     fun removeLocationListener(listener: LocationUpdateListener) { | ||||||
|  |         locationListeners.remove(listener) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onLocationChanged(location: Location) { | ||||||
|  |         Timber.d("on location changed") | ||||||
|  |         val changeType = isBetterLocation(location, lastLocationVar) | ||||||
|  |         if (changeType == LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) { | ||||||
|  |             lastLocationVar = location | ||||||
|  |             locationListeners.forEach { it.onLocationChangedSignificantly(LatLng.from(location)) } | ||||||
|  |         } else if (lastLocationVar?.let { location.distanceTo(it) }!! >= 500) { | ||||||
|  |             locationListeners.forEach { it.onLocationChangedMedium(LatLng.from(location)) } | ||||||
|  |         } else if (changeType == LocationChangeType.LOCATION_SLIGHTLY_CHANGED) { | ||||||
|  |             lastLocationVar = location | ||||||
|  |             locationListeners.forEach { it.onLocationChangedSlightly(LatLng.from(location)) } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Deprecated("Deprecated in Java", ReplaceWith( | ||||||
|  |         "Timber.d(\"%s's status changed to %d\", provider, status)", | ||||||
|  |         "timber.log.Timber" | ||||||
|  |     ) | ||||||
|  |     ) | ||||||
|  |     override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { | ||||||
|  |         Timber.d("%s's status changed to %d", provider, status) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     override fun onProviderEnabled(provider: String) { | ||||||
|  |         Timber.d("Provider %s enabled", provider) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun onProviderDisabled(provider: String) { | ||||||
|  |         Timber.d("Provider %s disabled", provider) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun isNetworkProviderEnabled(): Boolean { | ||||||
|  |         return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fun isGPSProviderEnabled(): Boolean { | ||||||
|  |         return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     enum class LocationChangeType { | ||||||
|  |         LOCATION_SIGNIFICANTLY_CHANGED, | ||||||
|  |         LOCATION_SLIGHTLY_CHANGED, | ||||||
|  |         LOCATION_MEDIUM_CHANGED, | ||||||
|  |         LOCATION_NOT_CHANGED, | ||||||
|  |         PERMISSION_JUST_GRANTED, | ||||||
|  |         MAP_UPDATED, | ||||||
|  |         SEARCH_CUSTOM_AREA, | ||||||
|  |         CUSTOM_QUERY | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -1,7 +0,0 @@ | ||||||
| package fr.free.nrw.commons.location; |  | ||||||
| 
 |  | ||||||
| public interface LocationUpdateListener { |  | ||||||
|     void onLocationChangedSignificantly(LatLng latLng); // Will be used to update all nearby markers on the map |  | ||||||
|     void onLocationChangedSlightly(LatLng latLng); // Will be used to track users motion |  | ||||||
|     void onLocationChangedMedium(LatLng latLng); // Will be used updating nearby card view notification |  | ||||||
| } |  | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | package fr.free.nrw.commons.location | ||||||
|  | 
 | ||||||
|  | interface LocationUpdateListener { | ||||||
|  |     // Will be used to update all nearby markers on the map | ||||||
|  |     fun onLocationChangedSignificantly(latLng: LatLng) | ||||||
|  | 
 | ||||||
|  |     // Will be used to track users motion | ||||||
|  |     fun onLocationChangedSlightly(latLng: LatLng) | ||||||
|  | 
 | ||||||
|  |     // Will be used updating nearby card view notification | ||||||
|  |     fun onLocationChangedMedium(latLng: LatLng) | ||||||
|  | } | ||||||
|  | @ -3,7 +3,10 @@ package fr.free.nrw.commons.mwapi; | ||||||
| import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; | import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
|  | import fr.free.nrw.commons.BuildConfig; | ||||||
| import fr.free.nrw.commons.category.CategoryItem; | import fr.free.nrw.commons.category.CategoryItem; | ||||||
|  | import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage; | ||||||
|  | import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse; | ||||||
| import io.reactivex.Single; | import io.reactivex.Single; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | @ -11,14 +14,11 @@ import java.util.LinkedHashSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| import javax.inject.Named; |  | ||||||
| import okhttp3.HttpUrl; | import okhttp3.HttpUrl; | ||||||
| import okhttp3.OkHttpClient; | import okhttp3.OkHttpClient; | ||||||
| import okhttp3.Request; | import okhttp3.Request; | ||||||
| import okhttp3.Response; | import okhttp3.Response; | ||||||
| import okhttp3.ResponseBody; | import okhttp3.ResponseBody; | ||||||
| import fr.free.nrw.commons.wikidata.mwapi.MwQueryPage; |  | ||||||
| import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse; |  | ||||||
| import timber.log.Timber; | import timber.log.Timber; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -30,14 +30,11 @@ import timber.log.Timber; | ||||||
| public class CategoryApi { | public class CategoryApi { | ||||||
| 
 | 
 | ||||||
|     private final OkHttpClient okHttpClient; |     private final OkHttpClient okHttpClient; | ||||||
|     private final String commonsBaseUrl; |  | ||||||
|     private final Gson gson; |     private final Gson gson; | ||||||
| 
 | 
 | ||||||
|     @Inject |     @Inject | ||||||
|     public CategoryApi(OkHttpClient okHttpClient, Gson gson, |     public CategoryApi(final OkHttpClient okHttpClient, final Gson gson) { | ||||||
|                        @Named("wikimedia_api_host") String commonsBaseUrl) { |  | ||||||
|         this.okHttpClient = okHttpClient; |         this.okHttpClient = okHttpClient; | ||||||
|         this.commonsBaseUrl = commonsBaseUrl; |  | ||||||
|         this.gson = gson; |         this.gson = gson; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -75,9 +72,9 @@ public class CategoryApi { | ||||||
|      * @param coords Coordinates to build query with |      * @param coords Coordinates to build query with | ||||||
|      * @return URL for API query |      * @return URL for API query | ||||||
|      */ |      */ | ||||||
|     private HttpUrl buildUrl(String coords) { |     private HttpUrl buildUrl(final String coords) { | ||||||
|         return HttpUrl |         return HttpUrl | ||||||
|                 .parse(commonsBaseUrl) |                 .parse(BuildConfig.WIKIMEDIA_API_HOST) | ||||||
|                 .newBuilder() |                 .newBuilder() | ||||||
|                 .addQueryParameter("action", "query") |                 .addQueryParameter("action", "query") | ||||||
|                 .addQueryParameter("prop", "categories|coordinates|pageprops") |                 .addQueryParameter("prop", "categories|coordinates|pageprops") | ||||||
|  |  | ||||||
|  | @ -42,8 +42,8 @@ class LanguagesAdapter constructor( | ||||||
|         AppLanguageLookUpTable(context) |         AppLanguageLookUpTable(context) | ||||||
| 
 | 
 | ||||||
|     init { |     init { | ||||||
|         languageNamesList = language.localizedNames |         languageNamesList = language.getLocalizedNames() | ||||||
|         languageCodesList = language.codes |         languageCodesList = language.getCodes() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private val filter = LanguageFilter() |     private val filter = LanguageFilter() | ||||||
|  | @ -117,7 +117,7 @@ class LanguagesAdapter constructor( | ||||||
|      */ |      */ | ||||||
|     fun getIndexOfUserDefaultLocale(context: Context): Int { |     fun getIndexOfUserDefaultLocale(context: Context): Int { | ||||||
|         val userLanguageCode = context.locale?.language ?: return DEFAULT_INDEX |         val userLanguageCode = context.locale?.language ?: return DEFAULT_INDEX | ||||||
|         return language.codes.indexOf(userLanguageCode).takeIf { it >= 0 } ?: DEFAULT_INDEX |         return language.getCodes().indexOf(userLanguageCode).takeIf { it >= 0 } ?: DEFAULT_INDEX | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fun getIndexOfLanguageCode(languageCode: String): Int = languageCodesList.indexOf(languageCode) |     fun getIndexOfLanguageCode(languageCode: String): Int = languageCodesList.indexOf(languageCode) | ||||||
|  | @ -128,17 +128,17 @@ class LanguagesAdapter constructor( | ||||||
|         override fun performFiltering(constraint: CharSequence?): FilterResults { |         override fun performFiltering(constraint: CharSequence?): FilterResults { | ||||||
|             val filterResults = FilterResults() |             val filterResults = FilterResults() | ||||||
|             val temp: LinkedHashMap<String, String> = LinkedHashMap() |             val temp: LinkedHashMap<String, String> = LinkedHashMap() | ||||||
|             if (constraint != null && language.localizedNames != null) { |             if (constraint != null) { | ||||||
|                 val length: Int = language.localizedNames.size |                 val length: Int = language.getLocalizedNames().size | ||||||
|                 var i = 0 |                 var i = 0 | ||||||
|                 while (i < length) { |                 while (i < length) { | ||||||
|                     val key: String = language.codes[i] |                     val key: String = language.getCodes()[i] | ||||||
|                     val value: String = language.localizedNames[i] |                     val value: String = language.getLocalizedNames()[i] | ||||||
|                     val defaultlanguagecode = getIndexOfUserDefaultLocale(context) |                     val defaultlanguagecode = getIndexOfUserDefaultLocale(context) | ||||||
|                     if (value.contains(constraint, true) || |                     if (value.contains(constraint, true) || | ||||||
|                         Locale(key) |                         Locale(key) | ||||||
|                             .getDisplayName( |                             .getDisplayName( | ||||||
|                                 Locale(language.codes[defaultlanguagecode]), |                                 Locale(language.getCodes()[defaultlanguagecode]), | ||||||
|                             ).contains(constraint, true) |                             ).contains(constraint, true) | ||||||
|                     ) { |                     ) { | ||||||
|                         temp[key] = value |                         temp[key] = value | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package fr.free.nrw.commons.upload; | package fr.free.nrw.commons.upload; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; | ||||||
| import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; | import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; | ||||||
| 
 | 
 | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  | @ -53,7 +54,7 @@ public class PendingUploadsPresenter implements UserActionListener { | ||||||
|         final ContributionsRemoteDataSource contributionsRemoteDataSource, |         final ContributionsRemoteDataSource contributionsRemoteDataSource, | ||||||
|         final ContributionsRepository contributionsRepository, |         final ContributionsRepository contributionsRepository, | ||||||
|         final UploadRepository uploadRepository, |         final UploadRepository uploadRepository, | ||||||
|         @Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) { |         @Named(IO_THREAD) final Scheduler ioThreadScheduler) { | ||||||
|         this.contributionBoundaryCallback = contributionBoundaryCallback; |         this.contributionBoundaryCallback = contributionBoundaryCallback; | ||||||
|         this.contributionsRepository = contributionsRepository; |         this.contributionsRepository = contributionsRepository; | ||||||
|         this.uploadRepository = uploadRepository; |         this.uploadRepository = uploadRepository; | ||||||
|  |  | ||||||
|  | @ -9,6 +9,8 @@ import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException | ||||||
| import fr.free.nrw.commons.category.CategoryEditHelper | import fr.free.nrw.commons.category.CategoryEditHelper | ||||||
| import fr.free.nrw.commons.category.CategoryItem | import fr.free.nrw.commons.category.CategoryItem | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule | import fr.free.nrw.commons.di.CommonsApplicationModule | ||||||
|  | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD | ||||||
|  | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.MAIN_THREAD | ||||||
| import fr.free.nrw.commons.repository.UploadRepository | import fr.free.nrw.commons.repository.UploadRepository | ||||||
| import fr.free.nrw.commons.upload.depicts.proxy | import fr.free.nrw.commons.upload.depicts.proxy | ||||||
| import io.reactivex.Observable | import io.reactivex.Observable | ||||||
|  | @ -30,8 +32,8 @@ class CategoriesPresenter | ||||||
|     @Inject |     @Inject | ||||||
|     constructor( |     constructor( | ||||||
|         private val repository: UploadRepository, |         private val repository: UploadRepository, | ||||||
|         @param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler, |         @param:Named(IO_THREAD) private val ioScheduler: Scheduler, | ||||||
|         @param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler, |         @param:Named(MAIN_THREAD) private val mainThreadScheduler: Scheduler, | ||||||
|     ) : CategoriesContract.UserActionListener { |     ) : CategoriesContract.UserActionListener { | ||||||
|         companion object { |         companion object { | ||||||
|             private val DUMMY: CategoriesContract.View = proxy() |             private val DUMMY: CategoriesContract.View = proxy() | ||||||
|  |  | ||||||
|  | @ -7,6 +7,8 @@ import fr.free.nrw.commons.Media | ||||||
| import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException | import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException | ||||||
| import fr.free.nrw.commons.bookmarks.items.BookmarkItemsController | import fr.free.nrw.commons.bookmarks.items.BookmarkItemsController | ||||||
| import fr.free.nrw.commons.di.CommonsApplicationModule | import fr.free.nrw.commons.di.CommonsApplicationModule | ||||||
|  | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD | ||||||
|  | import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.MAIN_THREAD | ||||||
| import fr.free.nrw.commons.repository.UploadRepository | import fr.free.nrw.commons.repository.UploadRepository | ||||||
| import fr.free.nrw.commons.upload.structure.depictions.DepictedItem | import fr.free.nrw.commons.upload.structure.depictions.DepictedItem | ||||||
| import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems | import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems | ||||||
|  | @ -31,8 +33,8 @@ class DepictsPresenter | ||||||
|     @Inject |     @Inject | ||||||
|     constructor( |     constructor( | ||||||
|         private val repository: UploadRepository, |         private val repository: UploadRepository, | ||||||
|         @param:Named(CommonsApplicationModule.IO_THREAD) private val ioScheduler: Scheduler, |         @param:Named(IO_THREAD) private val ioScheduler: Scheduler, | ||||||
|         @param:Named(CommonsApplicationModule.MAIN_THREAD) private val mainThreadScheduler: Scheduler, |         @param:Named(MAIN_THREAD) private val mainThreadScheduler: Scheduler, | ||||||
|     ) : DepictsContract.UserActionListener { |     ) : DepictsContract.UserActionListener { | ||||||
|         companion object { |         companion object { | ||||||
|             private val DUMMY = proxy<DepictsContract.View>() |             private val DUMMY = proxy<DepictsContract.View>() | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ import retrofit2.converter.gson.GsonConverterFactory | ||||||
| class CommonsServiceFactory( | class CommonsServiceFactory( | ||||||
|     private val okHttpClient: OkHttpClient, |     private val okHttpClient: OkHttpClient, | ||||||
| ) { | ) { | ||||||
|     private val builder: Retrofit.Builder by lazy { |     val builder: Retrofit.Builder by lazy { | ||||||
|         // All instances of retrofit share this configuration, but create it lazily |         // All instances of retrofit share this configuration, but create it lazily | ||||||
|         Retrofit |         Retrofit | ||||||
|             .Builder() |             .Builder() | ||||||
|  | @ -17,15 +17,11 @@ class CommonsServiceFactory( | ||||||
|             .addConverterFactory(GsonConverterFactory.create(GsonUtil.getDefaultGson())) |             .addConverterFactory(GsonConverterFactory.create(GsonUtil.getDefaultGson())) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private val retrofitCache: MutableMap<String, Retrofit> = mutableMapOf() |     val retrofitCache: MutableMap<String, Retrofit> = mutableMapOf() | ||||||
| 
 | 
 | ||||||
|     fun <T : Any> create( |     inline fun <reified T: Any> create(baseUrl: String): T = | ||||||
|         baseUrl: String, |         retrofitCache.getOrPut(baseUrl) { | ||||||
|         service: Class<T>, |  | ||||||
|     ): T = |  | ||||||
|         retrofitCache |  | ||||||
|             .getOrPut(baseUrl) { |  | ||||||
|             // Cache instances of retrofit based on API backend |             // Cache instances of retrofit based on API backend | ||||||
|             builder.baseUrl(baseUrl).build() |             builder.baseUrl(baseUrl).build() | ||||||
|             }.create(service) |         }.create(T::class.java) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -62,5 +62,5 @@ class LatLngTests { | ||||||
|     private fun assertPrettyCoordinateString( |     private fun assertPrettyCoordinateString( | ||||||
|         expected: String, |         expected: String, | ||||||
|         place: LatLng, |         place: LatLng, | ||||||
|     ) = assertEquals(expected, place.prettyCoordinateString) |     ) = assertEquals(expected, place.getPrettyCoordinateString()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -37,9 +37,8 @@ class TestCommonsApplication : Application() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @Suppress("MemberVisibilityCanBePrivate") | @Suppress("MemberVisibilityCanBePrivate") | ||||||
| class MockCommonsApplicationModule( | class MockCommonsApplicationModule(appContext: Context) : CommonsApplicationModule(appContext) { | ||||||
|     appContext: Context, | 
 | ||||||
| ) : CommonsApplicationModule(appContext) { |  | ||||||
|     val defaultSharedPreferences: JsonKvStore = mock() |     val defaultSharedPreferences: JsonKvStore = mock() | ||||||
|     val locationServiceManager: LocationServiceManager = mock() |     val locationServiceManager: LocationServiceManager = mock() | ||||||
|     val mockDbOpenHelper: DBOpenHelper = mock() |     val mockDbOpenHelper: DBOpenHelper = mock() | ||||||
|  | @ -50,16 +49,13 @@ class MockCommonsApplicationModule( | ||||||
|     val modificationClient: ContentProviderClient = mock() |     val modificationClient: ContentProviderClient = mock() | ||||||
|     val uploadPrefs: JsonKvStore = mock() |     val uploadPrefs: JsonKvStore = mock() | ||||||
| 
 | 
 | ||||||
|     override fun provideCategoryContentProviderClient(context: Context?): ContentProviderClient = categoryClient |     override fun provideCategoryContentProviderClient(context: Context): ContentProviderClient = categoryClient | ||||||
| 
 | 
 | ||||||
|     override fun provideContributionContentProviderClient(context: Context?): ContentProviderClient = contributionClient |     override fun provideContributionContentProviderClient(context: Context): ContentProviderClient = contributionClient | ||||||
| 
 | 
 | ||||||
|     override fun provideModificationContentProviderClient(context: Context?): ContentProviderClient = modificationClient |     override fun provideModificationContentProviderClient(context: Context): ContentProviderClient = modificationClient | ||||||
| 
 | 
 | ||||||
|     override fun providesDefaultKvStore( |     override fun providesDefaultKvStore(context: Context, gson: Gson): JsonKvStore = defaultSharedPreferences | ||||||
|         context: Context, |  | ||||||
|         gson: Gson, |  | ||||||
|     ): JsonKvStore = defaultSharedPreferences |  | ||||||
| 
 | 
 | ||||||
|     override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager |     override fun provideLocationServiceManager(context: Context): LocationServiceManager = locationServiceManager | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -248,11 +248,11 @@ class MediaDetailFragmentUnitTests { | ||||||
|     @Throws(Exception::class) |     @Throws(Exception::class) | ||||||
|     fun testOnUpdateCoordinatesClickedCurrentLocationNull() { |     fun testOnUpdateCoordinatesClickedCurrentLocationNull() { | ||||||
|         `when`(media.coordinates).thenReturn(null) |         `when`(media.coordinates).thenReturn(null) | ||||||
|         `when`(locationManager.lastLocation).thenReturn(null) |         `when`(locationManager.getLastLocation()).thenReturn(null) | ||||||
|         `when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297") |         `when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297") | ||||||
|         fragment.onUpdateCoordinatesClicked() |         fragment.onUpdateCoordinatesClicked() | ||||||
|         Mockito.verify(media, Mockito.times(1)).coordinates |         Mockito.verify(media, Mockito.times(1)).coordinates | ||||||
|         Mockito.verify(locationManager, Mockito.times(1)).lastLocation |         Mockito.verify(locationManager, Mockito.times(1)).getLastLocation() | ||||||
|         val shadowActivity: ShadowActivity = shadowOf(activity) |         val shadowActivity: ShadowActivity = shadowOf(activity) | ||||||
|         val startedIntent = shadowActivity.nextStartedActivity |         val startedIntent = shadowActivity.nextStartedActivity | ||||||
|         val shadowIntent: ShadowIntent = shadowOf(startedIntent) |         val shadowIntent: ShadowIntent = shadowOf(startedIntent) | ||||||
|  | @ -276,11 +276,11 @@ class MediaDetailFragmentUnitTests { | ||||||
|     @Throws(Exception::class) |     @Throws(Exception::class) | ||||||
|     fun testOnUpdateCoordinatesClickedCurrentLocationNotNull() { |     fun testOnUpdateCoordinatesClickedCurrentLocationNotNull() { | ||||||
|         `when`(media.coordinates).thenReturn(null) |         `when`(media.coordinates).thenReturn(null) | ||||||
|         `when`(locationManager.lastLocation).thenReturn(LatLng(-0.000001, -0.999999, 0f)) |         `when`(locationManager.getLastLocation()).thenReturn(LatLng(-0.000001, -0.999999, 0f)) | ||||||
|         `when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297") |         `when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297") | ||||||
| 
 | 
 | ||||||
|         fragment.onUpdateCoordinatesClicked() |         fragment.onUpdateCoordinatesClicked() | ||||||
|         Mockito.verify(locationManager, Mockito.times(3)).lastLocation |         Mockito.verify(locationManager, Mockito.times(3)).getLastLocation() | ||||||
|         val shadowActivity: ShadowActivity = shadowOf(activity) |         val shadowActivity: ShadowActivity = shadowOf(activity) | ||||||
|         val startedIntent = shadowActivity.nextStartedActivity |         val startedIntent = shadowActivity.nextStartedActivity | ||||||
|         val shadowIntent: ShadowIntent = shadowOf(startedIntent) |         val shadowIntent: ShadowIntent = shadowOf(startedIntent) | ||||||
|  |  | ||||||
|  | @ -54,8 +54,8 @@ class LanguagesAdapterTest { | ||||||
|                 .from(context) |                 .from(context) | ||||||
|                 .inflate(R.layout.row_item_languages_spinner, null) as View |                 .inflate(R.layout.row_item_languages_spinner, null) as View | ||||||
| 
 | 
 | ||||||
|         languageNamesList = language.localizedNames |         languageNamesList = language.getLocalizedNames() | ||||||
|         languageCodesList = language.codes |         languageCodesList = language.getCodes() | ||||||
| 
 | 
 | ||||||
|         languagesAdapter = LanguagesAdapter(context, selectedLanguages) |         languagesAdapter = LanguagesAdapter(context, selectedLanguages) | ||||||
|     } |     } | ||||||
|  | @ -124,12 +124,12 @@ class LanguagesAdapterTest { | ||||||
|         var i = 0 |         var i = 0 | ||||||
|         var s = 0 |         var s = 0 | ||||||
|         while (i < length) { |         while (i < length) { | ||||||
|             val key: String = language.codes[i] |             val key: String = language.getCodes()[i] | ||||||
|             val value: String = language.localizedNames[i] |             val value: String = language.getLocalizedNames()[i] | ||||||
|             if (value.contains(constraint, true) || |             if (value.contains(constraint, true) || | ||||||
|                 Locale(key) |                 Locale(key) | ||||||
|                     .getDisplayName( |                     .getDisplayName( | ||||||
|                         Locale(language.codes[defaultlanguagecode!!]), |                         Locale(language.getCodes()[defaultlanguagecode!!]), | ||||||
|                     ).contains(constraint, true) |                     ).contains(constraint, true) | ||||||
|             ) { |             ) { | ||||||
|                 s++ |                 s++ | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Nicolas Raoul
						Nicolas Raoul