android: switch to nativeactivity, fix clicks
This commit is contained in:
parent
98619cc362
commit
6bce9ec071
15 changed files with 1670 additions and 1756 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
*.iml
|
||||
android/build
|
||||
android/.idea
|
||||
android/.gradle
|
||||
android/local.properties
|
||||
|
|
3049
Cargo.lock
generated
3049
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -111,7 +111,7 @@ nokhwa-mac = { git = "https://github.com/l1npengtul/nokhwa", rev = "612c861ef153
|
|||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies]
|
||||
env_logger = "0.11.3"
|
||||
winit = { version = "0.30.7" }
|
||||
winit = { version = "0.30.11" }
|
||||
eframe = { version = "0.31.1", features = ["wgpu", "glow"] }
|
||||
arboard = "3.2.0"
|
||||
rfd = "0.15.0"
|
||||
|
@ -120,10 +120,9 @@ interprocess = { version = "2.2.1", features = ["tokio"] }
|
|||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.15.0"
|
||||
jni = "0.21.1"
|
||||
wgpu = "25.0.0"
|
||||
android-activity = { version = "0.6.0", features = ["game-activity"] }
|
||||
winit = { version = "0.30.7", features = ["android-game-activity"] }
|
||||
eframe = { version = "0.31.1", features = ["wgpu", "android-game-activity"] }
|
||||
android-activity = { version = "0.6.0", features = ["native-activity"] }
|
||||
winit = { version = "0.30.11", features = ["android-native-activity"] }
|
||||
eframe = { version = "0.31.1", default-features = false, features = ["glow", "android-native-activity"] }
|
||||
|
||||
[patch.crates-io]
|
||||
egui_extras = { git = "https://github.com/ardocrat/egui", branch = "back_button_android" }
|
||||
|
|
|
@ -3,14 +3,14 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
compileSdk 35
|
||||
ndkVersion '26.0.10792818'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "mw.gri.android"
|
||||
minSdk 24
|
||||
targetSdk 33
|
||||
versionCode 3
|
||||
targetSdk 35
|
||||
versionCode 4
|
||||
versionName "0.2.4"
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ android {
|
|||
storePassword keystoreProperties['storePassword']
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -54,14 +53,11 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
|
||||
// To use the Games Activity library
|
||||
implementation "androidx.games:games-activity:2.0.2"
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
|
||||
// Android Camera
|
||||
implementation 'androidx.camera:camera-core:1.2.3'
|
||||
implementation 'androidx.camera:camera-camera2:1.2.3'
|
||||
implementation 'androidx.camera:camera-lifecycle:1.2.3'
|
||||
implementation 'androidx.camera:camera-core:1.4.2'
|
||||
implementation 'androidx.camera:camera-camera2:1.4.2'
|
||||
implementation 'androidx.camera:camera-lifecycle:1.4.2'
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application
|
||||
android:hardwareAccelerated="true"
|
||||
|
@ -63,7 +64,11 @@
|
|||
|
||||
<meta-data android:name="android.app.lib_name" android:value="grim" />
|
||||
</activity>
|
||||
<service android:name=".BackgroundService" android:stopWithTask="true" />
|
||||
|
||||
<service
|
||||
android:name=".BackgroundService"
|
||||
android:stopWithTask="true"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -2,13 +2,13 @@ package mw.gri.android;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.*;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.*;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -32,25 +32,6 @@ public class BackgroundService extends Service {
|
|||
public static final String ACTION_START_NODE = "start_node";
|
||||
public static final String ACTION_STOP_NODE = "stop_node";
|
||||
public static final String ACTION_EXIT = "exit";
|
||||
public static final String ACTION_REFRESH = "refresh";
|
||||
public static final String ACTION_STOP = "stop";
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(ACTION_STOP)) {
|
||||
mStopped = true;
|
||||
// Remove actions buttons.
|
||||
mNotificationBuilder.mActions.clear();
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
manager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
|
||||
} else {
|
||||
mHandler.removeCallbacks(mUpdateSyncStatus);
|
||||
mHandler.post(mUpdateSyncStatus);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final Runnable mUpdateSyncStatus = new Runnable() {
|
||||
@SuppressLint("RestrictedApi")
|
||||
|
@ -170,9 +151,6 @@ public class BackgroundService extends Service {
|
|||
|
||||
// Update sync status at notification.
|
||||
mHandler.post(mUpdateSyncStatus);
|
||||
|
||||
// Register receiver to refresh notifications by intent.
|
||||
registerReceiver(mReceiver, new IntentFilter(ACTION_REFRESH));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -203,7 +181,6 @@ public class BackgroundService extends Service {
|
|||
|
||||
// Stop updating the notification.
|
||||
mHandler.removeCallbacks(mUpdateSyncStatus);
|
||||
unregisterReceiver(mReceiver);
|
||||
clearNotification();
|
||||
|
||||
// Remove service from foreground state.
|
||||
|
@ -226,12 +203,12 @@ public class BackgroundService extends Service {
|
|||
}
|
||||
|
||||
// Start the service.
|
||||
public static void start(Context context) {
|
||||
if (!isServiceRunning(context)) {
|
||||
public static void start(Context c) {
|
||||
if (!isServiceRunning(c)) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(new Intent(context, BackgroundService.class));
|
||||
ContextCompat.startForegroundService(c, new Intent(c, BackgroundService.class));
|
||||
} else {
|
||||
context.startService(new Intent(context, BackgroundService.class));
|
||||
c.startService(new Intent(c, BackgroundService.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package mw.gri.android;
|
|||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.NativeActivity;
|
||||
import android.content.*;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
|
@ -12,12 +13,9 @@ import android.os.Process;
|
|||
import android.provider.Settings;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.util.Size;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.camera.core.*;
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||
|
@ -27,37 +25,41 @@ import androidx.core.graphics.Insets;
|
|||
import androidx.core.view.DisplayCutoutCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import com.google.androidgamesdk.GameActivity;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static android.content.ClipDescription.MIMETYPE_TEXT_HTML;
|
||||
import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
|
||||
|
||||
public class MainActivity extends GameActivity {
|
||||
public static String STOP_APP_ACTION = "STOP_APP";
|
||||
public class MainActivity extends NativeActivity {
|
||||
private static final int FILE_PICK_REQUEST = 1001;
|
||||
private static final int FILE_PERMISSIONS_REQUEST = 1002;
|
||||
|
||||
private static final int NOTIFICATIONS_PERMISSION_CODE = 1;
|
||||
private static final int CAMERA_PERMISSION_CODE = 2;
|
||||
|
||||
public static final String STOP_APP_ACTION = "STOP_APP_ACTION";
|
||||
|
||||
static {
|
||||
System.loadLibrary("grim");
|
||||
}
|
||||
|
||||
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent i) {
|
||||
if (i.getAction().equals(STOP_APP_ACTION)) {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (Objects.equals(intent.getAction(), MainActivity.STOP_APP_ACTION)) {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ImageAnalysis mImageAnalysis = new ImageAnalysis.Builder()
|
||||
.setTargetResolution(new Size(640, 480))
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build();
|
||||
|
||||
|
@ -66,9 +68,6 @@ public class MainActivity extends GameActivity {
|
|||
private ExecutorService mCameraExecutor = null;
|
||||
private boolean mUseBackCamera = true;
|
||||
|
||||
private ActivityResultLauncher<Intent> mFilePickResult = null;
|
||||
private ActivityResultLauncher<Intent> mOpenFilePermissionsResult = null;
|
||||
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
@ -80,14 +79,15 @@ public class MainActivity extends GameActivity {
|
|||
}
|
||||
|
||||
// Clear cache on start.
|
||||
String cacheDir = Objects.requireNonNull(getExternalCacheDir()).getPath();
|
||||
if (savedInstanceState == null) {
|
||||
Utils.deleteDirectoryContent(new File(getExternalCacheDir().getPath()), false);
|
||||
Utils.deleteDirectoryContent(new File(cacheDir), false);
|
||||
}
|
||||
|
||||
// Setup environment variables for native code.
|
||||
try {
|
||||
Os.setenv("HOME", getExternalFilesDir("").getPath(), true);
|
||||
Os.setenv("XDG_CACHE_HOME", getExternalCacheDir().getPath(), true);
|
||||
Os.setenv("HOME", Objects.requireNonNull(getExternalFilesDir("")).getPath(), true);
|
||||
Os.setenv("XDG_CACHE_HOME", cacheDir, true);
|
||||
Os.setenv("ARTI_FS_DISABLE_PERMISSION_CHECKS", "true", true);
|
||||
} catch (ErrnoException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -95,54 +95,10 @@ public class MainActivity extends GameActivity {
|
|||
|
||||
super.onCreate(null);
|
||||
|
||||
// Register receiver to finish activity from the BackgroundService.
|
||||
registerReceiver(mBroadcastReceiver, new IntentFilter(STOP_APP_ACTION));
|
||||
|
||||
// Register associated file opening result.
|
||||
mOpenFilePermissionsResult = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
onFile();
|
||||
}
|
||||
} else if (result.getResultCode() == RESULT_OK) {
|
||||
onFile();
|
||||
}
|
||||
}
|
||||
);
|
||||
// Register file pick result.
|
||||
mFilePickResult = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
int resultCode = result.getResultCode();
|
||||
Intent data = result.getData();
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
String path = "";
|
||||
if (data != null) {
|
||||
Uri uri = data.getData();
|
||||
String name = "pick" + Utils.getFileExtension(uri, this);
|
||||
File file = new File(getExternalCacheDir(), name);
|
||||
try (InputStream is = getContentResolver().openInputStream(uri);
|
||||
OutputStream os = new FileOutputStream(file)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = is.read(buffer)) > 0) {
|
||||
os.write(buffer, 0, length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
path = file.getPath();
|
||||
}
|
||||
onFilePick(path);
|
||||
} else {
|
||||
onFilePick("");
|
||||
}
|
||||
});
|
||||
ContextCompat.registerReceiver(this, mReceiver, new IntentFilter(STOP_APP_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED);
|
||||
|
||||
// Listener for display insets (cutouts) to pass values into native code.
|
||||
View content = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||
View content = findViewById(android.R.id.content).getRootView();
|
||||
ViewCompat.setOnApplyWindowInsetsListener(content, (v, insets) -> {
|
||||
// Get display cutouts.
|
||||
DisplayCutoutCompat dc = insets.getDisplayCutout();
|
||||
|
@ -171,7 +127,7 @@ public class MainActivity extends GameActivity {
|
|||
return insets;
|
||||
});
|
||||
|
||||
findViewById(android.R.id.content).post(() -> {
|
||||
content.post(() -> {
|
||||
// Request notifications permissions if needed.
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
String notificationsPermission = Manifest.permission.POST_NOTIFICATIONS;
|
||||
|
@ -193,6 +149,44 @@ public class MainActivity extends GameActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
switch (requestCode) {
|
||||
case FILE_PICK_REQUEST:
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
onFile();
|
||||
}
|
||||
} else if (resultCode == RESULT_OK) {
|
||||
onFile();
|
||||
}
|
||||
case FILE_PERMISSIONS_REQUEST:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
String path = "";
|
||||
if (data != null) {
|
||||
Uri uri = data.getData();
|
||||
String name = "pick" + Utils.getFileExtension(uri, this);
|
||||
File file = new File(getExternalCacheDir(), name);
|
||||
try (InputStream is = getContentResolver().openInputStream(uri);
|
||||
OutputStream os = new FileOutputStream(file)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
while ((length = is.read(buffer)) > 0) {
|
||||
os.write(buffer, 0, length);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
path = file.getPath();
|
||||
}
|
||||
onFilePick(path);
|
||||
} else {
|
||||
onFilePick("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
@ -215,7 +209,7 @@ public class MainActivity extends GameActivity {
|
|||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
Intent i = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
|
||||
mOpenFilePermissionsResult.launch(i);
|
||||
startActivityForResult(i, FILE_PERMISSIONS_REQUEST);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -269,42 +263,9 @@ public class MainActivity extends GameActivity {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
// To support non-english input.
|
||||
if (event.getAction() == KeyEvent.ACTION_MULTIPLE && event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN) {
|
||||
if (!event.getCharacters().isEmpty()) {
|
||||
onInput(event.getCharacters());
|
||||
return false;
|
||||
}
|
||||
// Pass any other input values into native code.
|
||||
} else if (event.getAction() == KeyEvent.ACTION_UP &&
|
||||
event.getKeyCode() != KeyEvent.KEYCODE_ENTER &&
|
||||
event.getKeyCode() != KeyEvent.KEYCODE_BACK) {
|
||||
onInput(String.valueOf((char)event.getUnicodeChar()));
|
||||
return false;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
// Provide last entered character from soft keyboard into native code.
|
||||
public native void onInput(String character);
|
||||
|
||||
// Implemented into native code to handle display insets change.
|
||||
native void onDisplayInsets(int[] cutouts);
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
onBack();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
// Implemented into native code to handle key code BACK event.
|
||||
public native void onBack();
|
||||
|
||||
// Called from native code to exit app.
|
||||
public void exit() {
|
||||
finishAndRemoveTask();
|
||||
|
@ -312,7 +273,6 @@ public class MainActivity extends GameActivity {
|
|||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
unregisterReceiver(mBroadcastReceiver);
|
||||
BackgroundService.stop(this);
|
||||
|
||||
// Kill process after 3 secs if app was terminated from recent apps to prevent app hang.
|
||||
|
@ -342,14 +302,16 @@ public class MainActivity extends GameActivity {
|
|||
// Called from native code to get text from clipboard.
|
||||
public String pasteText() {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
String text;
|
||||
ClipDescription desc = clipboard.getPrimaryClipDescription();
|
||||
ClipData data = clipboard.getPrimaryClip();
|
||||
String text = "";
|
||||
if (!(clipboard.hasPrimaryClip())) {
|
||||
text = "";
|
||||
} else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))
|
||||
&& !(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_HTML))) {
|
||||
} else if (desc != null && (!(desc.hasMimeType(MIMETYPE_TEXT_PLAIN))
|
||||
&& !(desc.hasMimeType(MIMETYPE_TEXT_HTML)))) {
|
||||
text = "";
|
||||
} else {
|
||||
ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
|
||||
} else if (data != null) {
|
||||
ClipData.Item item = data.getItemAt(0);
|
||||
text = item.getText().toString();
|
||||
}
|
||||
return text;
|
||||
|
@ -417,7 +379,7 @@ public class MainActivity extends GameActivity {
|
|||
}
|
||||
// Apply declared configs to CameraX using the same lifecycle owner
|
||||
mCameraProvider.unbindAll();
|
||||
mCameraProvider.bindToLifecycle(this, cameraSelector, mImageAnalysis);
|
||||
// mCameraProvider.bindToLifecycle(this, cameraSelector, mImageAnalysis);
|
||||
}
|
||||
|
||||
// Called from native code to stop camera.
|
||||
|
@ -471,8 +433,8 @@ public class MainActivity extends GameActivity {
|
|||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
try {
|
||||
mFilePickResult.launch(Intent.createChooser(intent, "Pick file"));
|
||||
} catch (android.content.ActivityNotFoundException ex) {
|
||||
startActivityForResult(Intent.createChooser(intent, "Pick file"), FILE_PICK_REQUEST);
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
onFilePick("");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,23 +4,18 @@ import android.content.BroadcastReceiver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class NotificationActionsReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent i) {
|
||||
String a = i.getAction();
|
||||
if (a.equals(BackgroundService.ACTION_START_NODE)) {
|
||||
if (Objects.equals(a, BackgroundService.ACTION_START_NODE)) {
|
||||
startNode();
|
||||
context.sendBroadcast(new Intent(BackgroundService.ACTION_REFRESH));
|
||||
} else if (a.equals(BackgroundService.ACTION_STOP_NODE)) {
|
||||
} else if (Objects.equals(a, BackgroundService.ACTION_STOP_NODE)) {
|
||||
stopNode();
|
||||
context.sendBroadcast(new Intent(BackgroundService.ACTION_REFRESH));
|
||||
} else {
|
||||
if (isNodeRunning()) {
|
||||
stopNodeToExit();
|
||||
context.sendBroadcast(new Intent(BackgroundService.ACTION_REFRESH));
|
||||
} else {
|
||||
context.sendBroadcast(new Intent(MainActivity.STOP_APP_ACTION));
|
||||
}
|
||||
stopNodeToExit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +25,4 @@ public class NotificationActionsReceiver extends BroadcastReceiver {
|
|||
native void stopNode();
|
||||
// Stop node and exit from the app.
|
||||
native void stopNodeToExit();
|
||||
// Check if node is running.
|
||||
native boolean isNodeRunning();
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<item name="android:statusBarColor">@color/yellow</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:navigationBarColor">@color/black</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -1,6 +1,5 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.6.1' apply false
|
||||
id 'com.android.library' version '8.6.1' apply false
|
||||
}
|
||||
|
||||
id 'com.android.application' version '8.10.0' apply false
|
||||
id 'com.android.library' version '8.10.0' apply false
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
#Mon May 02 15:39:12 BST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
@ -38,14 +38,15 @@ function build_lib() {
|
|||
[[ $1 == "v8" ]] && arch=arm64-v8a
|
||||
[[ $1 == "x86" ]] && arch=x86_64
|
||||
|
||||
sed -i -e 's/"rlib"/"cdylib","rlib"/g' Cargo.toml
|
||||
sed -i -e 's/"cdylib","rlib"]/"rlib"]/g' Cargo.toml
|
||||
sed -i -e 's/"rlib"]/"cdylib","rlib"]/g' Cargo.toml
|
||||
|
||||
# Fix for https://stackoverflow.com/questions/57193895/error-use-of-undeclared-identifier-pthread-mutex-robust-cargo-build-liblmdb-s
|
||||
# Uncomment lines below for the 1st build:
|
||||
#export CPPFLAGS="-DMDB_USE_ROBUST=0" && export CFLAGS="-DMDB_USE_ROBUST=0"
|
||||
#cargo ndk -t ${arch} build --profile release-apk
|
||||
#unset CPPFLAGS && unset CFLAGS
|
||||
cargo ndk -t "${arch}" -o android/app/src/main/jniLibs build --profile release-apk
|
||||
export CPPFLAGS="-DMDB_USE_ROBUST=0" && export CFLAGS="-DMDB_USE_ROBUST=0"
|
||||
cargo ndk -t ${arch} -o android/app/src/main/jniLibs build
|
||||
# unset CPPFLAGS && unset CFLAGS
|
||||
# cargo ndk -t "${arch}" -o android/app/src/main/jniLibs build
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
success=1
|
||||
|
@ -53,7 +54,7 @@ function build_lib() {
|
|||
success=0
|
||||
fi
|
||||
|
||||
sed -i -e 's/"cdylib","rlib"/"rlib"/g' Cargo.toml
|
||||
sed -i -e 's/"cdylib","rlib"]/"rlib"]/g' Cargo.toml
|
||||
rm -f Cargo.toml-e
|
||||
}
|
||||
|
||||
|
@ -117,4 +118,4 @@ else
|
|||
rm -rf android/app/src/main/jniLibs/*
|
||||
[ $success -eq 1 ] && build_lib "x86"
|
||||
[ $success -eq 1 ] && build_apk "x86_64" "$2"
|
||||
fi
|
||||
fi
|
||||
|
|
|
@ -14,14 +14,13 @@
|
|||
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::atomic::{AtomicI32, Ordering};
|
||||
|
||||
use egui::emath::GuiRounding;
|
||||
use egui::epaint::text::TextWrapping;
|
||||
use egui::epaint::{Color32, FontId, PathShape, PathStroke, RectShape, Stroke};
|
||||
use egui::load::SizedTexture;
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::text::{LayoutJob, TextFormat};
|
||||
use egui::{lerp, Button, CornerRadius, CursorIcon, PointerState, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, StrokeKind, TextureHandle, TextureOptions, UiBuilder, Widget};
|
||||
use egui::emath::GuiRounding;
|
||||
use egui::{lerp, Button, CornerRadius, CursorIcon, Rect, Response, Rgba, RichText, Sense, SizeHint, Spinner, StrokeKind, TextureHandle, TextureOptions, UiBuilder, Widget};
|
||||
use egui_extras::image::load_svg_bytes_with_size;
|
||||
|
||||
use crate::gui::icons::{CHECK_SQUARE, SQUARE};
|
||||
|
@ -158,19 +157,6 @@ impl View {
|
|||
ui.add_space(4.0);
|
||||
}
|
||||
|
||||
/// Temporary click optimization for touch screens, return `true` if it was clicked.
|
||||
fn touched(ui: &mut egui::Ui, resp: Response) -> bool {
|
||||
let drag_resp = resp.interact(Sense::click_and_drag());
|
||||
// Clear pointer event if dragging is out of button area
|
||||
if drag_resp.dragged() && !ui.rect_contains_pointer(drag_resp.rect) {
|
||||
ui.input_mut(|i| i.pointer = PointerState::default());
|
||||
}
|
||||
if drag_resp.drag_stopped() || drag_resp.clicked() || drag_resp.secondary_clicked() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Draw big size title button.
|
||||
pub fn title_button_big(ui: &mut egui::Ui, icon: &str, action: impl FnOnce(&mut egui::Ui)) {
|
||||
Self::title_button(ui, 22.0, icon, action);
|
||||
|
@ -199,8 +185,8 @@ impl View {
|
|||
.ui(ui)
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
br.surrender_focus();
|
||||
if Self::touched(ui, br) {
|
||||
(action)(ui);
|
||||
if br.clicked() {
|
||||
action(ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -239,8 +225,8 @@ impl View {
|
|||
|
||||
let br = button.ui(ui).on_hover_cursor(CursorIcon::PointingHand);
|
||||
br.surrender_focus();
|
||||
if Self::touched(ui, br) {
|
||||
(action)(ui);
|
||||
if br.clicked() {
|
||||
action(ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -258,8 +244,8 @@ impl View {
|
|||
/// Draw [`Button`] with specified background fill color and default text color.
|
||||
pub fn button(ui: &mut egui::Ui, text: String, fill: Color32, action: impl FnOnce()) {
|
||||
let br = Self::button_resp(ui, text, Colors::text_button(), fill);
|
||||
if Self::touched(ui, br) {
|
||||
(action)();
|
||||
if br.clicked() {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,8 +256,8 @@ impl View {
|
|||
fill: Color32,
|
||||
action: impl FnOnce()) {
|
||||
let br = Self::button_resp(ui, text, text_color, fill);
|
||||
if Self::touched(ui, br) {
|
||||
(action)();
|
||||
if br.clicked() {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,8 +268,8 @@ impl View {
|
|||
fill: Color32,
|
||||
action: impl FnOnce(&mut egui::Ui)) {
|
||||
let br = Self::button_resp(ui, text, text_color, fill);
|
||||
if Self::touched(ui, br) {
|
||||
(action)(ui);
|
||||
if br.clicked() {
|
||||
action(ui);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,8 +290,8 @@ impl View {
|
|||
.fill(fill)
|
||||
.ui(ui)
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
if Self::touched(ui, br) {
|
||||
(action)(ui);
|
||||
if br.clicked() {
|
||||
action(ui);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -345,8 +331,8 @@ impl View {
|
|||
.ui(ui)
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
br.surrender_focus();
|
||||
if Self::touched(ui, br.clone()) {
|
||||
(action)();
|
||||
if br.clicked() {
|
||||
action();
|
||||
}
|
||||
|
||||
// Draw stroke.
|
||||
|
@ -449,7 +435,7 @@ impl View {
|
|||
rect.min += egui::emath::vec2(side_margin, ui.available_height() / 2.0 - height / 2.0);
|
||||
rect.max -= egui::emath::vec2(side_margin, 0.0);
|
||||
ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| {
|
||||
(content)(ui);
|
||||
content(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -468,7 +454,7 @@ impl View {
|
|||
}
|
||||
|
||||
/// Draw the button that looks like checkbox with callback on check.
|
||||
pub fn checkbox(ui: &mut egui::Ui, checked: bool, text: String, callback: impl FnOnce()) {
|
||||
pub fn checkbox(ui: &mut egui::Ui, checked: bool, text: String, action: impl FnOnce()) {
|
||||
let (text_value, color) = match checked {
|
||||
true => (format!("{} {}", CHECK_SQUARE, text), Colors::text_button()),
|
||||
false => (format!("{} {}", SQUARE, text), Colors::checkbox())
|
||||
|
@ -480,8 +466,8 @@ impl View {
|
|||
.fill(Colors::TRANSPARENT)
|
||||
.ui(ui)
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
if Self::touched(ui, br) {
|
||||
(callback)();
|
||||
if br.clicked() {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,7 +480,7 @@ impl View {
|
|||
// Draw radio button.
|
||||
let mut response = ui.radio(*current == value, text)
|
||||
.on_hover_cursor(CursorIcon::PointingHand);
|
||||
if Self::touched(ui, response.clone()) && *current != value {
|
||||
if response.clicked() && *current != value {
|
||||
*current = value;
|
||||
response.mark_changed();
|
||||
}
|
||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -70,22 +70,10 @@ fn android_main(app: AndroidApp) {
|
|||
let height = app.config().screen_height_dp().unwrap() as f32;
|
||||
let size = egui::emath::vec2(width, height);
|
||||
let mut options = NativeOptions {
|
||||
android_app: Some(app.clone()),
|
||||
viewport: egui::ViewportBuilder::default().with_inner_size(size),
|
||||
..Default::default()
|
||||
};
|
||||
// Setup limits that are guaranteed to be compatible with Android devices.
|
||||
options.wgpu_options.device_descriptor = std::sync::Arc::new(|_| {
|
||||
let base_limits = wgpu::Limits::downlevel_webgl2_defaults();
|
||||
wgpu::DeviceDescriptor {
|
||||
memory_hints: wgpu::MemoryHints::default(),
|
||||
label: Some("egui wgpu device"),
|
||||
required_features: wgpu::Features::default(),
|
||||
required_limits: wgpu::Limits {
|
||||
max_texture_dimension_2d: 8192,
|
||||
..base_limits
|
||||
},
|
||||
}
|
||||
});
|
||||
options.event_loop_builder = Some(Box::new(move |builder| {
|
||||
builder.with_android_app(app);
|
||||
}));
|
||||
|
|
|
@ -745,19 +745,6 @@ pub extern "C" fn Java_mw_gri_android_BackgroundService_canStopNode(
|
|||
return (!loading && Node::is_running()) as jni::sys::jboolean;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
/// Check if node stop is possible.
|
||||
pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_isNodeRunning(
|
||||
_env: jni::JNIEnv,
|
||||
_class: jni::objects::JObject,
|
||||
_activity: jni::objects::JObject,
|
||||
) -> jni::sys::jboolean {
|
||||
return Node::is_running() as jni::sys::jboolean;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(target_os = "android")]
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -794,7 +781,11 @@ pub extern "C" fn Java_mw_gri_android_NotificationActionsReceiver_stopNodeToExit
|
|||
_class: jni::objects::JObject,
|
||||
_activity: jni::objects::JObject,
|
||||
) {
|
||||
Node::stop(true);
|
||||
if Node::is_running() {
|
||||
Node::stop(true);
|
||||
} else {
|
||||
NODE_STATE.exit_after_stop.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
Loading…
Add table
Reference in a new issue