diff --git a/app/src/main/java/rs/chir/cv/ImageProxyLuminanceSource.java b/app/src/main/java/rs/chir/cv/ImageProxyLuminanceSource.java index 333cde5..14f73f7 100644 --- a/app/src/main/java/rs/chir/cv/ImageProxyLuminanceSource.java +++ b/app/src/main/java/rs/chir/cv/ImageProxyLuminanceSource.java @@ -51,6 +51,7 @@ public class ImageProxyLuminanceSource extends LuminanceSource { /** * Converts an RGB value into a luminance value. + * * @param r The red channel in the range [0, 1]. * @param g The green channel in the range [0, 1]. * @param b The blue channel in the range [0, 1]. @@ -62,6 +63,7 @@ public class ImageProxyLuminanceSource extends LuminanceSource { /** * Converts an RGB value into a luminance value. + * * @param r The red channel in the range [0, 255]. * @param g The green channel in the range [0, 255]. * @param b The blue channel in the range [0, 255]. @@ -76,6 +78,7 @@ public class ImageProxyLuminanceSource extends LuminanceSource { /** * Retrieves a plane proxy from the image proxy. + * * @param planeIndex the plane index to retrieve. * @return the plane proxy. */ @@ -85,9 +88,10 @@ public class ImageProxyLuminanceSource extends LuminanceSource { /** * Retrieves a single pixel on a plane from the image. + * * @param planeIndex the plane index to retrieve the pixel from. - * @param x the x coordinate of the pixel. - * @param y the y coordinate of the pixel. + * @param x the x coordinate of the pixel. + * @param y the y coordinate of the pixel. * @return the pixel. */ private byte getOnPlane(int planeIndex, int x, int y) { @@ -101,6 +105,7 @@ public class ImageProxyLuminanceSource extends LuminanceSource { /** * Retrieves the luminance value of a single pixel from the image. + * * @param x the x coordinate of the pixel. * @param y the y coordinate of the pixel. * @return the pixel. diff --git a/app/src/main/java/rs/chir/cv/QRAnalyzer.java b/app/src/main/java/rs/chir/cv/QRAnalyzer.java index 536d9ff..dc526a8 100644 --- a/app/src/main/java/rs/chir/cv/QRAnalyzer.java +++ b/app/src/main/java/rs/chir/cv/QRAnalyzer.java @@ -13,7 +13,7 @@ import rs.chir.compat.java.util.function.Function; /** * {@link ImageAnalysis.Analyzer} for QR codes. - * + *

* When a QR code is found, the analyzer will call the {@link QRAnalyzerListener} callback. * * @author Morten Delenk @@ -31,6 +31,7 @@ public class QRAnalyzer implements ImageAnalysis.Analyzer { /** * Creates a new QR analyzer. + * * @param listener The listener to call when a QR code is found. */ public QRAnalyzer(@NonNull QRAnalyzerListener listener) { @@ -52,6 +53,7 @@ public class QRAnalyzer implements ImageAnalysis.Analyzer { /** * Analyzes the given {@link ImageProxy}. + * * @param image The image to analyze. */ @Override @@ -67,6 +69,7 @@ public class QRAnalyzer implements ImageAnalysis.Analyzer { public interface QRAnalyzerListener { /** * Called when a QR code is found. + * * @param qrCode The contents of the QR code. */ void onQRFound(@NonNull String qrCode); diff --git a/app/src/main/java/rs/chir/cv/package-info.java b/app/src/main/java/rs/chir/cv/package-info.java index 79b970c..2485cb6 100644 --- a/app/src/main/java/rs/chir/cv/package-info.java +++ b/app/src/main/java/rs/chir/cv/package-info.java @@ -1,13 +1,11 @@ /** * Module for computer vision on Android. - * + *

* Currently supports: * *

- * - * */ package rs.chir.cv; \ No newline at end of file diff --git a/app/src/main/java/rs/chir/invtracker/api/CachedClientImpl.java b/app/src/main/java/rs/chir/invtracker/api/CachedClientImpl.java index 0c6652b..e60ce28 100644 --- a/app/src/main/java/rs/chir/invtracker/api/CachedClientImpl.java +++ b/app/src/main/java/rs/chir/invtracker/api/CachedClientImpl.java @@ -36,11 +36,13 @@ public class CachedClientImpl extends Client { private final Context context; /** * Cached objects. + * * @see TrackedItem */ private final Map objects = new java.util.HashMap<>(10); /** * Cached public key. + * * @see PublicKey */ private Optional publicKey = Optional.empty(); @@ -51,8 +53,9 @@ public class CachedClientImpl extends Client { /** * Creates a new cached client. + * * @param context the context used to retrieve network connectivity information - * @param client the underlying client + * @param client the underlying client */ public CachedClientImpl(@NonNull Context context, @NonNull Client client) { this.client = client; @@ -61,6 +64,7 @@ public class CachedClientImpl extends Client { /** * Returns whether aggressive caching is desired. + * * @return true if network connectivity is limited */ private boolean aggressivelyCache() { @@ -96,6 +100,7 @@ public class CachedClientImpl extends Client { /** * Caches the given object. + * * @param trackedItem the object to cache */ private void cacheObject(TrackedItem trackedItem) { @@ -148,7 +153,7 @@ public class CachedClientImpl extends Client { }); } Optional next = Optional.empty(); - if(items.size() != 0) { + if (items.size() != 0) { next = Optional.of(String.valueOf(items.get(items.size() - 1).id())); } return Single.just(new Cursor<>(items, next)); diff --git a/app/src/main/java/rs/chir/invtracker/api/Client.java b/app/src/main/java/rs/chir/invtracker/api/Client.java index d6aea83..5b48574 100644 --- a/app/src/main/java/rs/chir/invtracker/api/Client.java +++ b/app/src/main/java/rs/chir/invtracker/api/Client.java @@ -15,7 +15,7 @@ import rs.chir.invtracker.model.GeoRect; import rs.chir.invtracker.model.PasetoToken; import rs.chir.invtracker.model.PublicKey; import rs.chir.invtracker.model.TrackedItem; -import rs.chir.invtracker.utils.CursorStreamable; +import rs.chir.invtracker.utils.RXJavaAdapters; /** * The API Client abstract base class. @@ -29,6 +29,7 @@ public abstract class Client { /** * Gets the public key. + * * @return A single that emits the public key * @see PublicKey * @see Single @@ -38,6 +39,7 @@ public abstract class Client { /** * Attempts to log in with the given credentials. + * * @param username the username * @param password the password * @return A single that emits the API token @@ -49,6 +51,7 @@ public abstract class Client { /** * Retrieves an item with the given ID. + * * @param id the item ID * @return A single that emits the item * @see TrackedItem @@ -59,6 +62,7 @@ public abstract class Client { /** * Retrieves a page of items. + * * @param cont An optional continuation token * @return A single that emits a page of items * @see TrackedItem @@ -70,17 +74,19 @@ public abstract class Client { /** * Retrieve all items + * * @return A flowable that emits all items * @see TrackedItem * @see Flowable */ @NonNull public Flowable streamObjects() { - return new CursorStreamable<>(this::getObjects); + return RXJavaAdapters.fromCursor(this::getObjects); } /** * Retieves a page of items that are within the given rectangle. + * * @param rect The rectangle * @param cont An optional continuation token * @return A single that emits a page of items @@ -93,6 +99,7 @@ public abstract class Client { /** * Retrieves all items that are within the given rectangle. + * * @param rect The rectangle * @return A flowable that emits all items * @see TrackedItem @@ -100,11 +107,12 @@ public abstract class Client { */ @NonNull public Flowable streamObjects(@NonNull GeoRect rect) { - return new CursorStreamable<>(id -> this.getObjects(rect, id)); + return RXJavaAdapters.fromCursor(id -> this.getObjects(rect, id)); } /** * Retrieves all items that are in an optional rectangle. + * * @param rect The rectangle * @return A flowable that emits all matching items * @see TrackedItem @@ -117,6 +125,7 @@ public abstract class Client { /** * Retrieves a page of items that are within the given rectangle. + * * @param rect The rectangle * @param cont An optional continuation token * @return A single that emits a page of items @@ -128,7 +137,8 @@ public abstract class Client { /** * Updates the location of the given item. - * @param item The item + * + * @param item The item * @param location The location * @return A single that emits true on success * @see Single @@ -140,7 +150,8 @@ public abstract class Client { /** * Updates the location of the given item. - * @param itemId The item ID + * + * @param itemId The item ID * @param location The location * @return A single that emits true on success * @see Single @@ -150,6 +161,7 @@ public abstract class Client { /** * Starts a download for an image. + * * @param imageUrl The image URL * @return A single that emits the image input stream */ @@ -158,8 +170,9 @@ public abstract class Client { /** * Retrieves a page of locations for the given item. + * * @param itemId The item ID - * @param cont An optional continuation token + * @param cont An optional continuation token * @return A single that emits a page of locations * @see GeoLocation * @see Cursor @@ -169,6 +182,7 @@ public abstract class Client { /** * Retrieves all locations for the given item. + * * @param itemId The item ID * @return A flowable that emits all locations * @see GeoLocation @@ -176,11 +190,12 @@ public abstract class Client { */ @NonNull public Flowable streamLocations(long itemId) { - return new CursorStreamable<>(id -> this.getLocations(itemId, id)); + return RXJavaAdapters.fromCursor(id -> this.getLocations(itemId, id)); } /** * Uploads an image to the server. + * * @param uri The source URI * @return The URL of the uploaded image * @see Single @@ -191,6 +206,7 @@ public abstract class Client { /** * Updates an item. + * * @param item The item * @return A single that emits the updated item * @see Single @@ -201,6 +217,7 @@ public abstract class Client { /** * Creates a new item. + * * @param item The item * @return A single that emits the created item * @see Single @@ -211,6 +228,7 @@ public abstract class Client { /** * Deletes an item. + * * @param id The item id * @return A single that emits true on success * @see Single diff --git a/app/src/main/java/rs/chir/invtracker/api/ClientBackoff.java b/app/src/main/java/rs/chir/invtracker/api/ClientBackoff.java index a18935b..b3fbd86 100644 --- a/app/src/main/java/rs/chir/invtracker/api/ClientBackoff.java +++ b/app/src/main/java/rs/chir/invtracker/api/ClientBackoff.java @@ -8,11 +8,13 @@ import rs.chir.invtracker.utils.SingleBackoff; /** * An extended version of {@link SingleBackoff} that errors out on {@link UnauthorizedException}. + * * @param the type of the result */ class ClientBackoff extends SingleBackoff { /** * Creates a new client backoff. + * * @param singleSupplier the supplier of the result */ ClientBackoff(@NonNull Supplier> singleSupplier) { diff --git a/app/src/main/java/rs/chir/invtracker/api/ClientImpl.java b/app/src/main/java/rs/chir/invtracker/api/ClientImpl.java index 3b61006..71a62c8 100644 --- a/app/src/main/java/rs/chir/invtracker/api/ClientImpl.java +++ b/app/src/main/java/rs/chir/invtracker/api/ClientImpl.java @@ -16,8 +16,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import javax.xml.transform.TransformerException; @@ -32,7 +30,6 @@ import okio.BufferedSink; import okio.Okio; import rs.chir.compat.java.util.Base64; import rs.chir.compat.java.util.Optional; -import rs.chir.invtracker.client.BuildConfig; import rs.chir.invtracker.client.model.APIKey; import rs.chir.invtracker.model.Cursor; import rs.chir.invtracker.model.GeoLocation; @@ -40,7 +37,7 @@ import rs.chir.invtracker.model.GeoRect; import rs.chir.invtracker.model.PasetoToken; import rs.chir.invtracker.model.PublicKey; import rs.chir.invtracker.model.TrackedItem; -import rs.chir.invtracker.utils.CallAdapter; +import rs.chir.invtracker.utils.RXJavaAdapters; import rs.chir.utils.xml.SimpleXML; import rs.chir.utils.xml.XMLSerializable; @@ -75,7 +72,8 @@ public class ClientImpl extends Client { /** * Creates a new client. - * @param context the context used to retrieve the API token + * + * @param context the context used to retrieve the API token * @param cronetEngine the Cronet engine */ private ClientImpl(Context context, CronetEngine cronetEngine) { @@ -87,6 +85,7 @@ public class ClientImpl extends Client { /** * Creates a new client. + * * @param context the context used */ @NonNull @@ -97,6 +96,7 @@ public class ClientImpl extends Client { /** * Returns the HTTP client. + * * @return the HTTP client */ @NonNull @@ -106,9 +106,10 @@ public class ClientImpl extends Client { /** * Parses a response + * * @param response A single emitting the response - * @param clazz The expected class of the response - * @param The expected class of the response + * @param clazz The expected class of the response + * @param The expected class of the response * @return A single emitting the parsed response * @see SimpleXML#parseDocument(InputStream) * @see SimpleXML#deserialize(Node, Class) @@ -129,10 +130,11 @@ public class ClientImpl extends Client { /** * Encodes a request body. + * * @param body the body to encode - * @param the type of the body + * @param the type of the body * @return the encoded body - * @throws IOException If the serializer could not write to a {@link ByteArrayOutputStream} + * @throws IOException If the serializer could not write to a {@link ByteArrayOutputStream} * @throws TransformerException If the serializer could not transform the body to XML * @see SimpleXML#serialize(XMLSerializable) * @see SimpleXML#writeTo(Document, OutputStream) @@ -156,12 +158,13 @@ public class ClientImpl extends Client { Request request = new Request.Builder() .url(API_URL + "/public-key") .build(); - return new CallAdapter(this.client.newCall(request)); + return RXJavaAdapters.fromCall(this.client.newCall(request)); }), PublicKey.class); } /** * Creates a new {@link Request.Builder} for the given API route. + * * @param route the API route * @return A single emitting the {@link Request.Builder} */ @@ -188,7 +191,7 @@ public class ClientImpl extends Client { .header("Authorization", authHeader) .post(RequestBody.create(EMPTY_BODY)) .build(); - return new CallAdapter(this.client.newCall(request)); + return RXJavaAdapters.fromCall(this.client.newCall(request)); }), PasetoToken.class); } @@ -197,14 +200,14 @@ public class ClientImpl extends Client { public Single getObject(long id) { return this.parseResponse(new ClientBackoff<>(() -> this.createRequest("/objects/" + id).map(Request.Builder::build) .map(this.client::newCall) - .flatMap(CallAdapter::new)), TrackedItem.class); + .flatMap(RXJavaAdapters::fromCall)), TrackedItem.class); } @Override @NonNull public Single> getObjects(@NonNull Optional cont) { return this.parseResponse(new ClientBackoff<>(() -> this.createRequest("/objects" + cont.map(c -> "?start=" + c).orElse("")) - .flatMap(request -> new CallAdapter(this.client.newCall(request.get().build())))), + .flatMap(request -> RXJavaAdapters.fromCall(this.client.newCall(request.get().build())))), Cursor.class) .map(cursor -> (Cursor) cursor); } @@ -216,7 +219,7 @@ public class ClientImpl extends Client { .flatMap(request -> { var req = request.method("POST", RequestBody.create(this.encodeBody(rect))) .build(); - return new CallAdapter(this.client.newCall(req)); + return RXJavaAdapters.fromCall(this.client.newCall(req)); })), Cursor.class) .map(cursor -> (Cursor) cursor); @@ -229,7 +232,7 @@ public class ClientImpl extends Client { .flatMap(builder -> { var req = builder.method("POST", RequestBody.create(this.encodeBody(location))) .build(); - return new CallAdapter(this.client.newCall(req)); + return RXJavaAdapters.fromCall(this.client.newCall(req)); }).map(__ -> true); } @@ -246,7 +249,7 @@ public class ClientImpl extends Client { rb = Single.just(new Request.Builder() .url(imageUrl)); } - return rb.flatMap(request -> new ClientBackoff<>(() -> new CallAdapter(this.client.newCall(request.build())))) + return rb.flatMap(request -> new ClientBackoff<>(() -> RXJavaAdapters.fromCall(this.client.newCall(request.build())))) .map(Response::body).map(ResponseBody::byteStream); } @@ -254,7 +257,7 @@ public class ClientImpl extends Client { @NonNull public Single> getLocations(long itemId, @NonNull Optional cont) { return this.parseResponse(new ClientBackoff<>(() -> this.createRequest("/objects/" + itemId + "/locations" + cont.map(c -> "?start=" + c).orElse("")) - .flatMap(request -> new CallAdapter(this.client.newCall(request.get().build())))), + .flatMap(request -> RXJavaAdapters.fromCall(this.client.newCall(request.get().build())))), Cursor.class) .map(cursor -> (Cursor) cursor); } @@ -280,7 +283,7 @@ public class ClientImpl extends Client { return MediaType.parse(contentType); } }; - return new CallAdapter(this.client.newCall(request.method("POST", rb).build())); + return RXJavaAdapters.fromCall(this.client.newCall(request.method("POST", rb).build())); })).map(Response::headers).map(headers -> headers.get("Location")).map(header -> API_URL + header); } @@ -288,25 +291,25 @@ public class ClientImpl extends Client { @Override public Single updateObject(@NonNull TrackedItem item) { return this.parseResponse(new ClientBackoff<>(() -> this.createRequest("/objects/" + item.id()) - .flatMap(request -> new CallAdapter(this.client.newCall(request.method("PATCH", RequestBody.create(this.encodeBody(item))) - .build())))), - TrackedItem.class); + .flatMap(request -> RXJavaAdapters.fromCall(this.client.newCall(request.method("PATCH", RequestBody.create(this.encodeBody(item))) + .build())))), + TrackedItem.class); } @NonNull @Override public Single createObject(@NonNull TrackedItem item) { return this.parseResponse(new ClientBackoff<>(() -> this.createRequest("/objects") - .flatMap(request -> new CallAdapter(this.client.newCall(request.method("POST", RequestBody.create(this.encodeBody(item))) - .build())))), - TrackedItem.class); + .flatMap(request -> RXJavaAdapters.fromCall(this.client.newCall(request.method("POST", RequestBody.create(this.encodeBody(item))) + .build())))), + TrackedItem.class); } @NonNull @Override public Single deleteObject(long id) { return this.createRequest("/objects/" + id) - .flatMap(request -> new CallAdapter(this.client.newCall(request.method("DELETE", RequestBody.create(EMPTY_BODY)).build()))) + .flatMap(request -> RXJavaAdapters.fromCall(this.client.newCall(request.method("DELETE", RequestBody.create(EMPTY_BODY)).build()))) .map(__ -> true); } } diff --git a/app/src/main/java/rs/chir/invtracker/api/CronetEngineProvider.java b/app/src/main/java/rs/chir/invtracker/api/CronetEngineProvider.java index 2e8511c..cb955ac 100644 --- a/app/src/main/java/rs/chir/invtracker/api/CronetEngineProvider.java +++ b/app/src/main/java/rs/chir/invtracker/api/CronetEngineProvider.java @@ -14,7 +14,7 @@ import java.io.File; import io.reactivex.rxjava3.core.Single; import rs.chir.compat.java.util.Optional; -import rs.chir.invtracker.utils.TaskAdapter; +import rs.chir.invtracker.utils.RXJavaAdapters; /** * Utility class for creating a {@link CronetEngine} instance. @@ -36,6 +36,7 @@ public enum CronetEngineProvider { /** * Creates a new {@link CronetEngine} instance. + * * @param context The context used for installing cronet * @return A single that emits the {@link CronetEngine} instance */ @@ -43,7 +44,7 @@ public enum CronetEngineProvider { public static Single getInstance(@NonNull Context context) { if (!ENGINE.isPresent()) { var installTask = CronetProviderInstaller.installProvider(context); - return TaskAdapter.fromTask(installTask) + return RXJavaAdapters.fromTask(installTask) .map(_void -> new CronetEngine.Builder(context)) .onErrorResumeNext(throwable -> { Log.e("CronetEngineProvider", "Failed to install Cronet provider " + throwable); diff --git a/app/src/main/java/rs/chir/invtracker/api/package-info.java b/app/src/main/java/rs/chir/invtracker/api/package-info.java index 29aebc8..6b4ba1f 100644 --- a/app/src/main/java/rs/chir/invtracker/api/package-info.java +++ b/app/src/main/java/rs/chir/invtracker/api/package-info.java @@ -1,6 +1,6 @@ /** * API client tools. - * + *

* You can retrieve an API client using {@link rs.chir.invtracker.client.Application#getClient()}. */ package rs.chir.invtracker.api; \ No newline at end of file diff --git a/app/src/main/java/rs/chir/invtracker/client/Application.java b/app/src/main/java/rs/chir/invtracker/client/Application.java index 2f65475..3a0f4bd 100644 --- a/app/src/main/java/rs/chir/invtracker/client/Application.java +++ b/app/src/main/java/rs/chir/invtracker/client/Application.java @@ -25,7 +25,7 @@ import rs.chir.invtracker.api.ClientImpl; * @see android.app.Application */ public class Application extends android.app.Application { - /** + /** * The instance of the API client. */ private Optional client = Optional.empty(); @@ -36,6 +36,7 @@ public class Application extends android.app.Application { /** * Returns the Application instance for a context + * * @param context the context to get the Application instance for * @return the Application instance for the context */ @@ -64,6 +65,7 @@ public class Application extends android.app.Application { /** * Returns the preferences datastore. + * * @return the preferences datastore */ @NonNull @@ -73,6 +75,7 @@ public class Application extends android.app.Application { /** * Returns the API client. + * * @return A single that completes with the API client. */ @NonNull diff --git a/app/src/main/java/rs/chir/invtracker/client/EditItemFragment.java b/app/src/main/java/rs/chir/invtracker/client/EditItemFragment.java index 1d3be3f..2622a7b 100644 --- a/app/src/main/java/rs/chir/invtracker/client/EditItemFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/EditItemFragment.java @@ -1,10 +1,8 @@ package rs.chir.invtracker.client; -import static android.app.Activity.RESULT_OK; import static android.provider.MediaStore.ACTION_PICK_IMAGES; import android.Manifest; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -20,7 +18,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.core.content.FileProvider; -import androidx.fragment.app.Fragment; import java.io.File; import java.io.IOException; @@ -112,7 +109,7 @@ public class EditItemFragment extends FragmentBase { * Use this factory method to create a new instance of * this fragment using the provided parameters. * - * @param itemId the item ID to edit. + * @param itemId the item ID to edit. * @param editMode true if you want to edit, create otherwise. * @return A new instance of fragment EditItemFragment. */ @@ -145,7 +142,7 @@ public class EditItemFragment extends FragmentBase { this.subscribe(this.getClient().flatMap(client -> client.getObject(mId)), this::prefillItem); } this.getBinding().buttonTakePicture.setOnClickListener(v -> { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { this.requestPermission(Manifest.permission.CAMERA); return; } @@ -178,7 +175,7 @@ public class EditItemFragment extends FragmentBase { var description = this.getBinding().editTextDescription.getText().toString(); var item = new TrackedItem(mId, name, description, this.image, Optional.empty()); // Update or create the item - if(mEditMode) { + if (mEditMode) { this.subscribe(this.getClient().flatMap(client -> client.updateObject(item)), this::onActionSuccess); } else { this.subscribe(this.getClient().flatMap(client -> client.createObject(item)), this::onActionSuccess); @@ -187,10 +184,11 @@ public class EditItemFragment extends FragmentBase { /** * Handler for when the saving of the item is successful. + * * @param item the item that was saved. */ private void onActionSuccess(TrackedItem trackedItem) { - if(mEditMode) { + if (mEditMode) { // return if we were editing this.getNavController().navigateUp(); } else { @@ -201,6 +199,7 @@ public class EditItemFragment extends FragmentBase { /** * Pick an @{link ActivityResultLauncher} to use for picking an image. + * * @return the @{link ActivityResultLauncher} to use for picking an image. */ private ActivityResultLauncher getImagePickerLauncher() { @@ -220,6 +219,7 @@ public class EditItemFragment extends FragmentBase { /** * Callback for when an image was taken. + * * @param success true if the image was taken successfully. */ private void onPictureTaken(@NonNull Boolean success) { @@ -234,6 +234,7 @@ public class EditItemFragment extends FragmentBase { /** * Upload an image to the server. + * * @param uri the source image URI. */ private void uploadImage(Uri uri) { @@ -255,6 +256,7 @@ public class EditItemFragment extends FragmentBase { /** * Attempts to take a picture with the camera. + * * @throws IOException if the camera cannot be accessed. */ private void takePicture() throws IOException { @@ -277,6 +279,7 @@ public class EditItemFragment extends FragmentBase { /** * Prefill the fragment with the given item. + * * @param item the item to prefill with. */ private void prefillItem(@NonNull TrackedItem trackedItem) { diff --git a/app/src/main/java/rs/chir/invtracker/client/FragmentBase.java b/app/src/main/java/rs/chir/invtracker/client/FragmentBase.java index d2cd179..61776a6 100644 --- a/app/src/main/java/rs/chir/invtracker/client/FragmentBase.java +++ b/app/src/main/java/rs/chir/invtracker/client/FragmentBase.java @@ -5,7 +5,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.IdRes; @@ -22,8 +21,8 @@ import androidx.viewbinding.ViewBinding; import com.google.android.material.snackbar.Snackbar; -import java.util.ConcurrentModificationException; import java.lang.reflect.InvocationTargetException; +import java.util.ConcurrentModificationException; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; @@ -38,6 +37,7 @@ import rs.chir.utils.TypeParameterGetter; /** * Base class for all fragments. + * * @param the type of the view binding. */ public class FragmentBase extends Fragment implements TypeParameterGetter { @@ -56,9 +56,23 @@ public class FragmentBase extends Fragment */ @Nullable private MenuProvider menuProvider; + private String requestedPermission; + /** + * Permissions request launcher + */ + private final ActivityResultLauncher permissionLauncher = this.registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + var permission = this.requestedPermission; + this.requestedPermission = null; + if (isGranted) { + this.onPermissionGranted(permission); + } else { + this.onPermissionDenied(permission); + } + }); /** * Retrieves the fragment binding + * * @return the fragment binding */ @NonNull @@ -71,6 +85,7 @@ public class FragmentBase extends Fragment /** * Sets the fragment binding + * * @param binding the fragment binding */ protected void setBinding(@NonNull FragmentBinding binding) { @@ -79,8 +94,9 @@ public class FragmentBase extends Fragment /** * Called when the view is created. - * @param inflater the layout inflater - * @param container the container view + * + * @param inflater the layout inflater + * @param container the container view * @param savedInstanceState the saved instance state * @return the inflated view */ @@ -92,10 +108,11 @@ public class FragmentBase extends Fragment /** * Called when the view is created. - * @param inflater the layout inflater - * @param container the container view + * + * @param inflater the layout inflater + * @param container the container view * @param savedInstanceState the saved instance state - * @param clazz the class of the fragment binding + * @param clazz the class of the fragment binding * @return the inflated view */ protected View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -115,6 +132,7 @@ public class FragmentBase extends Fragment /** * Adds a disposable to the {@link CompositeDisposable} + * * @param disposable the disposable to add */ protected void setAction(@NonNull Disposable action) { @@ -127,7 +145,8 @@ public class FragmentBase extends Fragment /** * Subscribe to the given observable and add it to the {@link CompositeDisposable} - * @param value the observable to subscribe to + * + * @param value the observable to subscribe to * @param consumer the consumer to call when the observable emits an item */ protected void subscribe(@NonNull Observable value, @NonNull Consumer consumer) { @@ -136,7 +155,8 @@ public class FragmentBase extends Fragment /** * Subscribe to the given single and add it to the {@link CompositeDisposable} - * @param value the single to subscribe to + * + * @param value the single to subscribe to * @param consumer the consumer to call when the single emits an item */ protected void subscribe(@NonNull Single value, @NonNull Consumer consumer) { @@ -145,9 +165,10 @@ public class FragmentBase extends Fragment /** * Subscribe to the given single and add it to the {@link CompositeDisposable} - * @param value the single to subscribe to + * + * @param value the single to subscribe to * @param consumer the consumer to call when the observable emits an item - * @param error the consumer to call when an error occurs + * @param error the consumer to call when an error occurs */ protected void subscribe(@NonNull Single value, @NonNull Consumer consumer, @NonNull Consumer error) { this.subscribe(value.toObservable(), consumer, error); @@ -155,9 +176,10 @@ public class FragmentBase extends Fragment /** * Subscribe to the given observable and add it to the {@link CompositeDisposable} - * @param value the observable to subscribe to + * + * @param value the observable to subscribe to * @param consumer the consumer to call when the observable emits an item - * @param error the consumer to call when an error occurs + * @param error the consumer to call when an error occurs */ protected void subscribe(@NonNull Observable value, @NonNull Consumer consumer, @NonNull Consumer error) { this.setAction(value.subscribeOn(Schedulers.io()) @@ -167,7 +189,8 @@ public class FragmentBase extends Fragment /** * Called when the fragment is attached to the view. - * @param view the view + * + * @param view the view * @param savedInstanceState the saved instance state */ @Override @@ -181,6 +204,7 @@ public class FragmentBase extends Fragment /** * Creates this fragment’s menu provider. + * * @return the menu provider, or null if no menu provider is needed */ @Nullable @@ -213,6 +237,7 @@ public class FragmentBase extends Fragment /** * Returns the menu host + * * @return the menu host */ @NonNull @@ -223,6 +248,7 @@ public class FragmentBase extends Fragment /** * Returns the application + * * @return the application */ @NonNull @@ -232,6 +258,7 @@ public class FragmentBase extends Fragment /** * Returns the API client + * * @return the API client */ @NonNull @@ -241,6 +268,7 @@ public class FragmentBase extends Fragment /** * Returns the navigation controller + * * @return the navigation controller */ @NonNull @@ -250,15 +278,16 @@ public class FragmentBase extends Fragment /** * Navigate to the given fragment + * * @param id the action id */ void navigate(@IdRes int id) { this.getNavController().navigate(id); } - /** * Checks if the app has the given permission + * * @param permission the permission to check * @return true if the app has the permission, false otherwise */ @@ -268,6 +297,7 @@ public class FragmentBase extends Fragment /** * Called on action error + * * @param throwable the throwable */ protected void onActionError(@NonNull Throwable throwable) { @@ -279,51 +309,41 @@ public class FragmentBase extends Fragment /** * Navigates to the given fragment + * * @param fragmentDirection the fragment direction */ void navigate(NavDirections fragmentDirection) { this.getNavController().navigate(fragmentDirection); } - private String requestedPermission; - - /** - * Permissions request launcher - */ - private final ActivityResultLauncher permissionLauncher = this.registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { - var permission = this.requestedPermission; - this.requestedPermission = null; - if (isGranted) { - this.onPermissionGranted(permission); - } else { - this.onPermissionDenied(permission); - } - }); - /** * Called when the requested permission is granted + * * @param permission the permission */ protected void onPermissionGranted(@NonNull String permission) { } + /** * Called when the requested permission is denied + * * @param permission the permission */ protected void onPermissionDenied(@NonNull String permission) { - this.onActionError(new RuntimeException("Permission denied: " + permission)); + this.onActionError(new RuntimeException("Permission denied: " + permission)); } /** * Requests the given permission + * * @param permission the permission to request */ protected void requestPermission(@NonNull String permission) { - if(this.hasPermission(permission)) { + if (this.hasPermission(permission)) { this.onPermissionGranted(permission); return; } - if(this.requestedPermission != null) { + if (this.requestedPermission != null) { throw new ConcurrentModificationException("Only one permission can be requested at a time"); } this.requestedPermission = permission; diff --git a/app/src/main/java/rs/chir/invtracker/client/ItemDetailFragment.java b/app/src/main/java/rs/chir/invtracker/client/ItemDetailFragment.java index 9162402..caa981b 100644 --- a/app/src/main/java/rs/chir/invtracker/client/ItemDetailFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/ItemDetailFragment.java @@ -11,7 +11,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.view.MenuProvider; -import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import java.io.File; @@ -60,6 +59,7 @@ public class ItemDetailFragment extends FragmentBase /** * Called when the fragment is created. + * * @param savedInstanceState The saved instance state. */ @Override @@ -72,7 +72,8 @@ public class ItemDetailFragment extends FragmentBase /** * Called when the fragment is attached to the activity. - * @param view The view. + * + * @param view The view. * @param savedInstanceState The saved instance state. */ @Override @@ -99,7 +100,7 @@ public class ItemDetailFragment extends FragmentBase // Reload the item this.subscribe(this.getClient().flatMap(client -> client.getObject(this.mId)), this::itemLoaded); } - + @Override protected void onActionError(@NonNull Throwable throwable) { Log.e("ItemDetailFragment", "Error loading item", throwable); @@ -134,6 +135,7 @@ public class ItemDetailFragment extends FragmentBase /** * Fetches the image from the server + * * @param image The image URI */ private void fetchImage(@NonNull URI image) { @@ -143,7 +145,8 @@ public class ItemDetailFragment extends FragmentBase /** * Called when the URL is loaded - * @param image The image URI + * + * @param image The image URI * @param inputStream the response body */ private void imageLoaded(@NonNull URI image, @NonNull InputStream inputStream) { @@ -171,6 +174,7 @@ public class ItemDetailFragment extends FragmentBase /** * Loads and returns the image if present + * * @param image The image URI * @return The image if present */ @@ -195,6 +199,7 @@ public class ItemDetailFragment extends FragmentBase /** * Returns the item id + * * @return The item id */ long getmId() { @@ -208,7 +213,8 @@ public class ItemDetailFragment extends FragmentBase var builder = new AlertDialog.Builder(this.requireContext()); builder.setMessage(R.string.delete_warning) .setPositiveButton(R.string.yes, (__1, __2) -> this.reallyDelete()) - .setNegativeButton(R.string.no, (__1, __2) -> {}); + .setNegativeButton(R.string.no, (__1, __2) -> { + }); builder.create().show(); } diff --git a/app/src/main/java/rs/chir/invtracker/client/ItemDetailMenuProvider.java b/app/src/main/java/rs/chir/invtracker/client/ItemDetailMenuProvider.java index 9386779..dde7175 100644 --- a/app/src/main/java/rs/chir/invtracker/client/ItemDetailMenuProvider.java +++ b/app/src/main/java/rs/chir/invtracker/client/ItemDetailMenuProvider.java @@ -18,6 +18,7 @@ class ItemDetailMenuProvider implements MenuProvider { /** * Creates a new instance of {@link ItemDetailMenuProvider} + * * @param itemDetailFragment the fragment */ ItemDetailMenuProvider(ItemDetailFragment itemDetailFragment) { @@ -26,7 +27,8 @@ class ItemDetailMenuProvider implements MenuProvider { /** * Called when the menu is created - * @param menu the menu + * + * @param menu the menu * @param inflater the menu inflater */ @Override @@ -36,6 +38,7 @@ class ItemDetailMenuProvider implements MenuProvider { /** * Called when an item is selected + * * @param item the item * @return true if the item was handled, false otherwise */ diff --git a/app/src/main/java/rs/chir/invtracker/client/LoadFragment.java b/app/src/main/java/rs/chir/invtracker/client/LoadFragment.java index 237fe26..f855dd9 100644 --- a/app/src/main/java/rs/chir/invtracker/client/LoadFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/LoadFragment.java @@ -19,6 +19,7 @@ public class LoadFragment extends FragmentBase { /** * Called when the API key is set. + * * @param hasToken {@code true} if the API key is set, {@code false} otherwise. */ private void tokenResponse(boolean hasToken) { diff --git a/app/src/main/java/rs/chir/invtracker/client/LoginFragment.java b/app/src/main/java/rs/chir/invtracker/client/LoginFragment.java index 0e5bcdc..d326565 100644 --- a/app/src/main/java/rs/chir/invtracker/client/LoginFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/LoginFragment.java @@ -40,6 +40,7 @@ public class LoginFragment extends FragmentBase { /** * Called when the login button is clicked. + * * @param view The view. */ private void login(View view) { @@ -54,6 +55,7 @@ public class LoginFragment extends FragmentBase { /** * Called when the login response is received. + * * @param token The token. */ private void loginResponse(@NonNull PasetoToken pasetoToken) { diff --git a/app/src/main/java/rs/chir/invtracker/client/MainActivity.java b/app/src/main/java/rs/chir/invtracker/client/MainActivity.java index 85079b0..8fcca7b 100644 --- a/app/src/main/java/rs/chir/invtracker/client/MainActivity.java +++ b/app/src/main/java/rs/chir/invtracker/client/MainActivity.java @@ -45,6 +45,7 @@ public class MainActivity extends AppCompatActivity { /** * Returns the activity binding. + * * @return The activity binding. */ @NonNull diff --git a/app/src/main/java/rs/chir/invtracker/client/MapFragment.java b/app/src/main/java/rs/chir/invtracker/client/MapFragment.java index c80dd93..1c12b3c 100644 --- a/app/src/main/java/rs/chir/invtracker/client/MapFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/MapFragment.java @@ -1,7 +1,6 @@ package rs.chir.invtracker.client; import android.os.Bundle; -import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -18,9 +17,6 @@ import org.osmdroid.config.Configuration; import org.osmdroid.util.GeoPoint; import org.osmdroid.views.overlay.Marker; -import java.util.ArrayList; -import java.util.List; - import rs.chir.invtracker.client.databinding.FragmentMapBinding; import rs.chir.invtracker.model.TrackedItem; import rs.chir.invtracker.utils.SingleLocation; @@ -70,6 +66,7 @@ public class MapFragment extends FragmentBase { /** * Creates and adds a marker for a tracked item + * * @param item the tracked item to add a marker for */ private void addMarker(@NonNull TrackedItem item) { @@ -103,8 +100,8 @@ public class MapFragment extends FragmentBase { var map = this.getBinding().map; map.getOverlays().clear(); this.subscribe(this.getClient().toObservable() - .flatMap(client -> client.streamObjects().toObservable()) - .filter(item -> item.lastKnownLocation().isPresent()), + .flatMap(client -> client.streamObjects().toObservable()) + .filter(item -> item.lastKnownLocation().isPresent()), this::addMarker); } diff --git a/app/src/main/java/rs/chir/invtracker/client/MapMenuProvider.java b/app/src/main/java/rs/chir/invtracker/client/MapMenuProvider.java index d803306..78b11d4 100644 --- a/app/src/main/java/rs/chir/invtracker/client/MapMenuProvider.java +++ b/app/src/main/java/rs/chir/invtracker/client/MapMenuProvider.java @@ -18,6 +18,7 @@ public class MapMenuProvider implements MenuProvider { /** * Creates a new instance of {@link MapMenuProvider} + * * @param mapFragment the fragment */ public MapMenuProvider(MapFragment mapFragment) { diff --git a/app/src/main/java/rs/chir/invtracker/client/NearbyFragment.java b/app/src/main/java/rs/chir/invtracker/client/NearbyFragment.java index e09585a..f703603 100644 --- a/app/src/main/java/rs/chir/invtracker/client/NearbyFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/NearbyFragment.java @@ -58,6 +58,7 @@ public class NearbyFragment extends FragmentBase { /** * Change the filter mode. + * * @param filterMode The new filter mode. */ void switchFilter(ListFilterMode filterMode) { @@ -87,6 +88,7 @@ public class NearbyFragment extends FragmentBase { /** * Called when the location response is received. + * * @param location The location. */ private void onLocationResponse(@NonNull GeoLocation location) { diff --git a/app/src/main/java/rs/chir/invtracker/client/NearbyMenuProvider.java b/app/src/main/java/rs/chir/invtracker/client/NearbyMenuProvider.java index 456f64b..f420b40 100644 --- a/app/src/main/java/rs/chir/invtracker/client/NearbyMenuProvider.java +++ b/app/src/main/java/rs/chir/invtracker/client/NearbyMenuProvider.java @@ -6,7 +6,6 @@ import android.view.MenuItem; import androidx.annotation.NonNull; import androidx.core.view.MenuProvider; -import androidx.navigation.fragment.NavHostFragment; /** * The menu provider for {@link NearbyFragment} @@ -19,6 +18,7 @@ class NearbyMenuProvider implements MenuProvider { /** * Creates a new instance of {@link NearbyMenuProvider} + * * @param nearbyFragment the fragment */ public NearbyMenuProvider(NearbyFragment nearbyFragment) { diff --git a/app/src/main/java/rs/chir/invtracker/client/NearbyOnTouchListener.java b/app/src/main/java/rs/chir/invtracker/client/NearbyOnTouchListener.java index 27d2411..5e4e32a 100644 --- a/app/src/main/java/rs/chir/invtracker/client/NearbyOnTouchListener.java +++ b/app/src/main/java/rs/chir/invtracker/client/NearbyOnTouchListener.java @@ -18,6 +18,7 @@ class NearbyOnTouchListener implements RecyclerView.OnItemTouchListener { /** * Creates a new instance of {@link NearbyOnTouchListener} + * * @param nearbyFragment the fragment */ public NearbyOnTouchListener(NearbyFragment nearbyFragment) { diff --git a/app/src/main/java/rs/chir/invtracker/client/QRCodeFragment.java b/app/src/main/java/rs/chir/invtracker/client/QRCodeFragment.java index e1b6098..e196286 100644 --- a/app/src/main/java/rs/chir/invtracker/client/QRCodeFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/QRCodeFragment.java @@ -8,7 +8,6 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; import androidx.print.PrintHelper; import com.google.zxing.BarcodeFormat; @@ -130,6 +129,6 @@ public class QRCodeFragment extends FragmentBase { @Override protected void onPermissionGranted(@NonNull String permission) { - this.storeBitmap(); + this.storeBitmap(); } } diff --git a/app/src/main/java/rs/chir/invtracker/client/QRScanFragment.java b/app/src/main/java/rs/chir/invtracker/client/QRScanFragment.java index 53be539..c316402 100644 --- a/app/src/main/java/rs/chir/invtracker/client/QRScanFragment.java +++ b/app/src/main/java/rs/chir/invtracker/client/QRScanFragment.java @@ -29,7 +29,7 @@ import java.util.regex.Pattern; import rs.chir.cv.QRAnalyzer; import rs.chir.invtracker.client.databinding.FragmentQrScanBinding; import rs.chir.invtracker.model.GeoLocation; -import rs.chir.invtracker.utils.ListenableFutureAdapter; +import rs.chir.invtracker.utils.RXJavaAdapters; import rs.chir.invtracker.utils.SingleLocation; import rs.chir.utils.math.Vec2; @@ -84,20 +84,20 @@ public class QRScanFragment extends FragmentBase implemen @Override protected void onPermissionGranted(String permission) { - if(permission.equals(Manifest.permission.CAMERA)) { - this.startCamera(); - } else if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { - var fusedLocationClient = LocationServices.getFusedLocationProviderClient(this.requireActivity()); - this.subscribe(SingleLocation.getNextLocation(fusedLocationClient, this.requireContext()), location -> this.updateLocation(id, location), err -> { - err.printStackTrace(); - this.showDetailView(id); - }); - } + if (permission.equals(Manifest.permission.CAMERA)) { + this.startCamera(); + } else if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { + var fusedLocationClient = LocationServices.getFusedLocationProviderClient(this.requireActivity()); + this.subscribe(SingleLocation.getNextLocation(fusedLocationClient, this.requireContext()), location -> this.updateLocation(id, location), err -> { + err.printStackTrace(); + this.showDetailView(id); + }); + } } @Override protected void onPermissionDenied(String permission) { - if(permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { + if (permission.equals(Manifest.permission.ACCESS_FINE_LOCATION)) { this.showDetailView(id); } else { super.onPermissionDenied(permission); @@ -121,6 +121,7 @@ public class QRScanFragment extends FragmentBase implemen /** * Returns the size of the application window in pixels + * * @return the size of the application window in pixels */ @NonNull @@ -139,7 +140,7 @@ public class QRScanFragment extends FragmentBase implemen private void startCamera() { // request the camera provider - this.subscribe(new ListenableFutureAdapter<>(ProcessCameraProvider.getInstance(this.requireContext()), ContextCompat.getMainExecutor(this.requireContext())), this::openedCamera); + this.subscribe(RXJavaAdapters.fromListenableFuture(ProcessCameraProvider.getInstance(this.requireContext()), ContextCompat.getMainExecutor(this.requireContext())), this::openedCamera); } @Override @@ -164,6 +165,7 @@ public class QRScanFragment extends FragmentBase implemen /** * Show or hide the viewfinder + * * @param hide true to hide the viewfinder, false to show it */ private void hideUI(boolean b) { @@ -175,7 +177,8 @@ public class QRScanFragment extends FragmentBase implemen /** * Update the location of the object - * @param id the object id + * + * @param id the object id * @param location the location of the object */ private void updateLocation(long id, GeoLocation location) { @@ -193,6 +196,7 @@ public class QRScanFragment extends FragmentBase implemen /** * Show the detail view of the object + * * @param id the object id */ private void showDetailView(long id) { @@ -201,6 +205,7 @@ public class QRScanFragment extends FragmentBase implemen /** * Initializes the camera + * * @param cameraProvider the camera provider */ private void openedCamera(@NonNull ProcessCameraProvider cameraProvider) { diff --git a/app/src/main/java/rs/chir/invtracker/client/model/APIKey.java b/app/src/main/java/rs/chir/invtracker/client/model/APIKey.java index 18cd697..b5d3e33 100644 --- a/app/src/main/java/rs/chir/invtracker/client/model/APIKey.java +++ b/app/src/main/java/rs/chir/invtracker/client/model/APIKey.java @@ -7,7 +7,6 @@ import androidx.datastore.preferences.core.Preferences; import androidx.datastore.preferences.core.PreferencesKeys; import io.reactivex.rxjava3.core.Single; -import rs.chir.compat.java.util.Objects; import rs.chir.compat.java.util.Optional; import rs.chir.invtracker.client.Application; @@ -25,7 +24,7 @@ public enum APIKey { * Sets the API key in the preferences datastore. * * @param context the context to use for accessing the preferences datastore. - * @param key the API key to set. + * @param key the API key to set. * @return a {@link Single} that completes when the API key is set. */ @NonNull diff --git a/app/src/main/java/rs/chir/invtracker/client/model/ItemListAdapter.java b/app/src/main/java/rs/chir/invtracker/client/model/ItemListAdapter.java index 2ecfd76..de498b7 100644 --- a/app/src/main/java/rs/chir/invtracker/client/model/ItemListAdapter.java +++ b/app/src/main/java/rs/chir/invtracker/client/model/ItemListAdapter.java @@ -14,13 +14,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.schedulers.Schedulers; import rs.chir.compat.java.util.Optional; -import rs.chir.invtracker.api.Client; import rs.chir.invtracker.client.Application; import rs.chir.invtracker.client.R; import rs.chir.invtracker.model.GeoRect; @@ -56,7 +53,7 @@ public class ItemListAdapter extends RecyclerView.Adapter bounds) { Application.getInstance(context) @@ -88,7 +85,7 @@ public class ItemListAdapter extends RecyclerView.Adapter client.streamLocations(itemId).toObservable()) + .getClient().toObservable().flatMap(client -> client.streamLocations(itemId).toObservable()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(item -> { @@ -83,7 +80,8 @@ public class LocationListAdapter extends RecyclerView.Adapter[trunc(val), trunc(val) + 1). + * * @param val the value to randomize. * @return the randomized value. */ @@ -86,6 +91,7 @@ public class LocationRounder { /** * Performs the rounding operation. + * * @param val the value to round. * @return the rounded value. */ @@ -100,6 +106,7 @@ public class LocationRounder { /** * Rounds a {@link GeoLocation}. + * * @param location the location to round. * @return the rounded location. */ diff --git a/app/src/main/java/rs/chir/invtracker/client/model/ServerPublicKey.java b/app/src/main/java/rs/chir/invtracker/client/model/ServerPublicKey.java index aa846b1..2f50ab7 100644 --- a/app/src/main/java/rs/chir/invtracker/client/model/ServerPublicKey.java +++ b/app/src/main/java/rs/chir/invtracker/client/model/ServerPublicKey.java @@ -27,6 +27,7 @@ public enum ServerPublicKey { /** * Returns a flowable for the serialized public key. + * * @param context the context to use for accessing the preferences datastore. * @return a flowable for the serialized public key. */ diff --git a/app/src/main/java/rs/chir/invtracker/utils/BitmapStorage.java b/app/src/main/java/rs/chir/invtracker/utils/BitmapStorage.java index 4f868f8..2f836e4 100644 --- a/app/src/main/java/rs/chir/invtracker/utils/BitmapStorage.java +++ b/app/src/main/java/rs/chir/invtracker/utils/BitmapStorage.java @@ -14,6 +14,7 @@ import java.io.IOException; public interface BitmapStorage { /** * Creates an instance of {@link BitmapStorage} optimized for the current android version. + * * @param context The application context. */ @NonNull @@ -27,7 +28,8 @@ public interface BitmapStorage { /** * Saves the bitmap to the storage. - * @param bitmap The bitmap to save. + * + * @param bitmap The bitmap to save. * @param fileName The name of the file to save the bitmap to. * @throws IOException If the bitmap cannot be saved. */ diff --git a/app/src/main/java/rs/chir/invtracker/utils/BitmapStorageQ.java b/app/src/main/java/rs/chir/invtracker/utils/BitmapStorageQ.java index 24db1bc..2c4de3c 100644 --- a/app/src/main/java/rs/chir/invtracker/utils/BitmapStorageQ.java +++ b/app/src/main/java/rs/chir/invtracker/utils/BitmapStorageQ.java @@ -26,6 +26,7 @@ public class BitmapStorageQ implements BitmapStorage { /** * Creates a new instance of {@link BitmapStorageQ}. + * * @param context the context to use. */ public BitmapStorageQ(@NonNull Context context) { diff --git a/app/src/main/java/rs/chir/invtracker/utils/BitmapUtils.java b/app/src/main/java/rs/chir/invtracker/utils/BitmapUtils.java index e989f6e..a14537f 100644 --- a/app/src/main/java/rs/chir/invtracker/utils/BitmapUtils.java +++ b/app/src/main/java/rs/chir/invtracker/utils/BitmapUtils.java @@ -17,9 +17,10 @@ public enum BitmapUtils { /** * Saves the bitmap to file + * * @param bitmap The bitmap to save - * @param file The file to save to - * @throws IOException If the file cannot be saved + * @param file The file to save to + * @throws IOException If the file cannot be saved * @throws FileNotFoundException If the file cannot be found */ public static void saveBitmap(@NonNull Bitmap bitmap, @NonNull String fileName) throws FileNotFoundException, IOException { @@ -28,8 +29,9 @@ public enum BitmapUtils { /** * Saves the bitmap to an output stream + * * @param bitmap The bitmap to save - * @param os The output stream to save to + * @param os The output stream to save to * @throws IOException If the file cannot be saved */ public static void saveBitmap(@NonNull Bitmap bitmap, @NonNull OutputStream os) throws IOException { diff --git a/app/src/main/java/rs/chir/invtracker/utils/CallAdapter.java b/app/src/main/java/rs/chir/invtracker/utils/CallAdapter.java deleted file mode 100644 index 9d6f8af..0000000 --- a/app/src/main/java/rs/chir/invtracker/utils/CallAdapter.java +++ /dev/null @@ -1,47 +0,0 @@ -package rs.chir.invtracker.utils; - -import java.io.IOException; -import java.util.List; - -import io.reactivex.rxjava3.annotations.NonNull; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.core.SingleObserver; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Response; - -/** - * Adapts a {@link Call} to a {@link Single}. - */ -public class CallAdapter extends Single implements Callback { - /** - * Subscribers to the call. - */ - private final List> subscribers = new java.util.ArrayList<>(1); - - /** - * Creates a new adapter. - */ - public CallAdapter(@androidx.annotation.NonNull Call call) { - call.enqueue(this); - } - - @Override - protected void subscribeActual(@NonNull SingleObserver observer) { - subscribers.add(observer); - } - - @Override - public void onFailure(@androidx.annotation.NonNull Call call, @androidx.annotation.NonNull IOException e) { - for (var subscriber : subscribers) { - subscriber.onError(e); - } - } - - @Override - public void onResponse(@androidx.annotation.NonNull Call call, @androidx.annotation.NonNull Response response) throws IOException { - for (var subscriber : subscribers) { - subscriber.onSuccess(response); - } - } -} diff --git a/app/src/main/java/rs/chir/invtracker/utils/ConnectionScorer.java b/app/src/main/java/rs/chir/invtracker/utils/ConnectionScorer.java index 94a903c..487f494 100644 --- a/app/src/main/java/rs/chir/invtracker/utils/ConnectionScorer.java +++ b/app/src/main/java/rs/chir/invtracker/utils/ConnectionScorer.java @@ -15,6 +15,7 @@ public enum ConnectionScorer { /** * Check if the data saver is enabled + * * @param cm the connectivity manager * @return true if the data saver is enabled */ @@ -27,6 +28,7 @@ public enum ConnectionScorer { /** * Scores the connection quality. + * * @param context * @return the connection quality score */ diff --git a/app/src/main/java/rs/chir/invtracker/utils/CursorStreamable.java b/app/src/main/java/rs/chir/invtracker/utils/CursorStreamable.java deleted file mode 100644 index d90c64e..0000000 --- a/app/src/main/java/rs/chir/invtracker/utils/CursorStreamable.java +++ /dev/null @@ -1,84 +0,0 @@ -package rs.chir.invtracker.utils; - -import org.reactivestreams.Subscriber; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.reactivex.rxjava3.annotations.NonNull; -import io.reactivex.rxjava3.core.Flowable; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.schedulers.Schedulers; -import rs.chir.compat.java.util.Optional; -import rs.chir.compat.java.util.function.Function; -import rs.chir.invtracker.model.Cursor; -import rs.chir.utils.xml.XMLSerializable; - -/** - * Adapts a {@link Cursor} to a {@link Flowable}. - */ -public class CursorStreamable extends Flowable { - /** - * Subscribers - */ - private final List> subscribers = new ArrayList<>(1); - /** - * The cursor supplier - */ - private final Function, Single>> cursorSupplier; - /** - * Whether the loading has started. We don’t start streaming data until the first subscriber is - * added. - */ - private final AtomicBoolean isLoading = new AtomicBoolean(false); - - /** - * Creates a new streamable. - * @param cursorSupplier the cursor supplier - */ - public CursorStreamable(@NonNull Function, Single>> cursorSupplier) { - this.cursorSupplier = cursorSupplier; - } - - @Override - protected void subscribeActual(@NonNull Subscriber subscriber) { - subscribers.add(subscriber); - if (this.isLoading.compareAndSet(false, true)) { - this.loadNextChunk(Optional.empty()); - } - } - - /** - * Loads the next chunk of data. - * @param lastId the ID of the last item in the previous chunk - */ - private void loadNextChunk(Optional lastId) { - this.cursorSupplier.apply(lastId) - .subscribeOn(Schedulers.io()) - .subscribe(cursor -> { - for (var item : cursor.items()) { - // send all items to all subscribers - for (var subscriber : subscribers) { - subscriber.onNext(item); - } - } - // While the nextId should be empty if there are no more items, - // some places may still return it, so we check for how many items are in the cursor. - if (cursor.nextId().isEmpty() || cursor.items().isEmpty()) { - // no more items to load, tell that to all subscribers - for (var subscriber : subscribers) { - subscriber.onComplete(); - } - } else { - // otherwise, load the next chunk - this.loadNextChunk(cursor.nextId()); - } - }, t -> { - // notify all subscribers of the error - for (var subscriber : subscribers) { - subscriber.onError(t); - } - }); - } -} diff --git a/app/src/main/java/rs/chir/invtracker/utils/ListenableFutureAdapter.java b/app/src/main/java/rs/chir/invtracker/utils/ListenableFutureAdapter.java deleted file mode 100644 index fab00c4..0000000 --- a/app/src/main/java/rs/chir/invtracker/utils/ListenableFutureAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package rs.chir.invtracker.utils; - -import androidx.annotation.NonNull; - -import com.google.common.util.concurrent.ListenableFuture; - -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; - -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.core.SingleObserver; - -/** - * Adapts a {@link ListenableFuture} to a {@link Single}. - */ -public class ListenableFutureAdapter extends Single { - /** - * The subscribers that are waiting for the future to complete. - */ - private final List> subscribers = new java.util.ArrayList<>(1); - - public ListenableFutureAdapter(@NonNull ListenableFuture future, @NonNull Executor executor) { - future.addListener(() -> { - try { - var result = future.get(); - for (var subscriber : subscribers) { - subscriber.onSuccess(result); - } - } catch (InterruptedException | ExecutionException e) { - for (var subscriber : subscribers) { - subscriber.onError(e); - } - } - }, executor); - } - - @Override - protected void subscribeActual(@NonNull SingleObserver observer) { - subscribers.add(observer); - } -} diff --git a/app/src/main/java/rs/chir/invtracker/utils/RXJavaAdapters.java b/app/src/main/java/rs/chir/invtracker/utils/RXJavaAdapters.java new file mode 100644 index 0000000..6a8038d --- /dev/null +++ b/app/src/main/java/rs/chir/invtracker/utils/RXJavaAdapters.java @@ -0,0 +1,105 @@ +package rs.chir.invtracker.utils; + +import androidx.annotation.NonNull; + +import com.google.android.gms.tasks.Task; +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; + +import io.reactivex.rxjava3.core.BackpressureStrategy; +import io.reactivex.rxjava3.core.Flowable; +import io.reactivex.rxjava3.core.FlowableEmitter; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.schedulers.Schedulers; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; +import rs.chir.compat.java.util.Optional; +import rs.chir.compat.java.util.function.Function; +import rs.chir.invtracker.model.Cursor; +import rs.chir.utils.xml.XMLSerializable; + +/** + * Various adapters to convert to {@link Single}s or {@link Flowable}s. + */ +public enum RXJavaAdapters { + ; + + /** + * Adapts a {@link Call} to a {@link Single}. + * + * @param call the call to adapt + * @return the single + */ + @NonNull + public static Single fromCall(@NonNull Call call) { + return Single.create(subscriber -> call.enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + subscriber.onError(e); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + subscriber.onSuccess(response); + } + })); + } + + private static void loadNextChunk(@NonNull Function, Single>> cursorSupplier, @NonNull FlowableEmitter subscriber, Optional lastId) { + var d = cursorSupplier.apply(lastId) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe(cursor -> { + for (var item : cursor.items()) { + subscriber.onNext(item); + } + if (cursor.nextId().isEmpty() || cursor.items().isEmpty()) { + subscriber.onComplete(); + } else { + RXJavaAdapters.loadNextChunk(cursorSupplier, subscriber, cursor.nextId()); + } + }, subscriber::onError); + subscriber.setDisposable(d); + } + + @NonNull + public static Flowable fromCursor(@NonNull Function, Single>> cursorSupplier) { + return Flowable.create(subscriber -> { + RXJavaAdapters.loadNextChunk(cursorSupplier, subscriber, Optional.empty()); + }, BackpressureStrategy.BUFFER); + } + + @NonNull + public static Single fromListenableFuture(@NonNull ListenableFuture future, @NonNull Executor executor) { + return Single.create(subscriber -> { + future.addListener(() -> { + try { + var result = future.get(); + subscriber.onSuccess(result); + } catch (InterruptedException | ExecutionException e) { + subscriber.onError(e); + } + }, executor); + }); + } + + @NonNull + public static Single> fromTask(@NonNull Task task) { + if (task.isComplete()) { + if (task.isSuccessful()) { + return Single.just(Optional.of(task.getResult())); + } else { + return Single.error(task.getException()); + } + } else { + return Single.create(subscriber -> { + task.addOnSuccessListener(v -> subscriber.onSuccess(Optional.ofNullable(v))); + task.addOnFailureListener(subscriber::onError); + }); + } + } +} diff --git a/app/src/main/java/rs/chir/invtracker/utils/SingleBackoff.java b/app/src/main/java/rs/chir/invtracker/utils/SingleBackoff.java index 9d94603..d363361 100644 --- a/app/src/main/java/rs/chir/invtracker/utils/SingleBackoff.java +++ b/app/src/main/java/rs/chir/invtracker/utils/SingleBackoff.java @@ -55,7 +55,7 @@ public class SingleBackoff extends Single { /** * Creates a new single backoff. * - * @param singleSupplier the supplier of the single to retry + * @param singleSupplier the supplier of the single to retry * @param exceptionFilter the exception filter for the single. If it returns true, the single is retried. */ public SingleBackoff(@androidx.annotation.NonNull Supplier> singleSupplier, @NonNull Predicate exceptionFilter) { @@ -87,6 +87,7 @@ public class SingleBackoff extends Single { /** * Reports the success of the single + * * @param result the result of the single */ private void onSuccess(T result) { @@ -98,6 +99,7 @@ public class SingleBackoff extends Single { /** * Reports the error of the single + * * @param error the error of the single */ private void onError(Throwable error) { diff --git a/app/src/main/java/rs/chir/invtracker/utils/SingleLocation.java b/app/src/main/java/rs/chir/invtracker/utils/SingleLocation.java index 63c75e0..e8982f2 100644 --- a/app/src/main/java/rs/chir/invtracker/utils/SingleLocation.java +++ b/app/src/main/java/rs/chir/invtracker/utils/SingleLocation.java @@ -17,11 +17,8 @@ import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.Priority; import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.core.SingleObserver; import rs.chir.compat.java.util.OptionalDouble; import rs.chir.invtracker.client.model.LocationRounder; import rs.chir.invtracker.model.GeoLocation; @@ -29,44 +26,14 @@ import rs.chir.invtracker.model.GeoLocation; /** * {@link Single} implementation that returns a location */ -public class SingleLocation extends Single { - /** - * List of subscribers to this single. - */ - final List> observers = new ArrayList<>(1); - - /** - * Creates a new Location Single - * - * @param client the location client to use for getting the location - * @param locationRequest the location request to use for getting the location - * @param context the context to use for rounding the location - */ - @SuppressLint("MissingPermission") - private SingleLocation(@NonNull FusedLocationProviderClient client, LocationRequest request, Context context) { - // Request a location update - client.requestLocationUpdates(request, new LocationCallback() { - @Override - public void onLocationResult(LocationResult locationResult) { - var location = locationResult.getLastLocation(); - if (location != null) { - for (var observer : observers) { - observer.onSuccess(SingleLocation.fromAndroidLocation(location, context)); - } - } else { - for (var observer : observers) { - observer.onError(new AndroidException("No location available")); - } - } - } - }, Looper.getMainLooper()); - } +public enum SingleLocation { + ; /** * Converts an Android location to a GeoLocation * * @param location the location to convert - * @param context the context to use for rounding the location + * @param context the context to use for rounding the location * @return the converted location */ @NonNull @@ -85,7 +52,8 @@ public class SingleLocation extends Single { /** * Retrieve the most recent location, requesting a location update if necessary. - * @param client the location client to use for getting the location + * + * @param client the location client to use for getting the location * @param context the context to use for rounding the location * @return the most recent location */ @@ -93,23 +61,25 @@ public class SingleLocation extends Single { @SuppressLint("MissingPermission") public static Single getSingleLocation(@NonNull FusedLocationProviderClient client, @NonNull Context context) { // Try accessing the last location - return TaskAdapter.fromTask(client.getLastLocation()) + return RXJavaAdapters.fromTask(client.getLastLocation()) .flatMap(location -> { if (location.isPresent()) { return Single.just(SingleLocation.fromAndroidLocation(location.get(), context)); } else { // if there is no last location, request a location update - return SingleLocation.getNextLocation(client, context); + return SingleLocation.getNextLocation(client, context); } }); } /** * Retrieves a location update, requesting a location update if necessary. - * @param client the location client to use for getting the location + * + * @param client the location client to use for getting the location * @param context the context to use for rounding the location * @return the next location */ + @SuppressLint("MissingPermission") @NonNull public static Single getNextLocation(@NonNull FusedLocationProviderClient client, @NonNull Context context) { var request = LocationRequest.create(); @@ -122,12 +92,19 @@ public class SingleLocation extends Single { .addLocationRequest(request); // TODO: you could probably cache the settings client? var settingsClient = LocationServices.getSettingsClient(context); - return TaskAdapter.fromTask(settingsClient.checkLocationSettings(builder.build())) - .flatMap(resp -> new SingleLocation(client, request, context)); - } - - @Override - protected void subscribeActual(@io.reactivex.rxjava3.annotations.NonNull SingleObserver observer) { - observers.add(observer); + return RXJavaAdapters.fromTask(settingsClient.checkLocationSettings(builder.build())) + .flatMap(resp -> Single.create(subscriber -> { + client.requestLocationUpdates(request, new LocationCallback() { + @Override + public void onLocationResult(@NonNull LocationResult locationResult) { + var location = locationResult.getLastLocation(); + if (location != null) { + subscriber.onSuccess(SingleLocation.fromAndroidLocation(location, context)); + } else { + subscriber.onError(new AndroidException("No location available")); + } + } + }, Looper.getMainLooper()); + })); } } diff --git a/app/src/main/java/rs/chir/invtracker/utils/TaskAdapter.java b/app/src/main/java/rs/chir/invtracker/utils/TaskAdapter.java deleted file mode 100644 index 597b6f1..0000000 --- a/app/src/main/java/rs/chir/invtracker/utils/TaskAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -package rs.chir.invtracker.utils; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.google.android.gms.tasks.Task; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.core.SingleObserver; -import rs.chir.compat.java.util.Optional; - -/** - * Single adapter for a GMS {@link Task}; - * @param the type of the result of the task. - */ -public class TaskAdapter extends Single> { - /** - * The subscribers to the task. - */ - private final List>> observers = new ArrayList<>(1); - - /** - * Creates a new adapter. - * @param task the task to wrap. - */ - private TaskAdapter(@NonNull Task task) { - task.addOnCompleteListener(result -> { - Log.i("TaskAdapter", "Task completed: " + result); - if (result.isSuccessful()) { - // rxjava does not like nulls, so we use Optional.empty() instead. - var out = Optional.ofNullable(result.getResult()); - for (var observer : observers) { - observer.onSuccess(out); - } - } else { - for (var observer : observers) { - observer.onError(result.getException()); - } - } - }); - } - - /** - * Creates a new adapter from a task - * - * @param task the task to wrap. - * @param the type of the result of the task. - * @return the adapter. - */ - @NonNull - public static Single> fromTask(@NonNull Task task) { - if (task.isComplete()) { - // if the task is successful, we don’t need to wrap it in an adapter. - if (task.isSuccessful()) { - return Single.just(Optional.ofNullable(task.getResult())); - } else { - return Single.error(Objects.requireNonNull(task.getException())); - } - } else { - return new TaskAdapter<>(task); - } - } - - @Override - protected void subscribeActual(@io.reactivex.rxjava3.annotations.NonNull SingleObserver> observer) { - observers.add(observer); - } -} diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index 54634a5..f6264a4 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,4 +1,6 @@ - + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 57c9193..3b5e2d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Wed Aug 10 11:16:39 GMT 2022 +#Mon Aug 15 10:52:08 GMT 2022 distributionBase=GRADLE_USER_HOME distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip distributionPath=wrapper/dists