Skip to content

Commit cd5ef26

Browse files
WIP
1 parent 4b1d1f4 commit cd5ef26

11 files changed

Lines changed: 490 additions & 355 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ dependencies {
9494
implementation("androidx.preference:preference:1.2.1")
9595
implementation("com.google.android.material:material:1.12.0")
9696
implementation("com.github.yalantis:ucrop:2.2.9")
97+
implementation("androidx.work:work-runtime:2.9.0")
9798
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
9899

99100
// Splash Screen

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
1313

1414
<uses-permission android:name="android.permission.CAMERA" />
15+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
16+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
1517
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
1618

1719
<uses-feature
@@ -188,5 +190,6 @@
188190
<action android:name="android.service.controls.ControlsProviderService" />
189191
</intent-filter>
190192
</service>
193+
<service android:name=".importexport.ImportExportWorker"/>
191194
</application>
192195
</manifest>

app/src/main/java/protect/card_locker/ImportExportActivity.java

Lines changed: 91 additions & 144 deletions
Large diffs are not rendered by default.

app/src/main/java/protect/card_locker/ImportExportTask.java

Lines changed: 0 additions & 143 deletions
This file was deleted.

app/src/main/java/protect/card_locker/MainActivity.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,29 @@
2626
import androidx.appcompat.widget.SearchView;
2727
import androidx.core.splashscreen.SplashScreen;
2828
import androidx.recyclerview.widget.RecyclerView;
29+
import androidx.work.Data;
30+
import androidx.work.WorkInfo;
31+
import androidx.work.WorkManager;
2932

3033
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
3134
import com.google.android.material.floatingactionbutton.FloatingActionButton;
35+
import com.google.android.material.snackbar.Snackbar;
3236
import com.google.android.material.tabs.TabLayout;
3337

3438
import java.io.UnsupportedEncodingException;
3539
import java.util.ArrayList;
3640
import java.util.Arrays;
3741
import java.util.Collections;
3842
import java.util.List;
43+
import java.util.Objects;
44+
import java.util.concurrent.ExecutionException;
3945
import java.util.concurrent.atomic.AtomicInteger;
4046

4147
import protect.card_locker.databinding.ContentMainBinding;
4248
import protect.card_locker.databinding.MainActivityBinding;
4349
import protect.card_locker.databinding.SortingOptionBinding;
50+
import protect.card_locker.importexport.DataFormat;
51+
import protect.card_locker.importexport.ImportExportWorker;
4452
import protect.card_locker.preferences.SettingsActivity;
4553

4654
public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCardCursorAdapter.CardAdapterListener {
@@ -71,6 +79,7 @@ public class MainActivity extends CatimaAppCompatActivity implements LoyaltyCard
7179

7280
private ActivityResultLauncher<Intent> mBarcodeScannerLauncher;
7381
private ActivityResultLauncher<Intent> mSettingsLauncher;
82+
private ActivityResultLauncher<Intent> mImportExportLauncher;
7483

7584
private ActionMode.Callback mCurrentActionModeCallback = new ActionMode.Callback() {
7685
@Override
@@ -304,6 +313,69 @@ public void onClick(DialogInterface dialog, int whichButton) {
304313
}
305314
});
306315

316+
mImportExportLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
317+
// User didn't ask for import or export
318+
if (result.getResultCode() != RESULT_OK) {
319+
return;
320+
}
321+
322+
// Watch for active imports/exports
323+
new Thread(() -> {
324+
WorkManager workManager = WorkManager.getInstance(MainActivity.this);
325+
326+
Snackbar importRunning = Snackbar.make(binding.getRoot(), R.string.importing, Snackbar.LENGTH_INDEFINITE);
327+
328+
while (true) {
329+
try {
330+
List<WorkInfo> activeImports = workManager.getWorkInfosForUniqueWork(ImportExportWorker.ACTION_IMPORT).get();
331+
332+
// We should only have one import running at a time, so it should be safe to always grab the latest
333+
WorkInfo activeImport = activeImports.get(activeImports.size() - 1);
334+
WorkInfo.State importState = activeImport.getState();
335+
336+
if (importState == WorkInfo.State.RUNNING || importState == WorkInfo.State.ENQUEUED || importState == WorkInfo.State.BLOCKED) {
337+
importRunning.show();
338+
} else if (importState == WorkInfo.State.SUCCEEDED) {
339+
importRunning.dismiss();
340+
runOnUiThread(() -> {
341+
Toast.makeText(getApplicationContext(), getString(R.string.importSuccessful), Toast.LENGTH_LONG).show();
342+
updateLoyaltyCardList(true);
343+
});
344+
345+
break;
346+
} else {
347+
importRunning.dismiss();
348+
349+
Data outputData = activeImport.getOutputData();
350+
351+
// 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
352+
// We want to get back into this function
353+
// A cheap fix would be to keep looping but if the user dismissed the dialog that could mean we're looping forever...
354+
if (Objects.equals(outputData.getString(ImportExportWorker.OUTPUT_ERROR_REASON), ImportExportWorker.ERROR_PASSWORD_REQUIRED)) {
355+
runOnUiThread(() -> ImportExportActivity.retryWithPassword(
356+
MainActivity.this,
357+
DataFormat.valueOf(outputData.getString(ImportExportWorker.INPUT_FORMAT)),
358+
Uri.parse(outputData.getString(ImportExportWorker.INPUT_URI))
359+
));
360+
} else {
361+
runOnUiThread(() -> {
362+
Toast.makeText(getApplicationContext(), getString(R.string.importFailed), Toast.LENGTH_LONG).show();
363+
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_REASON), Toast.LENGTH_LONG).show();
364+
Toast.makeText(getApplicationContext(), activeImport.getOutputData().getString(ImportExportWorker.OUTPUT_ERROR_DETAILS), Toast.LENGTH_LONG).show();
365+
});
366+
}
367+
368+
break;
369+
}
370+
} catch (ExecutionException e) {
371+
throw new RuntimeException(e);
372+
} catch (InterruptedException e) {
373+
throw new RuntimeException(e);
374+
}
375+
}
376+
}).start();
377+
});
378+
307379
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
308380
@Override
309381
public void handleOnBackPressed() {
@@ -641,7 +713,7 @@ public boolean onOptionsItemSelected(MenuItem inputItem) {
641713

642714
if (id == R.id.action_import_export) {
643715
Intent i = new Intent(getApplicationContext(), ImportExportActivity.class);
644-
startActivity(i);
716+
mImportExportLauncher.launch(i);
645717
return true;
646718
}
647719

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package protect.card_locker;
2+
3+
import static android.content.Context.NOTIFICATION_SERVICE;
4+
5+
import android.app.Notification;
6+
import android.app.NotificationChannel;
7+
import android.app.NotificationManager;
8+
import android.content.Context;
9+
10+
import androidx.annotation.NonNull;
11+
import androidx.annotation.Nullable;
12+
13+
public class NotificationHelper {
14+
15+
// Do not change these IDs!
16+
public static final String CHANNEL_IMPORT = "import";
17+
18+
public static final String CHANNEL_EXPORT = "export";
19+
20+
public static final int IMPORT_ID = 100;
21+
public static final int IMPORT_PROGRESS_ID = 101;
22+
public static final int EXPORT_ID = 103;
23+
public static final int EXPORT_PROGRESS_ID = 104;
24+
25+
26+
public static Notification.Builder createNotificationBuilder(@NonNull Context context, @NonNull String channel, @NonNull int icon, @NonNull String title, @Nullable String message) {
27+
Notification.Builder notificationBuilder = new Notification.Builder(context)
28+
.setSmallIcon(icon)
29+
.setTicker(title)
30+
.setContentTitle(title);
31+
32+
if (message != null) {
33+
notificationBuilder.setContentText(message);
34+
}
35+
36+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
37+
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
38+
NotificationChannel notificationChannel = new NotificationChannel(channel, getChannelName(channel), NotificationManager.IMPORTANCE_DEFAULT);
39+
notificationManager.createNotificationChannel(notificationChannel);
40+
41+
notificationBuilder.setChannelId(channel);
42+
}
43+
44+
return notificationBuilder;
45+
}
46+
47+
public static void sendNotification(@NonNull Context context, @NonNull int notificationId, @NonNull Notification notification) {
48+
NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
49+
50+
notificationManager.notify(notificationId, notification);
51+
}
52+
53+
private static String getChannelName(@NonNull String channel) {
54+
switch(channel) {
55+
case CHANNEL_IMPORT:
56+
return "Import";
57+
case CHANNEL_EXPORT:
58+
return "Export";
59+
default:
60+
throw new IllegalArgumentException("Unknown notification channel");
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)