Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
TheLastProject committed Apr 5, 2024
1 parent 6ed5e52 commit bd8ce13
Show file tree
Hide file tree
Showing 10 changed files with 491 additions and 147 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ dependencies {
implementation("androidx.preference:preference:1.2.1")
implementation("com.google.android.material:material:1.11.0")
implementation("com.github.yalantis:ucrop:2.2.8")
implementation("androidx.work:work-runtime:2.9.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")

// Splash Screen
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />

<uses-feature
Expand Down Expand Up @@ -186,5 +188,6 @@
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
<service android:name=".importexport.ImportExportWorker"/>
</application>
</manifest>
235 changes: 91 additions & 144 deletions app/src/main/java/protect/card_locker/ImportExportActivity.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public ImportExportResult call() {
return doInBackground();
}

interface TaskCompleteListener {
public interface TaskCompleteListener {
void onTaskComplete(ImportExportResult result, DataFormat format);
}

Expand Down
74 changes: 73 additions & 1 deletion app/src/main/java/protect/card_locker/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,29 @@
import androidx.appcompat.widget.SearchView;
import androidx.core.splashscreen.SplashScreen;
import androidx.recyclerview.widget.RecyclerView;
import androidx.work.Data;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import protect.card_locker.databinding.ContentMainBinding;
import protect.card_locker.databinding.MainActivityBinding;
import protect.card_locker.databinding.SortingOptionBinding;
import protect.card_locker.importexport.DataFormat;
import protect.card_locker.importexport.ImportExportWorker;
import protect.card_locker.preferences.SettingsActivity;

public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
Expand Down Expand Up @@ -73,6 +81,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard

private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
private ActivityResultLauncher<Intent> mSettingsLauncher;
private ActivityResultLauncher<Intent> mImportExportLauncher;

private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
@Override
Expand Down Expand Up @@ -304,6 +313,69 @@ public void onClick(DialogInterface dialog, int whichButton) {
}
});

mImportExportLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
// User didn't ask for import or export
if (result.getResultCode() != RESULT_OK) {
return;
}

// Watch for active imports/exports
new Thread(() -> {
WorkManager workManager = WorkManager.getInstance(MainActivity.this);

Snackbar importRunning = Snackbar.make(binding.getRoot(), R.string.importing, Snackbar.LENGTH_INDEFINITE);

while (true) {
try {
List<WorkInfo> activeImports = workManager.getWorkInfosForUniqueWork(ImportExportWorker.ACTION_IMPORT).get();

// We should only have one import running at a time, so it should be safe to always grab the latest
WorkInfo activeImport = activeImports.get(activeImports.size() - 1);
WorkInfo.State importState = activeImport.getState();

if (importState == WorkInfo.State.RUNNING || importState == WorkInfo.State.ENQUEUED || importState == WorkInfo.State.BLOCKED) {
importRunning.show();
} else if (importState == WorkInfo.State.SUCCEEDED) {
importRunning.dismiss();
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), getString(R.string.importSuccessful), Toast.LENGTH_LONG).show();
updateLoyaltyCardList(true);
});

break;
} else {
importRunning.dismiss();

Data outputData = activeImport.getOutputData();

// FIXME: This dialog will asynchronously be accepted or declined and we don't know the status of it so we can't show the import state
// We want to get back into this function
// A cheap fix would be to keep looping but if the user dismissed the dialog that could mean we're looping forever...
if (Objects.equals(outputData.getString(ImportExportWorker.OUTPUT_ERROR_REASON), ImportExportWorker.ERROR_PASSWORD_REQUIRED)) {
runOnUiThread(() -> ImportExportActivity.retryWithPassword(
MainActivity.this,
DataFormat.valueOf(outputData.getString(ImportExportWorker.INPUT_FORMAT)),
Uri.parse(outputData.getString(ImportExportWorker.INPUT_URI))
));
} else {
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(), getString(R.string.importFailed), Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_REASON), Toast.LENGTH_LONG).show();
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_DETAILS), Toast.LENGTH_LONG).show();
});
}

break;
}
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
});

getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
Expand Down Expand Up @@ -645,7 +717,7 @@ public boolean onOptionsItemSelected(MenuItem inputItem) {

if (id == R.id.action_import_export) {
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
startActivity(i);
mImportExportLauncher.launch(i);
return true;
}

Expand Down
63 changes: 63 additions & 0 deletions app/src/main/java/protect/card_locker/NotificationHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package protect.card_locker;

import static android.content.Context.NOTIFICATION_SERVICE;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public class NotificationHelper {

// Do not change these IDs!
public static final String CHANNEL_IMPORT = "import";

public static final String CHANNEL_EXPORT = "export";

public static final int IMPORT_ID = 100;
public static final int IMPORT_PROGRESS_ID = 101;
public static final int EXPORT_ID = 103;
public static final int EXPORT_PROGRESS_ID = 104;


public static Notification.Builder createNotificationBuilder(@NonNull Context context, @NonNull String channel, @NonNull int icon, @NonNull String title, @Nullable String message) {
Notification.Builder notificationBuilder = new Notification.Builder(context)
.setSmallIcon(icon)
.setTicker(title)
.setContentTitle(title);

if (message != null) {
notificationBuilder.setContentText(message);
}

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel(channel, getChannelName(channel), NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(notificationChannel);

notificationBuilder.setChannelId(channel);
}

return notificationBuilder;
}

public static void sendNotification(@NonNull Context context, @NonNull int notificationId, @NonNull Notification notification) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);

notificationManager.notify(notificationId, notification);
}

private static String getChannelName(@NonNull String channel) {
switch(channel) {
case CHANNEL_IMPORT:
return "Import";
case CHANNEL_EXPORT:
return "Export";
default:
throw new IllegalArgumentException("Unknown notification channel");
}
}
}
43 changes: 43 additions & 0 deletions app/src/main/java/protect/card_locker/PermissionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ public static boolean needsCameraPermission(Activity activity) {
return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED;
}

/**
* Check if post notifications permission is needed
*
* @param activity
* @return
*/
public static boolean needsPostNotificationsPermission(Activity activity) {
return ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED;
}

/**
* Call onRequestPermissionsResult after storage read permission was granted.
* Mocks a successful grant if a grant is not necessary.
Expand Down Expand Up @@ -91,4 +101,37 @@ public static void requestCameraPermission(CatimaAppCompatActivity activity, int
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}


/**
* Call onRequestPermissionsResult after notification permission was granted.
* Mocks a successful grant if a grant is not necessary.
*
* @param activity
* @param requestCode
*/
public static void requestPostNotificationsPermission(CatimaAppCompatActivity activity, int requestCode) {
int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED };

if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
String[] permissions = new String[0];
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
return;
}

String[] permissions = new String[]{ Manifest.permission.POST_NOTIFICATIONS};

if (needsPostNotificationsPermission(activity)) {
ActivityCompat.requestPermissions(activity, permissions, requestCode);
} else {
// FIXME: This points to onMockedRequestPermissionResult instead of to
// onRequestPermissionResult because onRequestPermissionResult was only introduced in
// Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too.
//
// When minSdk becomes 23, this should point to onRequestPermissionResult directly and
// the activity input variable should be changed from CatimaAppCompatActivity to
// Activity.
activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults);
}
}
}
Loading

0 comments on commit bd8ce13

Please sign in to comment.