mirror of
https://github.com/Dark98/SliceBeam.git
synced 2026-07-02 16:49:02 +00:00
Cloud features: part 1
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
[submodule "EventBus"]
|
[submodule "EventBus"]
|
||||||
path = EventBus
|
path = EventBus
|
||||||
url = https://github.com/utkabobr/EventBus
|
url = https://github.com/utkabobr/EventBus
|
||||||
|
[submodule "SAPIL"]
|
||||||
|
path = SAPIL
|
||||||
|
url = https://github.com/utkabobr/SAPIL
|
||||||
|
|||||||
Submodule
+1
Submodule SAPIL added at d0c6422d79
+8
-6
@@ -6,14 +6,14 @@ def commit = getGitCommitHash(file('.'))
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'ru.ytkab0bp.slicebeam'
|
namespace 'ru.ytkab0bp.slicebeam'
|
||||||
compileSdk 34
|
compileSdk 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "ru.ytkab0bp.slicebeam"
|
applicationId "ru.ytkab0bp.slicebeam"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 34
|
targetSdk 35
|
||||||
versionCode 6
|
versionCode 8
|
||||||
versionName "0.2.0"
|
versionName "0.3.0"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -90,12 +90,14 @@ dependencies {
|
|||||||
implementation project(":eventbus")
|
implementation project(":eventbus")
|
||||||
implementation project(":eventbus_api")
|
implementation project(":eventbus_api")
|
||||||
annotationProcessor project(":eventbus_processor")
|
annotationProcessor project(":eventbus_processor")
|
||||||
implementation 'com.github.instacart:truetime-android:3.5'
|
implementation project(":sapil")
|
||||||
|
|
||||||
|
implementation 'com.google.code.gson:gson:2.11.0'
|
||||||
|
implementation 'com.github.instacart:truetime-android:4.0.0.alpha'
|
||||||
implementation 'com.github.mrudultora:Colorpicker:1.2.0'
|
implementation 'com.github.mrudultora:Colorpicker:1.2.0'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'com.loopj.android:android-async-http:1.4.11'
|
implementation 'com.loopj.android:android-async-http:1.4.11'
|
||||||
implementation 'androidx.activity:activity:1.9.1'
|
implementation 'androidx.activity:activity:1.10.1'
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,15 @@
|
|||||||
<uses-feature android:glEsVersion="0x00030000" android:required="true"/>
|
<uses-feature android:glEsVersion="0x00030000" android:required="true"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="23" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||||
|
</intent>
|
||||||
|
<!-- WebView fails sometime if not queried, idk why -->
|
||||||
|
<package android:name="com.google.android.webview"/>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ public class BeamServerData {
|
|||||||
return !BuildConfig.IS_GOOGLE_PLAY || Prefs.isRussianIP();
|
return !BuildConfig.IS_GOOGLE_PLAY || Prefs.isRussianIP();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isCloudAvailable() {
|
||||||
|
return isBoostyAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
public static void load() {
|
public static void load() {
|
||||||
client.get(DATA_URL, new AsyncHttpResponseHandler() {
|
client.get(DATA_URL, new AsyncHttpResponseHandler() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -48,10 +48,14 @@ import java.util.Objects;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.sapil.APICallback;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudAPI;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||||
import ru.ytkab0bp.slicebeam.components.ChangeLogBottomSheet;
|
import ru.ytkab0bp.slicebeam.components.ChangeLogBottomSheet;
|
||||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissAIGeneratorMenu;
|
||||||
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||||
@@ -69,9 +73,11 @@ import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
|||||||
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
// Activity result
|
||||||
public final static int REQUEST_CODE_OPEN_FILE = 1, REQUEST_CODE_EXPORT_GCODE = 2,
|
public final static int REQUEST_CODE_OPEN_FILE = 1, REQUEST_CODE_EXPORT_GCODE = 2,
|
||||||
REQUEST_CODE_IMPORT_PROFILES = 3, REQUEST_CODE_EXPORT_PROFILES = 4,
|
REQUEST_CODE_IMPORT_PROFILES = 3, REQUEST_CODE_EXPORT_PROFILES = 4,
|
||||||
REQUEST_CODE_EXPORT_3MF = 5,
|
REQUEST_CODE_EXPORT_3MF = 5,
|
||||||
|
REQUEST_CODE_AI_GENERATOR_TAKE_PHOTO = 6, REQUEST_CODE_AI_GENERATOR_CHOOSE_PHOTO = 7;
|
||||||
|
|
||||||
private static MainActivity activeInstance;
|
private static MainActivity activeInstance;
|
||||||
|
|
||||||
@@ -79,6 +85,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
public static List<ConfigObject> EXPORTING_FILAMENTS;
|
public static List<ConfigObject> EXPORTING_FILAMENTS;
|
||||||
public static List<ConfigObject> EXPORTING_PRINTERS;
|
public static List<ConfigObject> EXPORTING_PRINTERS;
|
||||||
|
|
||||||
|
public static File aiTempFile;
|
||||||
|
|
||||||
private static SparseArray<NavigationDelegate> liveDelegate = new SparseArray<>();
|
private static SparseArray<NavigationDelegate> liveDelegate = new SparseArray<>();
|
||||||
private static int lastId;
|
private static int lastId;
|
||||||
|
|
||||||
@@ -317,6 +325,23 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
} else if (requestCode == REQUEST_CODE_AI_GENERATOR_TAKE_PHOTO) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissAIGeneratorMenu());
|
||||||
|
|
||||||
|
Bitmap bm = BitmapFactory.decodeFile(aiTempFile.getAbsolutePath());
|
||||||
|
generateAiModel(bm);
|
||||||
|
aiTempFile.delete();
|
||||||
|
aiTempFile = null;
|
||||||
|
} else if (requestCode == REQUEST_CODE_AI_GENERATOR_CHOOSE_PHOTO) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissAIGeneratorMenu());
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream in = getContentResolver().openInputStream(data.getData());
|
||||||
|
Bitmap bm = BitmapFactory.decodeStream(in);
|
||||||
|
generateAiModel(bm);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("ai_generator", "Failed to write to downloads", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -462,21 +487,106 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
.show();
|
|
||||||
return;
|
private void generateAiModel(Bitmap bm) {
|
||||||
|
String uploadTag = UUID.randomUUID().toString();
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuFileAIGeneratorUploading).tag(uploadTag));
|
||||||
|
IOUtils.IO_POOL.submit(()->{
|
||||||
|
Bitmap scaled;
|
||||||
|
if (bm.getWidth() > 1024 || bm.getHeight() > 1024) {
|
||||||
|
if (bm.getWidth() > bm.getHeight()) {
|
||||||
|
int w = 1024;
|
||||||
|
int h = (int) ((float) w * bm.getHeight() / bm.getWidth());
|
||||||
|
scaled = Bitmap.createScaledBitmap(bm, w, h, true);
|
||||||
|
} else {
|
||||||
|
int h = 1024;
|
||||||
|
int w = (int) ((float) h * bm.getWidth() / bm.getHeight());
|
||||||
|
scaled = Bitmap.createScaledBitmap(bm, w, h, true);
|
||||||
|
}
|
||||||
|
bm.recycle();
|
||||||
|
} else {
|
||||||
|
scaled = bm;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
scaled.compress(Bitmap.CompressFormat.PNG, 100, out);
|
||||||
|
scaled.recycle();
|
||||||
|
|
||||||
|
String processTag = UUID.randomUUID().toString();
|
||||||
|
CloudAPI.INSTANCE.modelsGenerate(Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP), "image/png", new APICallback<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(InputStream in) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(processTag));
|
||||||
|
|
||||||
|
String downloadTag = UUID.randomUUID().toString();
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuFileAIGeneratorDownloading).tag(downloadTag));
|
||||||
|
String fileName = "generated_" + UUID.randomUUID() + ".stl";
|
||||||
|
|
||||||
|
File f = new File(SliceBeam.getModelCacheDir(), fileName);
|
||||||
|
try {
|
||||||
|
FileOutputStream fos = new FileOutputStream(f);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
ContentValues contentValues = new ContentValues();
|
||||||
|
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
|
||||||
|
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.ms-pki.stl");
|
||||||
|
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
|
||||||
|
Uri uri = getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
|
||||||
|
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
OutputStream out = getContentResolver().openOutputStream(uri);
|
||||||
|
byte[] buf = new byte[10240];
|
||||||
|
int c;
|
||||||
|
while ((c = in.read(buf)) != -1) {
|
||||||
|
out.write(buf, 0, c);
|
||||||
|
fos.write(buf, 0, c);
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("ai_generator", "Failed to write to downloads", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
File downloadsDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
File file = new File(downloadsDirectory, fileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileOutputStream out = new FileOutputStream(file);
|
||||||
|
byte[] buf = new byte[10240];
|
||||||
|
int c;
|
||||||
|
while ((c = in.read(buf)) != -1) {
|
||||||
|
out.write(buf, 0, c);
|
||||||
|
fos.write(buf, 0, c);
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e("ai_generator", "Failed to write to downloads", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fos.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("ai_generator", "Failed to write to downloads", e);
|
||||||
|
}
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(downloadTag));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileAIGeneratorSavedAs, fileName));
|
||||||
|
loadFile(f, true);
|
||||||
|
CloudController.checkGeneratorRemaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
@Override
|
||||||
loadIniForImport(resolver.openInputStream(uri));
|
public void onException(Exception e) {
|
||||||
} catch (FileNotFoundException e) {
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(processTag));
|
||||||
new BeamAlertDialogBuilder(this)
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(MainActivity.this)
|
||||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
.setTitle(R.string.MenuFileAIGeneratorError)
|
||||||
.setMessage(e.toString())
|
.setMessage(e.toString())
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show();
|
.show());
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(uploadTag));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuFileAIGeneratorProcessing).tag(processTag));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadIniForImport(InputStream in) {
|
private void loadIniForImport(InputStream in) {
|
||||||
|
|||||||
@@ -1,29 +1,34 @@
|
|||||||
package ru.ytkab0bp.slicebeam;
|
package ru.ytkab0bp.slicebeam;
|
||||||
|
|
||||||
import static android.opengl.GLES30.*;
|
import static android.opengl.GLES30.GL_COLOR_BUFFER_BIT;
|
||||||
|
import static android.opengl.GLES30.GL_DEPTH_BUFFER_BIT;
|
||||||
|
import static android.opengl.GLES30.GL_DEPTH_TEST;
|
||||||
|
import static android.opengl.GLES30.glClear;
|
||||||
|
import static android.opengl.GLES30.glClearColor;
|
||||||
|
import static android.opengl.GLES30.glDisable;
|
||||||
|
import static android.opengl.GLES30.glEnable;
|
||||||
|
import static android.opengl.GLES30.glViewport;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.opengl.GLSurfaceView;
|
import android.opengl.GLSurfaceView;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.ImageSpan;
|
|
||||||
import android.text.style.ReplacementSpan;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -45,6 +50,7 @@ import androidx.core.view.ViewCompat;
|
|||||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||||
import androidx.dynamicanimation.animation.SpringForce;
|
import androidx.dynamicanimation.animation.SpringForce;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.viewpager2.widget.ViewPager2;
|
import androidx.viewpager2.widget.ViewPager2;
|
||||||
|
|
||||||
@@ -82,10 +88,15 @@ import javax.microedition.khronos.opengles.GL10;
|
|||||||
|
|
||||||
import cz.msebera.android.httpclient.Header;
|
import cz.msebera.android.httpclient.Header;
|
||||||
import ru.ytkab0bp.eventbus.EventHandler;
|
import ru.ytkab0bp.eventbus.EventHandler;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudAPI;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||||
|
import ru.ytkab0bp.slicebeam.components.CloudManageBottomSheet;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudLoginStateUpdatedEvent;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.BigHeaderItem;
|
import ru.ytkab0bp.slicebeam.recycler.BigHeaderItem;
|
||||||
|
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerAdapter;
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerAdapter;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.TextHintRecyclerItem;
|
import ru.ytkab0bp.slicebeam.recycler.TextHintRecyclerItem;
|
||||||
@@ -108,6 +119,7 @@ import ru.ytkab0bp.slicebeam.view.TextColorImageSpan;
|
|||||||
public class SetupActivity extends AppCompatActivity {
|
public class SetupActivity extends AppCompatActivity {
|
||||||
public final static String EXTRA_ABOUT = "about";
|
public final static String EXTRA_ABOUT = "about";
|
||||||
public final static String EXTRA_BOOSTY_ONLY = "boosty_only";
|
public final static String EXTRA_BOOSTY_ONLY = "boosty_only";
|
||||||
|
public final static String EXTRA_CLOUD_PROFILE = "cloud_profile";
|
||||||
|
|
||||||
private final static String TAG = "SetupActivity";
|
private final static String TAG = "SetupActivity";
|
||||||
|
|
||||||
@@ -140,6 +152,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
private List<ProfilesRepo> repos = new ArrayList<>();
|
private List<ProfilesRepo> repos = new ArrayList<>();
|
||||||
private ReposItem reposItem;
|
private ReposItem reposItem;
|
||||||
private ProfilesItem profilesItem;
|
private ProfilesItem profilesItem;
|
||||||
|
private CloudProfileItem cloudItem;
|
||||||
private boolean isReposLoaded;
|
private boolean isReposLoaded;
|
||||||
private boolean limitRepoFragmentCount = true;
|
private boolean limitRepoFragmentCount = true;
|
||||||
private boolean limitProfileFragmentCount = true;
|
private boolean limitProfileFragmentCount = true;
|
||||||
@@ -149,6 +162,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
private boolean isProfilesLoaded;
|
private boolean isProfilesLoaded;
|
||||||
private boolean about;
|
private boolean about;
|
||||||
private boolean boostyOnly;
|
private boolean boostyOnly;
|
||||||
|
private boolean cloudProfile;
|
||||||
|
|
||||||
private List<ConfigObject> enabledPrinters = new ArrayList<>();
|
private List<ConfigObject> enabledPrinters = new ArrayList<>();
|
||||||
|
|
||||||
@@ -166,8 +180,9 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
about = getIntent().getBooleanExtra(EXTRA_ABOUT, false);
|
about = getIntent().getBooleanExtra(EXTRA_ABOUT, false);
|
||||||
boostyOnly = getIntent().getBooleanExtra(EXTRA_BOOSTY_ONLY, false);
|
boostyOnly = getIntent().getBooleanExtra(EXTRA_BOOSTY_ONLY, false);
|
||||||
|
cloudProfile = getIntent().getBooleanExtra(EXTRA_CLOUD_PROFILE, false);
|
||||||
|
|
||||||
if (!about && !boostyOnly) {
|
if (!about && !boostyOnly && !cloudProfile) {
|
||||||
new BeamAlertDialogBuilder(this)
|
new BeamAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.IntroEarlyAccess)
|
.setTitle(R.string.IntroEarlyAccess)
|
||||||
.setMessage(R.string.IntroEarlyAccessMessage)
|
.setMessage(R.string.IntroEarlyAccessMessage)
|
||||||
@@ -175,11 +190,15 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boostyOnly || cloudProfile) {
|
||||||
|
backgroundProgress = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
pager = new ViewPager2(this);
|
pager = new ViewPager2(this);
|
||||||
adapter = new SimpleRecyclerAdapter() {
|
adapter = new SimpleRecyclerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return about || boostyOnly ? 1 : limitRepoFragmentCount ? REPOS_INDEX + 1 : limitProfileFragmentCount ? PROFILES_INDEX + 1 : super.getItemCount();
|
return about || boostyOnly || cloudProfile ? 1 : limitRepoFragmentCount ? REPOS_INDEX + 1 : limitProfileFragmentCount ? PROFILES_INDEX + 1 : super.getItemCount();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setItems();
|
setItems();
|
||||||
@@ -210,7 +229,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||||
if (position == 0 && !boostyOnly) {
|
if (position == 0 && !boostyOnly && !cloudProfile) {
|
||||||
backgroundProgress = positionOffset;
|
backgroundProgress = positionOffset;
|
||||||
} else {
|
} else {
|
||||||
backgroundProgress = 1f;
|
backgroundProgress = 1f;
|
||||||
@@ -348,14 +367,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
title.setTranslationY(ViewUtils.lerp(titleY, (ViewUtils.dp(52) - title.getHeight() * title.getScaleY()) / 2f, backgroundProgress));
|
invalidateTitleY();
|
||||||
float sc = ViewUtils.lerp(1, 22 / 32f, backgroundProgress);
|
|
||||||
title.setPivotX(title.getWidth() / 2f);
|
|
||||||
title.setPivotY(0);
|
|
||||||
title.setScaleX(sc);
|
|
||||||
title.setScaleY(sc);
|
|
||||||
int color = ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.textColorOnAccent), ThemesRepo.getColor(android.R.attr.colorAccent), backgroundProgress - boostyProgress);
|
|
||||||
title.setTextColor(color);
|
|
||||||
backgroundView.requestRender();
|
backgroundView.requestRender();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -368,7 +380,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
super.onSizeChanged(w, h, oldw, oldh);
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
|
||||||
titleY = h / 4;
|
titleY = h / 4;
|
||||||
title.setTranslationY(ViewUtils.lerp(titleY, title.getPaddingTop(), backgroundProgress));
|
invalidateTitleY();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fl.setClipChildren(false);
|
fl.setClipChildren(false);
|
||||||
@@ -417,10 +429,13 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
topColor = ColorUtils.blendARGB(bottomColor, ThemesRepo.getColor(R.attr.boostyColorTop), boostyProgress);
|
topColor = ColorUtils.blendARGB(bottomColor, ThemesRepo.getColor(R.attr.boostyColorTop), boostyProgress);
|
||||||
bottomColor = ColorUtils.blendARGB(bottomColor, ThemesRepo.getColor(R.attr.boostyColorBottom), boostyProgress);
|
bottomColor = ColorUtils.blendARGB(bottomColor, ThemesRepo.getColor(R.attr.boostyColorBottom), boostyProgress);
|
||||||
}
|
}
|
||||||
|
if (cloudProfile) {
|
||||||
|
bottomColor = ColorUtils.blendARGB(bottomColor, topColor, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
shader.setUniformColor("top_color", topColor);
|
shader.setUniformColor("top_color", topColor);
|
||||||
shader.setUniformColor("bottom_color", bottomColor);
|
shader.setUniformColor("bottom_color", bottomColor);
|
||||||
shader.setUniform("progress", backgroundProgress - (boostyProgress != 0 ? 1.2f : 0));
|
shader.setUniform("progress", backgroundProgress - (cloudProfile ? 1.4f : 0) - (boostyProgress != 0 ? 1.2f : 0));
|
||||||
shader.setUniform("time", time);
|
shader.setUniform("time", time);
|
||||||
backgroundModel.render();
|
backgroundModel.render();
|
||||||
shader.stopUsing();
|
shader.stopUsing();
|
||||||
@@ -434,7 +449,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
title = new TextView(this);
|
title = new TextView(this);
|
||||||
title.setGravity(Gravity.CENTER);
|
title.setGravity(Gravity.CENTER);
|
||||||
title.setTypeface(Typeface.DEFAULT_BOLD);
|
title.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
title.setText(R.string.AppName);
|
title.setText(cloudProfile ? R.string.SettingsCloudManageTitle : R.string.AppName);
|
||||||
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 32);
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 32);
|
||||||
title.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
title.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||||
title.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL));
|
title.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL));
|
||||||
@@ -459,6 +474,17 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void invalidateTitleY() {
|
||||||
|
float sc = ViewUtils.lerp(1, 22 / 32f, backgroundProgress);
|
||||||
|
title.setPivotX(title.getWidth() / 2f);
|
||||||
|
title.setPivotY(0);
|
||||||
|
title.setScaleX(sc);
|
||||||
|
title.setScaleY(sc);
|
||||||
|
int color = ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.textColorOnAccent), ThemesRepo.getColor(android.R.attr.colorAccent), cloudProfile ? 0f : backgroundProgress - boostyProgress);
|
||||||
|
title.setTextColor(color);
|
||||||
|
title.setTranslationY(ViewUtils.lerp(titleY, (ViewUtils.dp(52) - title.getHeight() * title.getScaleY()) / 2f, backgroundProgress));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
@@ -468,7 +494,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@EventHandler(runOnMainThread = true)
|
@EventHandler(runOnMainThread = true)
|
||||||
public void onDataUpdated(BeamServerDataUpdatedEvent e) {
|
public void onDataUpdated(BeamServerDataUpdatedEvent e) {
|
||||||
if (!about) {
|
if (!about && !boostyOnly && !cloudProfile) {
|
||||||
boolean wasBoosty = BOOSTY_INDEX != -1;
|
boolean wasBoosty = BOOSTY_INDEX != -1;
|
||||||
if (wasBoosty != BeamServerData.isBoostyAvailable()) {
|
if (wasBoosty != BeamServerData.isBoostyAvailable()) {
|
||||||
setItems();
|
setItems();
|
||||||
@@ -476,8 +502,18 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onCloudAuthStateUpdated(CloudLoginStateUpdatedEvent e) {
|
||||||
|
if (cloudProfile) {
|
||||||
|
cloudItem.bindLoginButton(true);
|
||||||
|
cloudItem.bindFeatures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setItems() {
|
private void setItems() {
|
||||||
if (boostyOnly) {
|
if (cloudProfile){
|
||||||
|
adapter.setItems(Collections.singletonList(cloudItem = new CloudProfileItem()));
|
||||||
|
} else if (boostyOnly) {
|
||||||
adapter.setItems(Collections.singletonList(new BoostyItem()));
|
adapter.setItems(Collections.singletonList(new BoostyItem()));
|
||||||
} else if (about) {
|
} else if (about) {
|
||||||
adapter.setItems(Collections.singletonList(new AboutItem()));
|
adapter.setItems(Collections.singletonList(new AboutItem()));
|
||||||
@@ -613,6 +649,323 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
fakeScroller.start();
|
fakeScroller.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class CloudProfileItem extends SimpleRecyclerItem<View> {
|
||||||
|
private FrameLayout buttonView;
|
||||||
|
private TextView buttonText;
|
||||||
|
private ProgressBar buttonProgress;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(Context ctx) {
|
||||||
|
LinearLayout ll = new LinearLayout(ctx);
|
||||||
|
ll.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
ll.setPadding(0, ViewUtils.dp(42), 0, 0);
|
||||||
|
|
||||||
|
TextView title = new TextView(ctx);
|
||||||
|
title.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||||
|
title.setText(R.string.SettingsCloudManageDescription);
|
||||||
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
title.setGravity(Gravity.CENTER);
|
||||||
|
title.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||||
|
ll.addView(title);
|
||||||
|
|
||||||
|
FrameLayout fl = new FrameLayout(ctx);
|
||||||
|
recyclerView = new RecyclerView(ctx);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
|
||||||
|
recyclerView.setAdapter(adapter = new SimpleRecyclerAdapter());
|
||||||
|
recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
|
fl.addView(recyclerView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||||
|
bindFeatures();
|
||||||
|
|
||||||
|
ll.addView(fl, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||||
|
|
||||||
|
TextView tosButton = new TextView(ctx);
|
||||||
|
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(ctx.getString(R.string.SettingsCloudManageTermsOfService)).append(" ");
|
||||||
|
Drawable dr = ContextCompat.getDrawable(ctx, R.drawable.external_link_outline_24);
|
||||||
|
int size = ViewUtils.dp(16);
|
||||||
|
dr.setBounds(0, 0, size, size);
|
||||||
|
sb.append("d", new TextColorImageSpan(dr, ViewUtils.dp(2f)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
tosButton.setText(sb);
|
||||||
|
tosButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||||
|
tosButton.setTextColor(Color.WHITE);
|
||||||
|
tosButton.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
tosButton.setGravity(Gravity.CENTER);
|
||||||
|
tosButton.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
||||||
|
tosButton.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||||
|
tosButton.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://beam3d.ru/slicebeam_cloud_tos.html"))));
|
||||||
|
ll.addView(tosButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
bottomMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
|
||||||
|
buttonView = new FrameLayout(ctx);
|
||||||
|
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(android.R.attr.colorAccent), 16));
|
||||||
|
|
||||||
|
buttonText = new TextView(ctx);
|
||||||
|
buttonText.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||||
|
buttonText.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
buttonText.setGravity(Gravity.CENTER);
|
||||||
|
buttonText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
buttonView.addView(buttonText, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
|
||||||
|
|
||||||
|
buttonProgress = new ProgressBar(ctx);
|
||||||
|
buttonProgress.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.textColorOnAccent)));
|
||||||
|
buttonView.addView(buttonProgress, new FrameLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28), Gravity.CENTER));
|
||||||
|
|
||||||
|
bindLoginButton(false);
|
||||||
|
|
||||||
|
ll.addView(buttonView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
bottomMargin = ViewUtils.dp(16);
|
||||||
|
}});
|
||||||
|
|
||||||
|
ll.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
return ll;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindFeatures() {
|
||||||
|
List<SimpleRecyclerItem> items = new ArrayList<>();
|
||||||
|
if (CloudController.getUserFeatures() != null) {
|
||||||
|
for (CloudAPI.SubscriptionLevel lvl : CloudController.getUserFeatures().levels) {
|
||||||
|
items.add(new CloudSubscriptionLevel(lvl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.setItems(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindLoginButton(boolean animate) {
|
||||||
|
boolean loggedIn = Prefs.getCloudAPIToken() != null;
|
||||||
|
boolean loading = !loggedIn && CloudController.isLoggingIn();
|
||||||
|
boolean wasLoading = buttonProgress.getTag() != null;
|
||||||
|
if (animate) {
|
||||||
|
if (wasLoading != loading) {
|
||||||
|
buttonProgress.setTag(loading ? 1 : null);
|
||||||
|
|
||||||
|
buttonProgress.animate().cancel();
|
||||||
|
buttonProgress.animate().scaleX(loading ? 1f : 0.4f).scaleY(loading ? 1f : 0.4f).alpha(loading ? 1f : 0f).setDuration(150).setInterpolator(ViewUtils.CUBIC_INTERPOLATOR).setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
if (loading) {
|
||||||
|
buttonProgress.setVisibility(View.VISIBLE);
|
||||||
|
buttonProgress.setAlpha(0f);
|
||||||
|
buttonProgress.setScaleX(0.4f);
|
||||||
|
buttonProgress.setScaleY(0.4f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
if (!loading) {
|
||||||
|
buttonProgress.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
buttonText.animate().cancel();
|
||||||
|
buttonText.animate().scaleX(!loading ? 1f : 0.4f).scaleY(!loading ? 1f : 0.4f).alpha(!loading ? 1f : 0f).setDuration(150).setInterpolator(ViewUtils.CUBIC_INTERPOLATOR).setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
if (!loading) {
|
||||||
|
buttonText.setVisibility(View.VISIBLE);
|
||||||
|
buttonText.setAlpha(0f);
|
||||||
|
buttonText.setScaleX(0.4f);
|
||||||
|
buttonText.setScaleY(0.4f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
if (loading) {
|
||||||
|
buttonText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buttonProgress.setTag(loading ? 1 : null);
|
||||||
|
buttonProgress.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
buttonText.setVisibility(loading ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
buttonText.setText(loggedIn ? R.string.SettingsCloudManageButtonManage : R.string.SettingsCloudManageButtonLogIn);
|
||||||
|
buttonView.setOnClickListener(v-> {
|
||||||
|
if (loading) {
|
||||||
|
new BeamAlertDialogBuilder(v.getContext())
|
||||||
|
.setTitle(R.string.SettingsCloudManageButtonLogInCancelTitle)
|
||||||
|
.setMessage(R.string.SettingsCloudManageButtonLogInCancel)
|
||||||
|
.setNegativeButton(R.string.No, null)
|
||||||
|
.setPositiveButton(R.string.Yes, (dialog, which) -> CloudController.cancelLogin())
|
||||||
|
.show();
|
||||||
|
} else if (Prefs.getCloudAPIToken() != null) {
|
||||||
|
new CloudManageBottomSheet(v.getContext()).show();
|
||||||
|
} else {
|
||||||
|
CloudController.beginLogin();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static class CloudSubscriptionLevel extends SimpleRecyclerItem<CloudSubscriptionLevel.LevelHolderView> {
|
||||||
|
private CloudAPI.SubscriptionLevel level;
|
||||||
|
|
||||||
|
private CloudSubscriptionLevel(CloudAPI.SubscriptionLevel level) {
|
||||||
|
this.level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LevelHolderView onCreateView(Context ctx) {
|
||||||
|
return new LevelHolderView(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindView(LevelHolderView view) {
|
||||||
|
view.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static class LevelHolderView extends LinearLayout implements IThemeView {
|
||||||
|
private ImageView icon;
|
||||||
|
private TextView title;
|
||||||
|
private TextView price;
|
||||||
|
|
||||||
|
private RecyclerView featuresLayout;
|
||||||
|
private SimpleRecyclerAdapter featuresAdapter;
|
||||||
|
|
||||||
|
public LevelHolderView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
setOrientation(VERTICAL);
|
||||||
|
setPadding(0, ViewUtils.dp(16), 0, ViewUtils.dp(8));
|
||||||
|
|
||||||
|
LinearLayout inner = new LinearLayout(context);
|
||||||
|
inner.setOrientation(HORIZONTAL);
|
||||||
|
inner.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
inner.setPadding(ViewUtils.dp(28), 0, ViewUtils.dp(28), 0);
|
||||||
|
addView(inner, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
bottomMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
|
||||||
|
icon = new ImageView(context);
|
||||||
|
inner.addView(icon, new LayoutParams(ViewUtils.dp(26), ViewUtils.dp(26)));
|
||||||
|
|
||||||
|
title = new TextView(context);
|
||||||
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
inner.addView(title, new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||||
|
leftMargin = ViewUtils.dp(12);
|
||||||
|
}});
|
||||||
|
|
||||||
|
price = new TextView(context);
|
||||||
|
price.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
price.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
inner.addView(price);
|
||||||
|
|
||||||
|
featuresLayout = new RecyclerView(context) {
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean dispatchHoverEvent(MotionEvent event) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
featuresLayout.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
featuresLayout.setAdapter(featuresAdapter = new SimpleRecyclerAdapter());
|
||||||
|
addView(featuresLayout, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
topMargin = ViewUtils.dp(3);
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
bottomMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
|
||||||
|
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||||
|
topMargin = ViewUtils.dp(12);
|
||||||
|
}});
|
||||||
|
onApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(CloudSubscriptionLevel item) {
|
||||||
|
CloudAPI.SubscriptionLevel lvl = item.level;
|
||||||
|
title.setText(lvl.title);
|
||||||
|
price.setText(lvl.price);
|
||||||
|
if (lvl.level <= 0) {
|
||||||
|
icon.setImageResource(R.drawable.zero_ruble_outline_28);
|
||||||
|
price.setText(R.string.SettingsCloudManageFree);
|
||||||
|
} else if (lvl.level == 1) {
|
||||||
|
icon.setImageResource(R.drawable.stars_outline_28);
|
||||||
|
} else {
|
||||||
|
icon.setImageResource(R.drawable.cloud_plus_outline_28);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SimpleRecyclerItem> items = new ArrayList<>();
|
||||||
|
CloudAPI.UserFeatures features = CloudController.getUserFeatures();
|
||||||
|
CloudAPI.UserInfo info = CloudController.getUserInfo();
|
||||||
|
Context ctx = getContext();
|
||||||
|
if (features.syncRequiredLevel != -1 && lvl.level >= features.syncRequiredLevel) {
|
||||||
|
items.add(new PreferenceItem()
|
||||||
|
.setForceDark(true)
|
||||||
|
.setPaddings(ViewUtils.dp(8))
|
||||||
|
.setIcon(R.drawable.sync_outline_28)
|
||||||
|
.setTitle(ctx.getString(R.string.SettingsCloudManageFeatureCloudSync))
|
||||||
|
.setSubtitle(ctx.getString(R.string.SettingsCloudManageFeatureCloudSyncDescription)));
|
||||||
|
}
|
||||||
|
if (features.aiGeneratorRequiredLevel != -1 && lvl.level >= features.aiGeneratorRequiredLevel) {
|
||||||
|
items.add(new PreferenceItem()
|
||||||
|
.setForceDark(true)
|
||||||
|
.setPaddings(ViewUtils.dp(8))
|
||||||
|
.setIcon(R.drawable.brain_outline_28)
|
||||||
|
.setTitle(ctx.getString(R.string.SettingsCloudManageFeatureAIGenerator))
|
||||||
|
.setSubtitle(ctx.getString(R.string.SettingsCloudManageFeatureAIGeneratorDescription, features.aiGeneratorModelsPerMonth)));
|
||||||
|
}
|
||||||
|
if (lvl.level > 0) {
|
||||||
|
items.add(new PreferenceItem()
|
||||||
|
.setForceDark(true)
|
||||||
|
.setPaddings(ViewUtils.dp(8))
|
||||||
|
.setIcon(R.drawable.box_heart_outline_28)
|
||||||
|
.setTitle(ctx.getString(R.string.SettingsCloudManageFeatureFreeForAll))
|
||||||
|
.setSubtitle(ctx.getString(R.string.SettingsCloudManageFeatureFreeForAllDescription)));
|
||||||
|
}
|
||||||
|
featuresAdapter.setItems(items);
|
||||||
|
featuresLayout.setVisibility(items.isEmpty() ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
boolean subscribed = lvl.level > 0 && info != null && lvl.level == info.currentLevel;
|
||||||
|
boolean allowSubscribe = lvl.level > 0 && (info == null || lvl.level > info.currentLevel);
|
||||||
|
if (subscribed) {
|
||||||
|
price.setText(R.string.SettingsCloudManageSubscribed);
|
||||||
|
}
|
||||||
|
price.setVisibility(allowSubscribe || subscribed ? View.VISIBLE : View.GONE);
|
||||||
|
setOnClickListener(v -> {
|
||||||
|
if (subscribed) {
|
||||||
|
v.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(lvl.manageUrl)));
|
||||||
|
} else {
|
||||||
|
new BeamAlertDialogBuilder(getContext())
|
||||||
|
.setTitle(lvl.title)
|
||||||
|
.setMessage(R.string.SettingsCloudManageLevelRedirectMessage)
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> v.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(lvl.subscribeOrUpgradeUrl))))
|
||||||
|
.setNegativeButton(R.string.SettingsCloudManageLevelRedirectAlreadySubscribed, (dialog, which) -> v.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(features.alreadySubscribedInfoUrl))))
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setClickable(allowSubscribe || subscribed);
|
||||||
|
onApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyTheme() {
|
||||||
|
int accent = ThemesRepo.getColor(android.R.attr.colorAccent);
|
||||||
|
if (ColorUtils.calculateLuminance(accent) >= 0.6f) {
|
||||||
|
accent = ColorUtils.blendARGB(accent, Color.BLACK, 0.075f);
|
||||||
|
}
|
||||||
|
boolean tooLight = ColorUtils.calculateLuminance(accent) >= 0.6f;
|
||||||
|
title.setTextColor(0xffffffff);
|
||||||
|
price.setTextColor(0xffffffff);
|
||||||
|
icon.setImageTintList(ColorStateList.valueOf(0xffffffff));
|
||||||
|
featuresLayout.setBackground(ViewUtils.createRipple(0, tooLight ? 0x33ffffff : 0x21ffffff, 24));
|
||||||
|
setBackground(ViewUtils.createRipple(0x21000000, ColorUtils.blendARGB(0xffffffff, accent, tooLight ? 0.9f : 0.75f), 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class AboutItem extends SimpleRecyclerItem<View> {
|
private final class AboutItem extends SimpleRecyclerItem<View> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.app.Application;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.instacart.truetime.time.TrueTimeImpl;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -26,6 +28,7 @@ import ru.ytkab0bp.slicebeam.boot.PrefsTask;
|
|||||||
import ru.ytkab0bp.slicebeam.boot.PrintConfigWarmupTask;
|
import ru.ytkab0bp.slicebeam.boot.PrintConfigWarmupTask;
|
||||||
import ru.ytkab0bp.slicebeam.boot.TrueTimeTask;
|
import ru.ytkab0bp.slicebeam.boot.TrueTimeTask;
|
||||||
import ru.ytkab0bp.slicebeam.boot.VibrationUtilsTask;
|
import ru.ytkab0bp.slicebeam.boot.VibrationUtilsTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.ConfigOptionDef;
|
import ru.ytkab0bp.slicebeam.slic3r.ConfigOptionDef;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||||
@@ -35,6 +38,7 @@ import ru.ytkab0bp.slicebeam.utils.Prefs;
|
|||||||
public class SliceBeam extends Application {
|
public class SliceBeam extends Application {
|
||||||
public static SliceBeam INSTANCE;
|
public static SliceBeam INSTANCE;
|
||||||
public static EventBus EVENT_BUS = EventBus.newBus("main");
|
public static EventBus EVENT_BUS = EventBus.newBus("main");
|
||||||
|
public static TrueTimeImpl TRUE_TIME;
|
||||||
public static Slic3rConfigWrapper CONFIG;
|
public static Slic3rConfigWrapper CONFIG;
|
||||||
public static int CONFIG_UID = 0;
|
public static int CONFIG_UID = 0;
|
||||||
public static BeamServerData SERVER_DATA;
|
public static BeamServerData SERVER_DATA;
|
||||||
@@ -82,6 +86,7 @@ public class SliceBeam extends Application {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("Config", "Failed to save config", e);
|
Log.e("Config", "Failed to save config", e);
|
||||||
}
|
}
|
||||||
|
CloudController.notifyDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getModelCacheDir() {
|
public static File getModelCacheDir() {
|
||||||
|
|||||||
@@ -1,22 +1,72 @@
|
|||||||
package ru.ytkab0bp.slicebeam.boot;
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
import com.instacart.library.truetime.TrueTime;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import com.instacart.truetime.TrueTimeEventListener;
|
||||||
|
import com.instacart.truetime.time.TrueTimeImpl;
|
||||||
|
import com.instacart.truetime.time.TrueTimeParameters;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
|
||||||
public class TrueTimeTask extends BootTask {
|
public class TrueTimeTask extends BootTask {
|
||||||
public TrueTimeTask() {
|
public TrueTimeTask() {
|
||||||
super(() -> {
|
super(() -> {
|
||||||
for (int i = 0; i < 2; i++) {
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
try {
|
SliceBeam.TRUE_TIME = new TrueTimeImpl(new TrueTimeParameters.Builder().buildParams(), Dispatchers.getIO(), new TrueTimeEventListener() {
|
||||||
TrueTime.build().withNtpHost("1.ru.pool.ntp.org").withConnectionTimeout(300).initialize();
|
@Override
|
||||||
break;
|
public void initialize(@NonNull TrueTimeParameters trueTimeParameters) {}
|
||||||
} catch (IOException ignore) {
|
|
||||||
try {
|
@Override
|
||||||
Thread.sleep(100);
|
public void initializeSuccess(@NonNull long[] longs) {
|
||||||
} catch (InterruptedException ignored) {}
|
latch.countDown();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void initializeFailed(@NonNull Exception e) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void nextInitializeIn(long l) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resolvedNtpHostToIPs(@NonNull String s, @NonNull List<? extends InetAddress> list) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lastSntpRequestAttempt(@NonNull InetAddress inetAddress) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sntpRequestFailed(@NonNull Exception e) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void syncDispatcherException(@NonNull Throwable throwable) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sntpRequest(@NonNull InetAddress inetAddress) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sntpRequestSuccessful(@NonNull InetAddress inetAddress) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sntpRequestFailed(@NonNull InetAddress inetAddress, @NonNull Exception e) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storingTrueTime(@NonNull long[] longs) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void returningTrueTime(@NonNull Date date) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void returningDeviceTime() {}
|
||||||
|
});
|
||||||
|
SliceBeam.TRUE_TIME.sync();
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
});
|
});
|
||||||
onWorker();
|
onWorker();
|
||||||
nonCritical = true;
|
nonCritical = true;
|
||||||
|
|||||||
@@ -0,0 +1,268 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.cloud;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.sapil.APICallback;
|
||||||
|
import ru.ytkab0bp.sapil.APILibrary;
|
||||||
|
import ru.ytkab0bp.sapil.APIRequestHandle;
|
||||||
|
import ru.ytkab0bp.sapil.APIRunner;
|
||||||
|
import ru.ytkab0bp.sapil.Arg;
|
||||||
|
import ru.ytkab0bp.sapil.Header;
|
||||||
|
import ru.ytkab0bp.sapil.Method;
|
||||||
|
import ru.ytkab0bp.sapil.RequestType;
|
||||||
|
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
|
||||||
|
public interface CloudAPI extends APIRunner {
|
||||||
|
CloudAPI INSTANCE = APILibrary.newRunner(CloudAPI.class, new RunnerConfig() {
|
||||||
|
private final Map<String, String> headers = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseURL() {
|
||||||
|
return "https://api.beam3d.ru/v1/";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultUserAgent() {
|
||||||
|
return "SliceBeam v" + BuildConfig.VERSION_NAME + "/" + BuildConfig.VERSION_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getDefaultHeaders() {
|
||||||
|
headers.clear();
|
||||||
|
if (Prefs.getCloudAPIToken() != null) {
|
||||||
|
headers.put("Authorization", "Bearer " + Prefs.getCloudAPIToken());
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins login flow, returns auth link
|
||||||
|
*/
|
||||||
|
@Method("login/begin")
|
||||||
|
APIRequestHandle loginBegin(APICallback<LoginData> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks new login state by session id
|
||||||
|
*/
|
||||||
|
@Method("login/check")
|
||||||
|
void loginCheck(@Arg("sessionId") String sessionId, APICallback<LoginState> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels login flow
|
||||||
|
*/
|
||||||
|
@Method("login/cancel")
|
||||||
|
void loginCancel(@Arg("sessionId") String sessionId, APICallback<Boolean> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current user info
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method("user/getInfo")
|
||||||
|
void userGetInfo(APICallback<UserInfo> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets user features
|
||||||
|
*/
|
||||||
|
@Method("user/getFeatures")
|
||||||
|
void userGetFeatures(APICallback<UserFeatures> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches sync state
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method("sync/getState")
|
||||||
|
void syncGetState(APICallback<SyncState> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads new data to the server
|
||||||
|
* <p>
|
||||||
|
* @param data New base64 encoded data
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method("sync/upload")
|
||||||
|
void syncUpload(@Arg("data") String data, APICallback<SyncState> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads base64 data
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method("sync/get")
|
||||||
|
void syncGet(APICallback<String> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates 3D model from image
|
||||||
|
* <p>
|
||||||
|
* @param image Base64 encoded image
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method(requestType = RequestType.POST, value = "models/generate")
|
||||||
|
void modelsGenerate(@Arg("") String image, @Header("Content-Type") String type, APICallback<InputStream> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets remaining model generations count
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method("models/getRemainingCount")
|
||||||
|
void modelsGetRemainingCount(APICallback<ModelsRemainingCount> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys token
|
||||||
|
* <p>
|
||||||
|
* Requires authorization
|
||||||
|
*/
|
||||||
|
@Method("logout")
|
||||||
|
void logout(APICallback<Boolean> callback);
|
||||||
|
|
||||||
|
final class LoginData {
|
||||||
|
/**
|
||||||
|
* Url that should be clicked by the user to authorize
|
||||||
|
*/
|
||||||
|
public String url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session identifier
|
||||||
|
*/
|
||||||
|
public String sessionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time at which session should be considered expired if not logged in
|
||||||
|
*/
|
||||||
|
public long expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoginState {
|
||||||
|
/**
|
||||||
|
* If user is now logged in
|
||||||
|
*/
|
||||||
|
public boolean loggedIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bearer token if auth was successful
|
||||||
|
*/
|
||||||
|
public String bearer;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UserFeatures {
|
||||||
|
/**
|
||||||
|
* Which level is required for data sync
|
||||||
|
*/
|
||||||
|
public int syncRequiredLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which level is required for AI model generator
|
||||||
|
*/
|
||||||
|
public int aiGeneratorRequiredLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models per month max
|
||||||
|
*/
|
||||||
|
public int aiGeneratorModelsPerMonth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Url at which user should be redirected for info about how to restore a subscription
|
||||||
|
*/
|
||||||
|
public String alreadySubscribedInfoUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscription levels
|
||||||
|
*/
|
||||||
|
public List<SubscriptionLevel> levels = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final class SubscriptionLevel {
|
||||||
|
/**
|
||||||
|
* Int representation
|
||||||
|
*/
|
||||||
|
public int level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Title of this level
|
||||||
|
*/
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Price of this level
|
||||||
|
*/
|
||||||
|
public String price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Url at which user should be redirected for purchase
|
||||||
|
*/
|
||||||
|
public String subscribeOrUpgradeUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Url at which user should be redirected for managing the subscription
|
||||||
|
*/
|
||||||
|
public String manageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class UserInfo {
|
||||||
|
/**
|
||||||
|
* User's id
|
||||||
|
*/
|
||||||
|
public String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User's display name
|
||||||
|
*/
|
||||||
|
public String displayName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User's avatar. Could be null
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String avatarUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current subscription level
|
||||||
|
*/
|
||||||
|
public int currentLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final class SyncState {
|
||||||
|
/**
|
||||||
|
* Cloud data last updated time
|
||||||
|
*/
|
||||||
|
public long lastUpdatedDate = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used size of cloud storage
|
||||||
|
*/
|
||||||
|
public long usedSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max storage size
|
||||||
|
*/
|
||||||
|
public long maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ModelsRemainingCount {
|
||||||
|
/**
|
||||||
|
* Used generations
|
||||||
|
*/
|
||||||
|
public int used;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max available generations
|
||||||
|
*/
|
||||||
|
public int max;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.cloud;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.sapil.APICallback;
|
||||||
|
import ru.ytkab0bp.sapil.APIRequestHandle;
|
||||||
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudFeaturesUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudLoginStateUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudModelsRemainingCountUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudUserInfoUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
||||||
|
|
||||||
|
public class CloudController {
|
||||||
|
public final static String USER_INFO_AI_GEN_TAG = "ai_gen_user_info";
|
||||||
|
private final static String CLOUD_SYNC_TAG = "cloud_sync";
|
||||||
|
|
||||||
|
private final static String TAG = "cloud";
|
||||||
|
private final static long MIN_SYNC_DELTA = 5 * 60 * 1000L; // Once in 5 minutes
|
||||||
|
private final static long MIN_SYNC_FEATURES_DELTA = 12 * 60 * 60 * 1000L; // Once in 12 hours
|
||||||
|
|
||||||
|
private static boolean isSyncInProgress;
|
||||||
|
private static CloudAPI.UserInfo userInfo;
|
||||||
|
private static CloudAPI.UserFeatures userFeatures;
|
||||||
|
|
||||||
|
private static int modelsUsed;
|
||||||
|
private static int modelsMaxGenerations;
|
||||||
|
private static boolean isLoggingIn;
|
||||||
|
private static APIRequestHandle beginLoginHandle;
|
||||||
|
private static String loginSessionId;
|
||||||
|
private static Runnable loginAutoCancel = () -> {
|
||||||
|
loginSessionId = null;
|
||||||
|
isLoggingIn = false;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudLoginStateUpdatedEvent());
|
||||||
|
};
|
||||||
|
private static Runnable loginCheck = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
CloudAPI.INSTANCE.loginCheck(loginSessionId, new APICallback<CloudAPI.LoginState>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.LoginState response) {
|
||||||
|
if (response.loggedIn) {
|
||||||
|
Prefs.setCloudAPIToken(response.bearer);
|
||||||
|
loadUserInfo();
|
||||||
|
ViewUtils.removeCallbacks(loginAutoCancel);
|
||||||
|
} else if (isLoggingIn) {
|
||||||
|
ViewUtils.postOnMainThread(loginCheck, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to check login state", e);
|
||||||
|
|
||||||
|
if (isLoggingIn) {
|
||||||
|
ViewUtils.postOnMainThread(loginCheck, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Gson gson = new Gson();
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
if (Prefs.getCloudCachedUserFeatures() != null) {
|
||||||
|
userFeatures = gson.fromJson(Prefs.getCloudCachedUserFeatures(), CloudAPI.UserFeatures.class);
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudFeaturesUpdatedEvent());
|
||||||
|
}
|
||||||
|
long now = SliceBeam.TRUE_TIME.now().getTime();
|
||||||
|
boolean needSyncInfo = userFeatures == null || now - Prefs.getCloudLastFeaturesSync() > MIN_SYNC_FEATURES_DELTA;
|
||||||
|
if (needSyncInfo) {
|
||||||
|
checkUserFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Prefs.getCloudAPIToken() != null) {
|
||||||
|
if (Prefs.getCloudCachedUserInfo() != null) {
|
||||||
|
userInfo = gson.fromJson(Prefs.getCloudCachedUserInfo(), CloudAPI.UserInfo.class);
|
||||||
|
modelsUsed = Prefs.getCloudCachedUsedModels();
|
||||||
|
modelsMaxGenerations = Prefs.getCloudCachedMaxModels();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needSyncInfo || userInfo == null) {
|
||||||
|
loadUserInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadUserInfo() {
|
||||||
|
CloudAPI.INSTANCE.userGetInfo(new APICallback<CloudAPI.UserInfo>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.UserInfo response) {
|
||||||
|
userInfo = response;
|
||||||
|
|
||||||
|
if (userInfo.id.equals("null")) {
|
||||||
|
userInfo = null;
|
||||||
|
Prefs.setCloudAPIToken(null);
|
||||||
|
Prefs.setCloudCachedUserInfo(null);
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudUserInfoUpdatedEvent());
|
||||||
|
|
||||||
|
if (isLoggingIn) {
|
||||||
|
isLoggingIn = false;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudLoginStateUpdatedEvent());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Prefs.setCloudCachedUserInfo(gson.toJson(userInfo));
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(USER_INFO_AI_GEN_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudUserInfoUpdatedEvent());
|
||||||
|
|
||||||
|
if (isLoggingIn) {
|
||||||
|
isLoggingIn = false;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudLoginStateUpdatedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSyncAvailable() && Prefs.isCloudProfileSyncEnabled()) {
|
||||||
|
long now = SliceBeam.TRUE_TIME.now().getTime();
|
||||||
|
if (now != Prefs.getLocalLastModified()) {
|
||||||
|
sendData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkGeneratorRemaining();
|
||||||
|
}
|
||||||
|
Prefs.setCloudLastFeaturesSync(SliceBeam.TRUE_TIME.now().getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to get user info", e);
|
||||||
|
ViewUtils.postOnMainThread(CloudController::init, 15000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLoggingIn() {
|
||||||
|
return isLoggingIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void beginLogin0() {
|
||||||
|
beginLoginHandle = CloudAPI.INSTANCE.loginBegin(new APICallback<CloudAPI.LoginData>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.LoginData response) {
|
||||||
|
loginSessionId = response.sessionId;
|
||||||
|
|
||||||
|
ViewUtils.postOnMainThread(loginAutoCancel, response.expiresAt * 1000L - SliceBeam.TRUE_TIME.now().getTime());
|
||||||
|
ViewUtils.postOnMainThread(loginCheck, 5000);
|
||||||
|
ViewUtils.postOnMainThread(() -> SliceBeam.INSTANCE.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(response.url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
ViewUtils.postOnMainThread(CloudController::beginLogin0, 15000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void beginLogin() {
|
||||||
|
isLoggingIn = true;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudLoginStateUpdatedEvent());
|
||||||
|
beginLogin0();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void cancelLogin() {
|
||||||
|
isLoggingIn = false;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudLoginStateUpdatedEvent());
|
||||||
|
if (loginSessionId != null) {
|
||||||
|
CloudAPI.INSTANCE.loginCancel(loginSessionId, response -> {});
|
||||||
|
}
|
||||||
|
if (beginLoginHandle != null && beginLoginHandle.isRunning()) {
|
||||||
|
beginLoginHandle.cancel();
|
||||||
|
beginLoginHandle = null;
|
||||||
|
}
|
||||||
|
ViewUtils.removeCallbacks(loginCheck);
|
||||||
|
ViewUtils.removeCallbacks(loginAutoCancel);
|
||||||
|
loginSessionId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logout() {
|
||||||
|
Prefs.setCloudAPIToken(null);
|
||||||
|
userInfo = null;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudLoginStateUpdatedEvent());
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudUserInfoUpdatedEvent());
|
||||||
|
CloudAPI.INSTANCE.logout(response -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkGeneratorRemaining() {
|
||||||
|
CloudAPI.INSTANCE.modelsGetRemainingCount(new APICallback<CloudAPI.ModelsRemainingCount>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.ModelsRemainingCount response) {
|
||||||
|
modelsUsed = response.used;
|
||||||
|
modelsMaxGenerations = response.max;
|
||||||
|
Prefs.setCloudCachedUsedMaxModels(modelsUsed, modelsMaxGenerations);
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudModelsRemainingCountUpdatedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to check remaining models", e);
|
||||||
|
ViewUtils.postOnMainThread(CloudController::checkGeneratorRemaining, 15000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void checkUserFeatures() {
|
||||||
|
CloudAPI.INSTANCE.userGetFeatures(new APICallback<CloudAPI.UserFeatures>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.UserFeatures response) {
|
||||||
|
userFeatures = response;
|
||||||
|
Prefs.setCloudCachedUserFeatures(gson.toJson(userFeatures));
|
||||||
|
if (Prefs.getCloudAPIToken() == null) {
|
||||||
|
Prefs.setCloudLastFeaturesSync(SliceBeam.TRUE_TIME.now().getTime());
|
||||||
|
}
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudFeaturesUpdatedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to get user features", e);
|
||||||
|
ViewUtils.postOnMainThread(CloudController::checkUserFeatures, 15000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CloudAPI.UserInfo getUserInfo() {
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CloudAPI.UserFeatures getUserFeatures() {
|
||||||
|
return userFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSyncAvailable() {
|
||||||
|
return Prefs.getCloudAPIToken() != null && userInfo != null && userFeatures != null && userInfo.currentLevel >= userFeatures.syncRequiredLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean needShowAIGenerator() {
|
||||||
|
return userFeatures != null && userFeatures.aiGeneratorRequiredLevel >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getGeneratedModels() {
|
||||||
|
return modelsUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getMaxGeneratedModels() {
|
||||||
|
return modelsMaxGenerations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sendData() {
|
||||||
|
if (isSyncInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: IMPORTANT: Check getState first, then show conflict info
|
||||||
|
long modified = Prefs.getLocalLastModified();
|
||||||
|
isSyncInProgress = true;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.CloudSyncInProgress).tag(CLOUD_SYNC_TAG));
|
||||||
|
CloudAPI.INSTANCE.syncUpload("", new APICallback<CloudAPI.SyncState>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.SyncState response) {
|
||||||
|
isSyncInProgress = false;
|
||||||
|
if (Prefs.getLocalLastModified() != modified) { // Re-send otherwise
|
||||||
|
sendData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Prefs.setCloudLastSync(modified);
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.CloudSyncSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to upload sync data", e);
|
||||||
|
isSyncInProgress = false;
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.ERROR, R.string.CloudSyncError));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyDataChanged() {
|
||||||
|
long now = SliceBeam.TRUE_TIME.now().getTime();
|
||||||
|
Prefs.setLocalLastModified(now);
|
||||||
|
if (!isSyncAvailable() || !Prefs.isCloudProfileSyncEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (now - Prefs.getCloudLastSync() > MIN_SYNC_DELTA) {
|
||||||
|
sendData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.components;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.Space;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudAPI;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
|
import ru.ytkab0bp.slicebeam.recycler.PreferenceSwitchItem;
|
||||||
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerAdapter;
|
||||||
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||||
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.TextColorImageSpan;
|
||||||
|
|
||||||
|
public class CloudManageBottomSheet extends BottomSheetDialog {
|
||||||
|
public CloudManageBottomSheet(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
LinearLayout ll = new LinearLayout(context);
|
||||||
|
ll.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
GradientDrawable gd = new GradientDrawable();
|
||||||
|
gd.setCornerRadii(new float[] {
|
||||||
|
ViewUtils.dp(28), ViewUtils.dp(28),
|
||||||
|
ViewUtils.dp(28), ViewUtils.dp(28),
|
||||||
|
0, 0,
|
||||||
|
0, 0
|
||||||
|
});
|
||||||
|
gd.setColor(ThemesRepo.getColor(R.attr.dialogBackground));
|
||||||
|
ll.setBackground(gd);
|
||||||
|
ll.setPadding(0, ViewUtils.dp(12), 0, ViewUtils.dp(12));
|
||||||
|
|
||||||
|
TextView title = new TextView(context);
|
||||||
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
|
||||||
|
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
title.setText(R.string.SettingsCloudManageButtonManage);
|
||||||
|
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||||
|
title.setGravity(Gravity.CENTER);
|
||||||
|
title.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||||
|
}});
|
||||||
|
ll.addView(title);
|
||||||
|
|
||||||
|
TextView description = new TextView(context);
|
||||||
|
description.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
description.setText(context.getString(R.string.SettingsCloudManageLoggedInAs, CloudController.getUserInfo().displayName));
|
||||||
|
description.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
description.setGravity(Gravity.CENTER);
|
||||||
|
description.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||||
|
topMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
ll.addView(description);
|
||||||
|
|
||||||
|
int currentLevel = CloudController.getUserInfo().currentLevel;
|
||||||
|
CloudAPI.SubscriptionLevel lvl = null;
|
||||||
|
CloudAPI.UserFeatures features = CloudController.getUserFeatures();
|
||||||
|
for (CloudAPI.SubscriptionLevel level : features.levels) {
|
||||||
|
if (level.level != -1 && level.level <= currentLevel && (lvl == null || level.level > lvl.level)) {
|
||||||
|
lvl = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lvl != null) {
|
||||||
|
List<SimpleRecyclerItem> items = new ArrayList<>();
|
||||||
|
if (currentLevel >= features.syncRequiredLevel) {
|
||||||
|
items.add(new PreferenceSwitchItem()
|
||||||
|
.setIcon(R.drawable.sync_outline_28)
|
||||||
|
.setTitle(context.getString(R.string.SettingsCloudManageFeatureCloudSync))
|
||||||
|
.setValueProvider(Prefs::isCloudProfileSyncEnabled)
|
||||||
|
.setChangeListener((buttonView, isChecked) -> {
|
||||||
|
Prefs.setCloudProfileSyncEnabled(isChecked);
|
||||||
|
if (isChecked) {
|
||||||
|
CloudController.notifyDataChanged();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (!items.isEmpty()) {
|
||||||
|
RecyclerView recyclerView = new RecyclerView(context);
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(context));
|
||||||
|
recyclerView.setBackground(ViewUtils.createRipple(0, ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x10), 16));
|
||||||
|
SimpleRecyclerAdapter adapter = new SimpleRecyclerAdapter();
|
||||||
|
adapter.setItems(items);
|
||||||
|
recyclerView.setAdapter(adapter);
|
||||||
|
ll.addView(recyclerView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
topMargin = ViewUtils.dp(12);
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView manageButton = new TextView(context);
|
||||||
|
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(context.getString(R.string.SettingsCloudManageSubscription)).append(" ");
|
||||||
|
Drawable dr = ContextCompat.getDrawable(context, R.drawable.external_link_outline_24);
|
||||||
|
int size = ViewUtils.dp(16);
|
||||||
|
dr.setBounds(0, 0, size, size);
|
||||||
|
sb.append("d", new TextColorImageSpan(dr, ViewUtils.dp(2f)), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
manageButton.setText(sb);
|
||||||
|
manageButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||||
|
manageButton.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
manageButton.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
manageButton.setGravity(Gravity.CENTER);
|
||||||
|
manageButton.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
||||||
|
manageButton.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||||
|
CloudAPI.SubscriptionLevel finalLvl = lvl;
|
||||||
|
manageButton.setOnClickListener(v -> v.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(finalLvl.manageUrl))));
|
||||||
|
ll.addView(manageButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(48)) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
topMargin = bottomMargin = ViewUtils.dp(6);
|
||||||
|
}});
|
||||||
|
} else {
|
||||||
|
ll.addView(new Space(context), new LinearLayout.LayoutParams(0, ViewUtils.dp(16)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextView buttonView = new TextView(context);
|
||||||
|
buttonView.setText(R.string.SettingsCloudManageButtonLogOut);
|
||||||
|
buttonView.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||||
|
buttonView.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
buttonView.setGravity(Gravity.CENTER);
|
||||||
|
buttonView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(R.attr.textColorNegative), 16));
|
||||||
|
buttonView.setOnClickListener(v-> {
|
||||||
|
CloudController.logout();
|
||||||
|
dismiss();
|
||||||
|
});
|
||||||
|
ll.addView(buttonView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
bottomMargin = ViewUtils.dp(4);
|
||||||
|
}});
|
||||||
|
|
||||||
|
setContentView(ll);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,10 @@ import android.content.res.ColorStateList;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.Path;
|
import android.graphics.Path;
|
||||||
|
import android.graphics.PointF;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
@@ -16,16 +20,23 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||||
import androidx.dynamicanimation.animation.SpringForce;
|
import androidx.dynamicanimation.animation.SpringForce;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import ru.ytkab0bp.slicebeam.R;
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.RandomUtils;
|
||||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
|
||||||
public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolderView> {
|
public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolderView> {
|
||||||
@@ -36,6 +47,7 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
public boolean isEnabled = true;
|
public boolean isEnabled = true;
|
||||||
public boolean isChecked = false;
|
public boolean isChecked = false;
|
||||||
public boolean isCheckable = false;
|
public boolean isCheckable = false;
|
||||||
|
public boolean isShiny = false;
|
||||||
public View.OnClickListener clickListener;
|
public View.OnClickListener clickListener;
|
||||||
public CompoundButton.OnCheckedChangeListener checkedChangeListener;
|
public CompoundButton.OnCheckedChangeListener checkedChangeListener;
|
||||||
|
|
||||||
@@ -61,6 +73,11 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BedMenuItem setShiny(boolean shiny) {
|
||||||
|
isShiny = shiny;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public BedMenuItem setSingleLine(boolean singleLine) {
|
public BedMenuItem setSingleLine(boolean singleLine) {
|
||||||
isSingleLine = singleLine;
|
isSingleLine = singleLine;
|
||||||
return this;
|
return this;
|
||||||
@@ -77,6 +94,9 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static class BedMenuItemHolderView extends LinearLayout implements IThemeView {
|
public final static class BedMenuItemHolderView extends LinearLayout implements IThemeView {
|
||||||
|
private final static float IN_BOUND = 0.05f;
|
||||||
|
private final static float OUT_BOUND = 0.1f;
|
||||||
|
|
||||||
private ImageView icon;
|
private ImageView icon;
|
||||||
private TextView title;
|
private TextView title;
|
||||||
|
|
||||||
@@ -84,8 +104,13 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
|
||||||
private Path path = new Path();
|
private Path path = new Path();
|
||||||
|
private Path path2 = new Path();
|
||||||
private float checkedProgress;
|
private float checkedProgress;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
private boolean shiny;
|
||||||
|
private List<Sparkle> sparkles;
|
||||||
|
private long lastDraw;
|
||||||
|
private Drawable sparkleDrawable;
|
||||||
|
|
||||||
public BedMenuItemHolderView(Context context) {
|
public BedMenuItemHolderView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -109,12 +134,17 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) {{
|
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT) {{
|
||||||
leftMargin = topMargin = bottomMargin = ViewUtils.dp(6);
|
leftMargin = topMargin = bottomMargin = ViewUtils.dp(6);
|
||||||
}});
|
}});
|
||||||
|
setClipToPadding(false);
|
||||||
|
setClipChildren(false);
|
||||||
setWillNotDraw(false);
|
setWillNotDraw(false);
|
||||||
onApplyTheme();
|
onApplyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(@NonNull Canvas canvas) {
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
long dt = Math.min(System.currentTimeMillis() - lastDraw, 16);
|
||||||
|
lastDraw = System.currentTimeMillis();
|
||||||
|
|
||||||
int rad = ViewUtils.dp(16);
|
int rad = ViewUtils.dp(16);
|
||||||
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), rad, rad, bgPaint);
|
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), rad, rad, bgPaint);
|
||||||
|
|
||||||
@@ -133,6 +163,61 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
}
|
}
|
||||||
|
|
||||||
super.draw(canvas);
|
super.draw(canvas);
|
||||||
|
|
||||||
|
if (shiny) {
|
||||||
|
float side = Math.min(getWidth(), getHeight());
|
||||||
|
canvas.save();
|
||||||
|
if (sparkles == null) sparkles = new ArrayList<>();
|
||||||
|
if (sparkleDrawable == null) {
|
||||||
|
sparkleDrawable = ContextCompat.getDrawable(SliceBeam.INSTANCE, R.drawable.sparkle_28);
|
||||||
|
sparkleDrawable.setColorFilter(new PorterDuffColorFilter(ThemesRepo.getColor(android.R.attr.colorAccent), PorterDuff.Mode.SRC_IN));
|
||||||
|
}
|
||||||
|
|
||||||
|
float p = dt / 1000f;
|
||||||
|
for (Iterator<Sparkle> iterator = sparkles.iterator(); iterator.hasNext(); ) {
|
||||||
|
Sparkle sparkle = iterator.next();
|
||||||
|
sparkle.position.x += sparkle.velocity.x * p;
|
||||||
|
sparkle.position.y += sparkle.velocity.y * p;
|
||||||
|
sparkle.velocity.x *= 0.9999f;
|
||||||
|
sparkle.velocity.y *= 0.9999f;
|
||||||
|
sparkle.living += dt;
|
||||||
|
|
||||||
|
int size = (int) (side * sparkle.size);
|
||||||
|
|
||||||
|
float fadems = 200;
|
||||||
|
if ((sparkle.position.x - sparkle.size > 0 && sparkle.position.x + sparkle.size < 1f) &&
|
||||||
|
sparkle.lifetime - sparkle.living > fadems) {
|
||||||
|
sparkle.living = (long) (sparkle.lifetime - fadems);
|
||||||
|
}
|
||||||
|
if (sparkle.living >= sparkle.lifetime) {
|
||||||
|
iterator.remove();
|
||||||
|
} else {
|
||||||
|
float alpha = sparkle.living < fadems ? sparkle.living / fadems : sparkle.living > sparkle.lifetime - fadems ? (sparkle.lifetime - sparkle.living) / fadems : 1f;
|
||||||
|
canvas.saveLayerAlpha(-OUT_BOUND * side, -OUT_BOUND * side, getWidth() + OUT_BOUND * side, getHeight() + OUT_BOUND * side, (int) (alpha * sparkle.alpha * 0xFF));
|
||||||
|
canvas.translate(sparkle.position.x * side, sparkle.position.y * side);
|
||||||
|
sparkleDrawable.setBounds(-size / 2, -size / 2, size / 2, size / 2);
|
||||||
|
sparkleDrawable.draw(canvas);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sparkles.size() < 20) {
|
||||||
|
int s = 20 - sparkles.size();
|
||||||
|
for (int i = 0; i < s; i++) {
|
||||||
|
if (RandomUtils.RANDOM.nextFloat() < 0.01f) {
|
||||||
|
Sparkle sparkle = new Sparkle();
|
||||||
|
boolean leftSide = RandomUtils.RANDOM.nextBoolean();
|
||||||
|
sparkle.position = new PointF(leftSide ? RandomUtils.randomf(-OUT_BOUND, 0) : RandomUtils.randomf(1, 1 + OUT_BOUND), RandomUtils.randomf(-OUT_BOUND, 1 + OUT_BOUND));
|
||||||
|
sparkle.velocity = new PointF(RandomUtils.randomf(-0.05f, 0.05f), RandomUtils.randomf(-0.05f, 0.05f));
|
||||||
|
sparkle.size = RandomUtils.randomf(0.1f, 0.12f);
|
||||||
|
sparkle.alpha = RandomUtils.randomf(0.5f, 1f);
|
||||||
|
sparkle.lifetime = RandomUtils.randoml(4000, 10000);
|
||||||
|
sparkles.add(sparkle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -146,10 +231,12 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
|
|
||||||
public void bind(BedMenuItem item) {
|
public void bind(BedMenuItem item) {
|
||||||
enabled = item.isEnabled;
|
enabled = item.isEnabled;
|
||||||
|
shiny = item.isShiny;
|
||||||
title.setMaxLines(item.isSingleLine ? 1 : 2);
|
title.setMaxLines(item.isSingleLine ? 1 : 2);
|
||||||
title.setText(item.titleRes);
|
title.setText(item.titleRes);
|
||||||
icon.setImageResource(item.iconRes);
|
icon.setImageResource(item.iconRes);
|
||||||
checkedProgress = item.isCheckable && item.isChecked ? 1 : 0;
|
checkedProgress = item.isCheckable && item.isChecked ? 1 : 0;
|
||||||
|
onApplyTheme();
|
||||||
title.setTextColor(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorPrimary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress));
|
title.setTextColor(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorPrimary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress));
|
||||||
icon.setImageTintList(ColorStateList.valueOf(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorSecondary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress)));
|
icon.setImageTintList(ColorStateList.valueOf(ColorUtils.blendARGB(ThemesRepo.getColor(android.R.attr.textColorSecondary), ThemesRepo.getColor(R.attr.textColorOnAccent), checkedProgress)));
|
||||||
|
|
||||||
@@ -187,5 +274,14 @@ public class BedMenuItem extends SimpleRecyclerItem<BedMenuItem.BedMenuItemHolde
|
|||||||
bgPaint.setColor(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x10));
|
bgPaint.setColor(ColorUtils.setAlphaComponent(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0x10));
|
||||||
accentPaint.setColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
accentPaint.setColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final static class Sparkle {
|
||||||
|
private PointF position;
|
||||||
|
private PointF velocity;
|
||||||
|
private float size;
|
||||||
|
private float alpha;
|
||||||
|
private long lifetime;
|
||||||
|
private long living;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Activity;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -14,12 +15,14 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -30,14 +33,22 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import ru.ytkab0bp.eventbus.EventHandler;
|
import ru.ytkab0bp.eventbus.EventHandler;
|
||||||
|
import ru.ytkab0bp.slicebeam.BeamServerData;
|
||||||
|
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||||
import ru.ytkab0bp.slicebeam.MainActivity;
|
import ru.ytkab0bp.slicebeam.MainActivity;
|
||||||
import ru.ytkab0bp.slicebeam.R;
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.SetupActivity;
|
||||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||||
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
import ru.ytkab0bp.slicebeam.components.UnfoldMenu;
|
||||||
import ru.ytkab0bp.slicebeam.components.WebViewMenu;
|
import ru.ytkab0bp.slicebeam.components.WebViewMenu;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudFeaturesUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudModelsRemainingCountUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissAIGeneratorMenu;
|
||||||
import ru.ytkab0bp.slicebeam.events.NeedDismissCalibrationsMenu;
|
import ru.ytkab0bp.slicebeam.events.NeedDismissCalibrationsMenu;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
|
import ru.ytkab0bp.slicebeam.events.SelectedObjectChangedEvent;
|
||||||
@@ -50,13 +61,18 @@ import ru.ytkab0bp.slicebeam.slic3r.Bed3D;
|
|||||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
|
||||||
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
||||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||||
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.SegmentsView;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
||||||
|
|
||||||
public class FileMenu extends ListBedMenu {
|
public class FileMenu extends ListBedMenu {
|
||||||
private final static List<String> K3D_SUPPORTED_LANGUAGES = Arrays.asList("en", "ru");
|
private final static List<String> K3D_SUPPORTED_LANGUAGES = Arrays.asList("en", "ru");
|
||||||
|
|
||||||
|
private boolean wasPortrait;
|
||||||
|
|
||||||
private String getK3DLanguage() {
|
private String getK3DLanguage() {
|
||||||
String lang = Locale.getDefault().getLanguage();
|
String lang = Locale.getDefault().getLanguage();
|
||||||
return K3D_SUPPORTED_LANGUAGES.contains(lang) ? lang : "en";
|
return K3D_SUPPORTED_LANGUAGES.contains(lang) ? lang : "en";
|
||||||
@@ -74,13 +90,18 @@ public class FileMenu extends ListBedMenu {
|
|||||||
.replace("\"", "\\\"");
|
.replace("\"", "\\\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasModel() {
|
||||||
|
return fragment.getGlView().getRenderer().getModel() != null;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasSelection() {
|
private boolean hasSelection() {
|
||||||
return fragment.getGlView().getRenderer().getModel() != null && fragment.getGlView().getRenderer().getSelectedObject() != -1;
|
return hasModel() && fragment.getGlView().getRenderer().getSelectedObject() != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
protected List<SimpleRecyclerItem> onCreateItems(boolean portrait) {
|
||||||
return Arrays.asList(
|
wasPortrait = portrait;
|
||||||
|
List<SimpleRecyclerItem> list = new ArrayList<>(Arrays.asList(
|
||||||
new BedMenuItem(R.string.MenuFileOpen, R.drawable.folder_simple_plus_outline_28).onClick(v -> {
|
new BedMenuItem(R.string.MenuFileOpen, R.drawable.folder_simple_plus_outline_28).onClick(v -> {
|
||||||
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
||||||
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
||||||
@@ -104,7 +125,31 @@ public class FileMenu extends ListBedMenu {
|
|||||||
fragment.updateModel();
|
fragment.updateModel();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new SpaceItem(portrait ? ViewUtils.dp(3) : 0, portrait ? 0 : ViewUtils.dp(3)),
|
new SpaceItem(portrait ? ViewUtils.dp(3) : 0, portrait ? 0 : ViewUtils.dp(3))));
|
||||||
|
if (BeamServerData.isBoostyAvailable() && CloudController.needShowAIGenerator()) {
|
||||||
|
list.add(new BedMenuItem(R.string.MenuFileAIGenerator, R.drawable.picture_stack_outline_28).setShiny(true).onClick(view -> {
|
||||||
|
if (Prefs.getCloudAPIToken() == null || CloudController.getUserInfo() != null && CloudController.getMaxGeneratedModels() == 0) {
|
||||||
|
Context ctx = view.getContext();
|
||||||
|
ctx.startActivity(new Intent(ctx, SetupActivity.class).putExtra(SetupActivity.EXTRA_CLOUD_PROFILE, true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CloudController.getUserInfo() == null) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuFileAIGeneratorPleaseWaitSetup).tag(CloudController.USER_INFO_AI_GEN_TAG));
|
||||||
|
ViewUtils.postOnMainThread(() -> {
|
||||||
|
if (CloudController.getUserInfo() == null) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CloudController.USER_INFO_AI_GEN_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.ERROR, R.string.MenuFileAIGeneratorErrorNotLoadedUserAccount));
|
||||||
|
} else {
|
||||||
|
fragment.showUnfoldMenu(new AIGeneratorMenu(), view);
|
||||||
|
}
|
||||||
|
}, 2500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.showUnfoldMenu(new AIGeneratorMenu(), view);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
list.addAll(Arrays.asList(
|
||||||
new BedMenuItem(R.string.MenuFileCalibrations, R.drawable.wrench_outline_28).setSingleLine(true).onClick(v -> {
|
new BedMenuItem(R.string.MenuFileCalibrations, R.drawable.wrench_outline_28).setSingleLine(true).onClick(v -> {
|
||||||
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
if (!fragment.getGlView().getRenderer().getBed().isValid()) {
|
||||||
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
Toast.makeText(fragment.getContext(), R.string.BedConfigurationError, Toast.LENGTH_SHORT).show();
|
||||||
@@ -200,14 +245,28 @@ public class FileMenu extends ListBedMenu {
|
|||||||
.show())
|
.show())
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show();
|
.show();
|
||||||
|
}),
|
||||||
|
new BedMenuItem(R.string.MenuFileExport3mf, R.drawable.arrow_down_to_square_outline_28).setEnabled(hasModel()).onClick(v -> {
|
||||||
|
if (fragment.getContext() instanceof Activity) {
|
||||||
|
Activity act = (Activity) fragment.getContext();
|
||||||
|
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
|
i.setType("application/3mf");
|
||||||
|
i.putExtra(Intent.EXTRA_TITLE, "SliceBeam_project.3mf");
|
||||||
|
act.startActivityForResult(i, MainActivity.REQUEST_CODE_EXPORT_3MF);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
));
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(runOnMainThread = true)
|
@EventHandler(runOnMainThread = true)
|
||||||
public void onObjectsChanged(ObjectsListChangedEvent e) {
|
public void onObjectsChanged(ObjectsListChangedEvent e) {
|
||||||
((BedMenuItem) adapter.getItems().get(1)).setEnabled(hasSelection());
|
((BedMenuItem) adapter.getItems().get(1)).setEnabled(hasSelection());
|
||||||
adapter.notifyItemChanged(1);
|
adapter.notifyItemChanged(1);
|
||||||
|
|
||||||
|
int i = 8 - (BeamServerData.isBoostyAvailable() && CloudController.needShowAIGenerator() ? 0 : 1);
|
||||||
|
((BedMenuItem) adapter.getItems().get(i)).setEnabled(hasModel());
|
||||||
|
adapter.notifyItemChanged(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(runOnMainThread = true)
|
@EventHandler(runOnMainThread = true)
|
||||||
@@ -216,8 +275,144 @@ public class FileMenu extends ListBedMenu {
|
|||||||
adapter.notifyItemChanged(1);
|
adapter.notifyItemChanged(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class CalibrationsMenu extends UnfoldMenu {
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onFeaturedUpdated(CloudFeaturesUpdatedEvent e) {
|
||||||
|
adapter.setItems(onCreateItems(wasPortrait));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final static class AIGeneratorMenu extends UnfoldMenu {
|
||||||
|
private TextView remainingView;
|
||||||
|
private SegmentsView segmentsView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||||
|
return (int) (portrait ? ViewUtils.dp(52) + ViewUtils.dp(60) * 2 + ViewUtils.dp(28) + ViewUtils.dp(18) + ViewUtils.dp(2) : into.getWidth() * 0.6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View onCreateView(Context ctx, boolean portrait) {
|
||||||
|
LinearLayout ll = new LinearLayout(ctx);
|
||||||
|
ll.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
RecyclerView rv = new FadeRecyclerView(ctx);
|
||||||
|
rv.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
|
SimpleRecyclerAdapter adapter = new SimpleRecyclerAdapter();
|
||||||
|
adapter.setItems(Arrays.asList(
|
||||||
|
new PreferenceItem().setIcon(R.drawable.camera_outline_28).setTitle(ctx.getString(R.string.MenuFileAIGeneratorFromCamera)).setOnClickListener(v -> {
|
||||||
|
if (CloudController.getGeneratedModels() >= CloudController.getMaxGeneratedModels()) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.ERROR, R.string.MenuFileAIGeneratorNoGenerationsLeft));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx instanceof MainActivity) {
|
||||||
|
try {
|
||||||
|
MainActivity.aiTempFile = File.createTempFile("ai_capture", ".jpg");
|
||||||
|
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
|
i.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(ctx, BuildConfig.APPLICATION_ID + ".provider", MainActivity.aiTempFile));
|
||||||
|
i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
((MainActivity) ctx).startActivityForResult(i, MainActivity.REQUEST_CODE_AI_GENERATOR_TAKE_PHOTO);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new PreferenceItem().setIcon(R.drawable.picture_outline_28).setTitle(ctx.getString(R.string.MenuFileAIGeneratorFromGallery)).setOnClickListener(v -> {
|
||||||
|
if (CloudController.getGeneratedModels() >= CloudController.getMaxGeneratedModels()) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.ERROR, R.string.MenuFileAIGeneratorNoGenerationsLeft));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx instanceof MainActivity) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setType("image/*");
|
||||||
|
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
|
((MainActivity) ctx).startActivityForResult(Intent.createChooser(intent, ""), MainActivity.REQUEST_CODE_AI_GENERATOR_CHOOSE_PHOTO);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
));
|
||||||
|
rv.setAdapter(adapter);
|
||||||
|
ll.addView(rv, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||||
|
|
||||||
|
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||||
|
|
||||||
|
remainingView = new TextView(ctx);
|
||||||
|
remainingView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13);
|
||||||
|
remainingView.setGravity(Gravity.CENTER);
|
||||||
|
remainingView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
ll.addView(remainingView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(18)) {{
|
||||||
|
topMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
|
||||||
|
segmentsView = new SegmentsView(ctx) {
|
||||||
|
@Override
|
||||||
|
protected int onGetColor(int i) {
|
||||||
|
return i == 1 ? ThemesRepo.getColor(android.R.attr.textColorSecondary) : ThemesRepo.getColor(android.R.attr.colorAccent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ll.addView(segmentsView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(12)) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(12);
|
||||||
|
topMargin = bottomMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
updateRemaining();
|
||||||
|
|
||||||
|
ll.addView(new DividerView(ctx), new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(1f)));
|
||||||
|
|
||||||
|
LinearLayout toolbar = new LinearLayout(ctx);
|
||||||
|
toolbar.setPadding(ViewUtils.dp(12), 0, ViewUtils.dp(12), 0);
|
||||||
|
toolbar.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
toolbar.setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
toolbar.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 0));
|
||||||
|
toolbar.setOnClickListener(v -> dismiss());
|
||||||
|
|
||||||
|
ImageView icon = new ImageView(ctx);
|
||||||
|
icon.setImageResource(R.drawable.arrow_left_outline_28);
|
||||||
|
icon.setColorFilter(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
toolbar.addView(icon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)));
|
||||||
|
|
||||||
|
TextView title = new TextView(ctx);
|
||||||
|
title.setText(R.string.MenuOrientationPositionBack);
|
||||||
|
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
|
||||||
|
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||||
|
toolbar.addView(title, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f) {{
|
||||||
|
leftMargin = ViewUtils.dp(12);
|
||||||
|
}});
|
||||||
|
ll.addView(toolbar, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)));
|
||||||
|
return ll;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.registerListener(this);
|
||||||
|
ViewUtils.postOnMainThread(() -> segmentsView.startAnimation(), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onDismiss(NeedDismissAIGeneratorMenu e) {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onRemainingUpdated(CloudModelsRemainingCountUpdatedEvent e) {
|
||||||
|
updateRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateRemaining() {
|
||||||
|
int rev = CloudController.getMaxGeneratedModels() - CloudController.getGeneratedModels();
|
||||||
|
remainingView.setText(SliceBeam.INSTANCE.getString(R.string.MenuFileAIGeneratorRemaining, rev, CloudController.getMaxGeneratedModels()));
|
||||||
|
segmentsView.setValues(new float[]{0, rev / (float) CloudController.getMaxGeneratedModels(), 1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class CalibrationsMenu extends UnfoldMenu {
|
||||||
|
@Override
|
||||||
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
public int getRequestedSize(FrameLayout into, boolean portrait) {
|
||||||
return (int) (portrait ? into.getHeight() * 0.35f : into.getWidth() * 0.6f);
|
return (int) (portrait ? into.getHeight() * 0.35f : into.getWidth() * 0.6f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ public abstract class ListBedMenu extends BedMenu {
|
|||||||
recyclerView = new RecyclerView(ctx);
|
recyclerView = new RecyclerView(ctx);
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(ctx, portrait ? RecyclerView.HORIZONTAL : RecyclerView.VERTICAL, false));
|
recyclerView.setLayoutManager(new LinearLayoutManager(ctx, portrait ? RecyclerView.HORIZONTAL : RecyclerView.VERTICAL, false));
|
||||||
recyclerView.setItemAnimator(null);
|
recyclerView.setItemAnimator(null);
|
||||||
|
recyclerView.setClipToPadding(false);
|
||||||
|
recyclerView.setClipChildren(false);
|
||||||
adapter = new SimpleRecyclerAdapter() {
|
adapter = new SimpleRecyclerAdapter() {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class CloudFeaturesUpdatedEvent {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class CloudLoginStateUpdatedEvent {}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class CloudModelsRemainingCountUpdatedEvent {}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class CloudUserInfoUpdatedEvent {
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class NeedDismissAIGeneratorMenu {}
|
||||||
@@ -15,9 +15,6 @@ import android.util.TypedValue;
|
|||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.webkit.WebChromeClient;
|
|
||||||
import android.webkit.WebResourceRequest;
|
|
||||||
import android.webkit.WebResourceResponse;
|
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
@@ -116,6 +113,7 @@ public class BedFragment extends Fragment {
|
|||||||
private UnfoldMenu currentUnfoldMenu;
|
private UnfoldMenu currentUnfoldMenu;
|
||||||
|
|
||||||
private BedSwipeDownLayout swipeDownLayout;
|
private BedSwipeDownLayout swipeDownLayout;
|
||||||
|
private boolean hasWebError;
|
||||||
private WebView panelWebView;
|
private WebView panelWebView;
|
||||||
private LinearLayout panelWebViewError;
|
private LinearLayout panelWebViewError;
|
||||||
private ImageView webViewErrIcon;
|
private ImageView webViewErrIcon;
|
||||||
@@ -229,7 +227,7 @@ public class BedFragment extends Fragment {
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
glView.onResume();
|
glView.onResume();
|
||||||
ConfigObject cfg = SliceBeam.CONFIG.findPrinter(SliceBeam.CONFIG.presets.get("printer"));
|
ConfigObject cfg = SliceBeam.CONFIG.findPrinter(SliceBeam.CONFIG.presets.get("printer"));
|
||||||
boolean enable = cfg != null && cfg.get("host_type") != null && !TextUtils.isEmpty(cfg.get("print_host"));
|
boolean enable = cfg != null && cfg.get("host_type") != null && !TextUtils.isEmpty(cfg.get("print_host")) && panelWebView != null;
|
||||||
swipeDownLayout.setEnableTop(enable);
|
swipeDownLayout.setEnableTop(enable);
|
||||||
if (enable) {
|
if (enable) {
|
||||||
String host = cfg.get("print_host");
|
String host = cfg.get("print_host");
|
||||||
@@ -246,6 +244,7 @@ public class BedFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
webViewProgressBar.animate().alpha(1).setDuration(150).start();
|
webViewProgressBar.animate().alpha(1).setDuration(150).start();
|
||||||
panelWebView.setAlpha(0f);
|
panelWebView.setAlpha(0f);
|
||||||
|
hasWebError = false;
|
||||||
panelWebView.loadUrl(host);
|
panelWebView.loadUrl(host);
|
||||||
panelWebViewError.animate().alpha(0).setDuration(150).setListener(new AnimatorListenerAdapter() {
|
panelWebViewError.animate().alpha(0).setDuration(150).setListener(new AnimatorListenerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -294,46 +293,58 @@ public class BedFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swipeDownLayout = new BedSwipeDownLayout(ctx);
|
swipeDownLayout = new BedSwipeDownLayout(ctx);
|
||||||
panelWebView = new WebView(ctx);
|
|
||||||
panelWebView.getSettings().setJavaScriptEnabled(true);
|
|
||||||
panelWebView.setWebViewClient(new WebViewClient() {
|
|
||||||
@Override
|
|
||||||
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
|
||||||
webViewErrDescription.setText(description);
|
|
||||||
panelWebViewError.setVisibility(View.VISIBLE);
|
|
||||||
panelWebViewError.setAlpha(0f);
|
|
||||||
panelWebViewError.animate().alpha(1).setDuration(150).setListener(null).start();
|
|
||||||
webViewProgressBar.animate().alpha(0).setDuration(150).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPageFinished(WebView view, String url) {
|
|
||||||
panelWebView.animate().alpha(1).setDuration(150).start();
|
|
||||||
webViewProgressBar.animate().alpha(0).setDuration(150).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
FrameLayout wfl = new FrameLayout(ctx);
|
FrameLayout wfl = new FrameLayout(ctx);
|
||||||
wfl.addView(panelWebView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
try {
|
||||||
panelWebViewError = new LinearLayout(ctx);
|
panelWebView = new WebView(ctx);
|
||||||
panelWebViewError.setVisibility(View.GONE);
|
panelWebView.getSettings().setJavaScriptEnabled(true);
|
||||||
panelWebViewError.setOrientation(LinearLayout.VERTICAL);
|
panelWebView.setWebViewClient(new WebViewClient() {
|
||||||
panelWebViewError.setGravity(Gravity.CENTER);
|
@Override
|
||||||
panelWebViewError.setPadding(ViewUtils.dp(12), ViewUtils.dp(12), ViewUtils.dp(12), ViewUtils.dp(12));
|
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||||
webViewErrIcon = new ImageView(ctx);
|
hasWebError = true;
|
||||||
webViewErrIcon.setImageResource(R.drawable.globe_cross_outline_28);
|
webViewErrDescription.setText(description);
|
||||||
panelWebViewError.addView(webViewErrIcon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
panelWebViewError.setVisibility(View.VISIBLE);
|
||||||
bottomMargin = ViewUtils.dp(8);
|
panelWebViewError.setAlpha(0f);
|
||||||
}});
|
panelWebViewError.animate().alpha(1).setDuration(150).setListener(new AnimatorListenerAdapter() {
|
||||||
webViewErrDescription = new TextView(ctx);
|
@Override
|
||||||
webViewErrDescription.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
public void onAnimationEnd(Animator animation) {
|
||||||
webViewErrDescription.setGravity(Gravity.CENTER);
|
panelWebView.setVisibility(View.GONE);
|
||||||
panelWebViewError.addView(webViewErrDescription);
|
}
|
||||||
wfl.addView(panelWebViewError, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
}).start();
|
||||||
|
webViewProgressBar.animate().alpha(0).setDuration(150).start();
|
||||||
|
}
|
||||||
|
|
||||||
webViewProgressBar = new ProgressBar(ctx);
|
@Override
|
||||||
webViewProgressBar.setAlpha(0f);
|
public void onPageFinished(WebView view, String url) {
|
||||||
wfl.addView(webViewProgressBar, new FrameLayout.LayoutParams(ViewUtils.dp(36), ViewUtils.dp(36), Gravity.CENTER));
|
if (!hasWebError) {
|
||||||
|
panelWebView.animate().alpha(1).setDuration(150).start();
|
||||||
|
webViewProgressBar.animate().alpha(0).setDuration(150).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
wfl.addView(panelWebView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
panelWebViewError = new LinearLayout(ctx);
|
||||||
|
panelWebViewError.setVisibility(View.GONE);
|
||||||
|
panelWebViewError.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
panelWebViewError.setGravity(Gravity.CENTER);
|
||||||
|
panelWebViewError.setPadding(ViewUtils.dp(12), ViewUtils.dp(12), ViewUtils.dp(12), ViewUtils.dp(12));
|
||||||
|
webViewErrIcon = new ImageView(ctx);
|
||||||
|
webViewErrIcon.setImageResource(R.drawable.globe_cross_outline_28);
|
||||||
|
panelWebViewError.addView(webViewErrIcon, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
||||||
|
bottomMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
webViewErrDescription = new TextView(ctx);
|
||||||
|
webViewErrDescription.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
webViewErrDescription.setGravity(Gravity.CENTER);
|
||||||
|
panelWebViewError.addView(webViewErrDescription);
|
||||||
|
wfl.addView(panelWebViewError, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
|
||||||
|
webViewProgressBar = new ProgressBar(ctx);
|
||||||
|
webViewProgressBar.setAlpha(0f);
|
||||||
|
wfl.addView(webViewProgressBar, new FrameLayout.LayoutParams(ViewUtils.dp(36), ViewUtils.dp(36), Gravity.CENTER));
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.wtf("BedFragment", "Failed to initialize webview", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (portrait) {
|
if (portrait) {
|
||||||
LinearLayout inner = new LinearLayout(ctx);
|
LinearLayout inner = new LinearLayout(ctx);
|
||||||
@@ -598,9 +609,11 @@ public class BedFragment extends Fragment {
|
|||||||
public void onApplyTheme() {
|
public void onApplyTheme() {
|
||||||
super.onApplyTheme();
|
super.onApplyTheme();
|
||||||
|
|
||||||
webViewErrIcon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
if (panelWebView != null) {
|
||||||
webViewErrDescription.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
webViewErrIcon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||||
webViewProgressBar.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
webViewErrDescription.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
webViewProgressBar.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||||
|
}
|
||||||
menuView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
menuView.setBackgroundColor(ThemesRepo.getColor(android.R.attr.windowBackground));
|
||||||
for (int i = 0; i < MenuCategory.values().length; i++) {
|
for (int i = 0; i < MenuCategory.values().length; i++) {
|
||||||
if (i != currentMenuSlot) {
|
if (i != currentMenuSlot) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import android.content.res.ColorStateList;
|
|||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -34,6 +36,8 @@ import androidx.dynamicanimation.animation.SpringAnimation;
|
|||||||
import androidx.dynamicanimation.animation.SpringForce;
|
import androidx.dynamicanimation.animation.SpringForce;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||||
import com.mrudultora.colorpicker.ColorPickerPopUp;
|
import com.mrudultora.colorpicker.ColorPickerPopUp;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -47,6 +51,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||||||
|
|
||||||
import ru.ytkab0bp.slicebeam.R;
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudAPI;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||||
import ru.ytkab0bp.slicebeam.components.BeamColorPickerPopUp;
|
import ru.ytkab0bp.slicebeam.components.BeamColorPickerPopUp;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
@@ -56,6 +62,7 @@ import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
|||||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceSwitchItem;
|
import ru.ytkab0bp.slicebeam.recycler.PreferenceSwitchItem;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.ConfigOptionDef;
|
import ru.ytkab0bp.slicebeam.slic3r.ConfigOptionDef;
|
||||||
|
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rLocalization;
|
||||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||||
@@ -68,6 +75,8 @@ import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
|||||||
import ru.ytkab0bp.slicebeam.view.ProfileDropdownView;
|
import ru.ytkab0bp.slicebeam.view.ProfileDropdownView;
|
||||||
|
|
||||||
public abstract class ProfileListFragment extends Fragment {
|
public abstract class ProfileListFragment extends Fragment {
|
||||||
|
public final static int SPECIAL_TYPE_CLOUD_HEADER = 0;
|
||||||
|
|
||||||
private final static Object ROTATION_PAYLOAD = new Object();
|
private final static Object ROTATION_PAYLOAD = new Object();
|
||||||
|
|
||||||
protected ProfileDropdownView dropdownView;
|
protected ProfileDropdownView dropdownView;
|
||||||
@@ -146,8 +155,8 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
int pos = getChildViewHolder(ch).getAdapterPosition();
|
int pos = getChildViewHolder(ch).getAdapterPosition();
|
||||||
if (pos == -1 || ch.getAlpha() < 1) continue;
|
if (pos == -1 || ch.getAlpha() < 1) continue;
|
||||||
|
|
||||||
boolean top = currentList.get(pos).title != null;
|
boolean top = currentList.get(pos).title != null || currentList.get(pos).hasSpecialType();
|
||||||
boolean bottom = pos == getAdapter().getItemCount() - 1 || currentList.get(pos + 1).title != null;
|
boolean bottom = pos == getAdapter().getItemCount() - 1 || currentList.get(pos + 1).title != null || currentList.get(pos + 1).hasSpecialType();
|
||||||
|
|
||||||
if (top && startI != -1) {
|
if (top && startI != -1) {
|
||||||
c.drawRoundRect(0, getChildAt(startI).getTop() + getChildAt(startI).getTranslationY(), getWidth(), ch.getTop() + ch.getTranslationY() - ViewUtils.dp(8), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
c.drawRoundRect(0, getChildAt(startI).getTop() + getChildAt(startI).getTranslationY(), getWidth(), ch.getTop() + ch.getTranslationY() - ViewUtils.dp(8), ViewUtils.dp(32), ViewUtils.dp(32), bgPaint);
|
||||||
@@ -204,7 +213,7 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
};
|
};
|
||||||
recyclerView.setItemAnimator(new CubicBezierItemAnimator());
|
recyclerView.setItemAnimator(new CubicBezierItemAnimator());
|
||||||
recyclerView.setAdapter(new RecyclerView.Adapter() {
|
recyclerView.setAdapter(new RecyclerView.Adapter() {
|
||||||
private final static int TYPE_TITLE = 0, TYPE_SIMPLE = 1;
|
private final static int TYPE_TITLE = 0, TYPE_CLOUD_PROFILE = 1, TYPE_SIMPLE = 2;
|
||||||
|
|
||||||
private Map<Class<?>, Integer> viewType = new HashMap<>();
|
private Map<Class<?>, Integer> viewType = new HashMap<>();
|
||||||
private Map<Integer, SimpleRecyclerItem> viewCreator = new HashMap<>();
|
private Map<Integer, SimpleRecyclerItem> viewCreator = new HashMap<>();
|
||||||
@@ -219,6 +228,9 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
v = viewCreator.get(viewType).onCreateView(ctx);
|
v = viewCreator.get(viewType).onCreateView(ctx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TYPE_CLOUD_PROFILE:
|
||||||
|
v = new CloudProfileHeaderView(ctx);
|
||||||
|
break;
|
||||||
case TYPE_TITLE:
|
case TYPE_TITLE:
|
||||||
v = new CategoryHolderView(ctx);
|
v = new CategoryHolderView(ctx);
|
||||||
break;
|
break;
|
||||||
@@ -258,6 +270,43 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
el.simpleItem.onBindView(holder.itemView);
|
el.simpleItem.onBindView(holder.itemView);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TYPE_CLOUD_PROFILE: {
|
||||||
|
OptionWrapper w = currentList.get(position);
|
||||||
|
CloudProfileHeaderView holderView = (CloudProfileHeaderView) holder.itemView;
|
||||||
|
holderView.setTag(w.color);
|
||||||
|
if (Prefs.getCloudAPIToken() != null) {
|
||||||
|
CloudAPI.UserInfo info = CloudController.getUserInfo();
|
||||||
|
if (info != null) {
|
||||||
|
if (!TextUtils.isEmpty(info.avatarUrl)) {
|
||||||
|
holderView.hasAvatar = true;
|
||||||
|
Glide.with(holderView.avatar)
|
||||||
|
.load(info.avatarUrl)
|
||||||
|
.circleCrop()
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
.into(holderView.avatar);
|
||||||
|
} else {
|
||||||
|
holderView.hasAvatar = false;
|
||||||
|
holderView.avatar.setImageResource(R.drawable.user_circle_outline_28);
|
||||||
|
}
|
||||||
|
|
||||||
|
holderView.title.setText(info.displayName);
|
||||||
|
} else {
|
||||||
|
holderView.hasAvatar = false;
|
||||||
|
holderView.avatar.setImageResource(R.drawable.user_circle_outline_28);
|
||||||
|
|
||||||
|
holderView.title.setText(R.string.SettingsCloudLoading);
|
||||||
|
}
|
||||||
|
holderView.subtitle.setText(R.string.SettingsCloudTapToManage);
|
||||||
|
} else {
|
||||||
|
holderView.hasAvatar = false;
|
||||||
|
holderView.avatar.setImageResource(R.drawable.user_circle_outline_28);
|
||||||
|
holderView.title.setText(R.string.SettingsCloudNotLoggedIn);
|
||||||
|
holderView.subtitle.setText(R.string.SettingsCloudTapToShowMore);
|
||||||
|
}
|
||||||
|
holderView.onApplyTheme();
|
||||||
|
holderView.setOnClickListener(view -> w.onClick.run());
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TYPE_TITLE: {
|
case TYPE_TITLE: {
|
||||||
OptionWrapper w = currentList.get(position);
|
OptionWrapper w = currentList.get(position);
|
||||||
CategoryHolderView holderView = (CategoryHolderView) holder.itemView;
|
CategoryHolderView holderView = (CategoryHolderView) holder.itemView;
|
||||||
@@ -301,6 +350,13 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
OptionWrapper w = currentList.get(position);
|
OptionWrapper w = currentList.get(position);
|
||||||
|
if (w.optionEl != null && w.optionEl.specialType != -1) {
|
||||||
|
switch (w.optionEl.specialType) {
|
||||||
|
default:
|
||||||
|
case SPECIAL_TYPE_CLOUD_HEADER:
|
||||||
|
return TYPE_CLOUD_PROFILE;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (w.title != null) return TYPE_TITLE;
|
if (w.title != null) return TYPE_TITLE;
|
||||||
|
|
||||||
if (w.optionEl.simpleItem != null) {
|
if (w.optionEl.simpleItem != null) {
|
||||||
@@ -469,7 +525,12 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
OptionElement el = items.get(i);
|
OptionElement el = items.get(i);
|
||||||
if (el == null) continue;
|
if (el == null) continue;
|
||||||
OptionWrapper w = el.title != null ? new OptionWrapper(el.icon, el.title, el.onClick, el.color, el.noTint) : new OptionWrapper(el);
|
OptionWrapper w = el.title != null ? new OptionWrapper(el.icon, el.title, el.onClick, el.color, el.noTint) : new OptionWrapper(el);
|
||||||
if (el.title != null) {
|
if (el.specialType != -1) {
|
||||||
|
w.color = el.color;
|
||||||
|
w.noTint = el.noTint;
|
||||||
|
w.onClick = el.onClick;
|
||||||
|
}
|
||||||
|
if (el.title != null || el.specialType != -1) {
|
||||||
w.categoryIndex = j;
|
w.categoryIndex = j;
|
||||||
categoryElements.put(j, new ArrayList<>());
|
categoryElements.put(j, new ArrayList<>());
|
||||||
j++;
|
j++;
|
||||||
@@ -563,6 +624,8 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class OptionElement {
|
public final class OptionElement {
|
||||||
|
public int specialType = -1;
|
||||||
|
|
||||||
public int icon;
|
public int icon;
|
||||||
public String title;
|
public String title;
|
||||||
public int color;
|
public int color;
|
||||||
@@ -916,6 +979,10 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
optionEl = el;
|
optionEl = el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasSpecialType() {
|
||||||
|
return optionEl != null && optionEl.specialType != -1;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(Context ctx) {
|
public View onCreateView(Context ctx) {
|
||||||
FrameLayout v = new FrameLayout(ctx);
|
FrameLayout v = new FrameLayout(ctx);
|
||||||
@@ -926,6 +993,49 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final static class CloudProfileHeaderView extends LinearLayout implements IThemeView {
|
||||||
|
private ImageView avatar;
|
||||||
|
private TextView title;
|
||||||
|
private TextView subtitle;
|
||||||
|
private boolean hasAvatar;
|
||||||
|
|
||||||
|
public CloudProfileHeaderView(Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
setOrientation(HORIZONTAL);
|
||||||
|
setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
setPadding(ViewUtils.dp(21), ViewUtils.dp(16), ViewUtils.dp(21), ViewUtils.dp(16));
|
||||||
|
|
||||||
|
avatar = new ImageView(context);
|
||||||
|
addView(avatar, new LayoutParams(ViewUtils.dp(26), ViewUtils.dp(26)) {{
|
||||||
|
setMarginEnd(ViewUtils.dp(12));
|
||||||
|
}});
|
||||||
|
|
||||||
|
LinearLayout ll = new LinearLayout(context);
|
||||||
|
ll.setOrientation(VERTICAL);
|
||||||
|
ll.setGravity(Gravity.CENTER);
|
||||||
|
title = new TextView(context);
|
||||||
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
|
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
ll.addView(title);
|
||||||
|
subtitle = new TextView(context);
|
||||||
|
subtitle.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||||
|
ll.addView(subtitle);
|
||||||
|
addView(ll, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||||
|
|
||||||
|
onApplyTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplyTheme() {
|
||||||
|
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 32));
|
||||||
|
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||||
|
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
if (!hasAvatar) {
|
||||||
|
avatar.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private final static class CategoryHolderView extends LinearLayout implements IThemeView {
|
private final static class CategoryHolderView extends LinearLayout implements IThemeView {
|
||||||
private ImageView icon;
|
private ImageView icon;
|
||||||
private TextView title;
|
private TextView title;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
|||||||
import ru.ytkab0bp.slicebeam.components.BeamColorPickerPopUp;
|
import ru.ytkab0bp.slicebeam.components.BeamColorPickerPopUp;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudUserInfoUpdatedEvent;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
import ru.ytkab0bp.slicebeam.recycler.PreferenceItem;
|
||||||
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
||||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
@@ -45,6 +46,10 @@ public class SettingsFragment extends ProfileListFragment {
|
|||||||
@Override
|
@Override
|
||||||
protected List<OptionElement> getConfigItems() {
|
protected List<OptionElement> getConfigItems() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
|
BeamServerData.isCloudAvailable() ? new OptionElement(SPECIAL_TYPE_CLOUD_HEADER).setOnClick(() -> {
|
||||||
|
Activity act = (Activity) getContext();
|
||||||
|
act.startActivity(new Intent(act, SetupActivity.class).putExtra(SetupActivity.EXTRA_CLOUD_PROFILE, true));
|
||||||
|
}) : null,
|
||||||
new OptionElement(R.drawable.paint_roller_outline_28, getContext().getString(R.string.SettingsInterface)),
|
new OptionElement(R.drawable.paint_roller_outline_28, getContext().getString(R.string.SettingsInterface)),
|
||||||
new OptionElement(new PreferenceItem().setTitle(getContext().getString(R.string.SettingsInterfaceTheme)).setValueProvider(() -> getContext().getString(Prefs.getThemeMode().title)).setOnClickListener(v -> {
|
new OptionElement(new PreferenceItem().setTitle(getContext().getString(R.string.SettingsInterfaceTheme)).setValueProvider(() -> getContext().getString(Prefs.getThemeMode().title)).setOnClickListener(v -> {
|
||||||
String[] items = new String[Prefs.ThemeMode.values().length];
|
String[] items = new String[Prefs.ThemeMode.values().length];
|
||||||
@@ -107,7 +112,7 @@ public class SettingsFragment extends ProfileListFragment {
|
|||||||
BeamTheme.LIGHT.colors.put(android.R.attr.colorAccent, Prefs.getAccentColor());
|
BeamTheme.LIGHT.colors.put(android.R.attr.colorAccent, Prefs.getAccentColor());
|
||||||
BeamTheme.DARK.colors.put(android.R.attr.colorAccent, Prefs.getAccentColor());
|
BeamTheme.DARK.colors.put(android.R.attr.colorAccent, Prefs.getAccentColor());
|
||||||
ThemesRepo.invalidate((Activity) getContext());
|
ThemesRepo.invalidate((Activity) getContext());
|
||||||
recyclerView.getAdapter().notifyItemChanged(1);
|
recyclerView.getAdapter().notifyItemChanged(2 - (BeamServerData.isCloudAvailable() ? 0 : 1));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButtonText(getContext().getString(R.string.SettingsInterfaceColorReset))
|
.setNegativeButtonText(getContext().getString(R.string.SettingsInterfaceColorReset))
|
||||||
@@ -130,7 +135,7 @@ public class SettingsFragment extends ProfileListFragment {
|
|||||||
Prefs.setRenderScale(variants[which]);
|
Prefs.setRenderScale(variants[which]);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
// I'm too lazy to calculate real position for now
|
// I'm too lazy to calculate real position for now
|
||||||
recyclerView.getAdapter().notifyItemChanged(3);
|
recyclerView.getAdapter().notifyItemChanged(4 - (BeamServerData.isCloudAvailable() ? 0 : 1));
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
})),
|
})),
|
||||||
@@ -177,6 +182,13 @@ public class SettingsFragment extends ProfileListFragment {
|
|||||||
setConfigItems(getConfigItems());
|
setConfigItems(getConfigItems());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onUserInfoUpdated(CloudUserInfoUpdatedEvent e) {
|
||||||
|
if (BeamServerData.isCloudAvailable()) {
|
||||||
|
recyclerView.getAdapter().notifyItemChanged(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cloneCurrentProfile() {}
|
protected void cloneCurrentProfile() {}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import androidx.core.content.ContextCompat;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.theme.BeamTheme;
|
||||||
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
import ru.ytkab0bp.slicebeam.theme.IThemeView;
|
||||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
@@ -35,6 +36,8 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
private boolean noTint;
|
private boolean noTint;
|
||||||
private ValueProvider valueProvider;
|
private ValueProvider valueProvider;
|
||||||
private float roundRadius;
|
private float roundRadius;
|
||||||
|
private int mPaddings = ViewUtils.dp(12);
|
||||||
|
private boolean mForceDark;
|
||||||
|
|
||||||
public PreferenceItem setTitle(CharSequence title) {
|
public PreferenceItem setTitle(CharSequence title) {
|
||||||
mTitle = title;
|
mTitle = title;
|
||||||
@@ -46,6 +49,16 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PreferenceItem setPaddings(int paddings) {
|
||||||
|
this.mPaddings = paddings;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreferenceItem setForceDark(boolean mForceDark) {
|
||||||
|
this.mForceDark = mForceDark;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PreferenceItem setSubtitleProvider(ValueProvider mSubtitle) {
|
public PreferenceItem setSubtitleProvider(ValueProvider mSubtitle) {
|
||||||
this.mSubtitle = mSubtitle;
|
this.mSubtitle = mSubtitle;
|
||||||
return this;
|
return this;
|
||||||
@@ -112,6 +125,8 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
private TextView value;
|
private TextView value;
|
||||||
private float radius;
|
private float radius;
|
||||||
|
|
||||||
|
private PreferenceItem item;
|
||||||
|
|
||||||
public PreferenceHolderView(Context context) {
|
public PreferenceHolderView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
@@ -165,14 +180,14 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
value.setVisibility(GONE);
|
value.setVisibility(GONE);
|
||||||
addView(value, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
addView(value, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
int pad = ViewUtils.dp(12);
|
|
||||||
setPadding(pad, pad, pad, pad);
|
|
||||||
setMinimumHeight(ViewUtils.dp(56));
|
setMinimumHeight(ViewUtils.dp(56));
|
||||||
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
onApplyTheme();
|
onApplyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(PreferenceItem item) {
|
void bind(PreferenceItem item) {
|
||||||
|
this.item = item;
|
||||||
|
setPadding(item.mPaddings, item.mPaddings, item.mPaddings, item.mPaddings);
|
||||||
title.setText(item.mTitle);
|
title.setText(item.mTitle);
|
||||||
title.setVisibility(TextUtils.isEmpty(item.mTitle) ? GONE : VISIBLE);
|
title.setVisibility(TextUtils.isEmpty(item.mTitle) ? GONE : VISIBLE);
|
||||||
|
|
||||||
@@ -217,15 +232,19 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
|
|
||||||
ViewGroup.LayoutParams params = icon.getLayoutParams();
|
ViewGroup.LayoutParams params = icon.getLayoutParams();
|
||||||
params.width = params.height = radius != 0 ? ViewUtils.dp(42) : ViewUtils.dp(28);
|
params.width = params.height = radius != 0 ? ViewUtils.dp(42) : ViewUtils.dp(28);
|
||||||
|
if (item.mForceDark) {
|
||||||
|
onApplyTheme();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplyTheme() {
|
public void onApplyTheme() {
|
||||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
BeamTheme theme = item != null && item.mForceDark ? BeamTheme.DARK : ThemesRepo.getCurrent();
|
||||||
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
title.setTextColor(theme.colors.get(android.R.attr.textColorPrimary));
|
||||||
value.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
subtitle.setTextColor(theme.colors.get(android.R.attr.textColorSecondary));
|
||||||
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
value.setTextColor(theme.colors.get(android.R.attr.textColorSecondary));
|
||||||
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
icon.setImageTintList(ColorStateList.valueOf(theme.colors.get(android.R.attr.textColorSecondary)));
|
||||||
|
setBackground(ViewUtils.createRipple(theme.colors.get(android.R.attr.colorControlHighlight), 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,10 @@ public class PreferenceSwitchItem extends SimpleRecyclerItem<PreferenceSwitchIte
|
|||||||
|
|
||||||
icon = new ImageView(context);
|
icon = new ImageView(context);
|
||||||
icon.setLayoutParams(new LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
icon.setLayoutParams(new LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
||||||
setMarginEnd(ViewUtils.dp(16));
|
setMarginStart(ViewUtils.dp(4));
|
||||||
gravity = Gravity.CENTER_VERTICAL;
|
setMarginEnd(ViewUtils.dp(8));
|
||||||
}});
|
}});
|
||||||
|
addView(icon);
|
||||||
|
|
||||||
LinearLayout innerLayout = new LinearLayout(context);
|
LinearLayout innerLayout = new LinearLayout(context);
|
||||||
innerLayout.setOrientation(VERTICAL);
|
innerLayout.setOrientation(VERTICAL);
|
||||||
@@ -168,7 +169,7 @@ public class PreferenceSwitchItem extends SimpleRecyclerItem<PreferenceSwitchIte
|
|||||||
public void onApplyTheme() {
|
public void onApplyTheme() {
|
||||||
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
title.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||||
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.colorAccent)));
|
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorSecondary)));
|
||||||
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class BeamTheme {
|
|||||||
colors.put(R.attr.dialogBackground, 0xffffffff);
|
colors.put(R.attr.dialogBackground, 0xffffffff);
|
||||||
colors.put(R.attr.switchThumbUncheckedColor, 0xffeef2f3);
|
colors.put(R.attr.switchThumbUncheckedColor, 0xffeef2f3);
|
||||||
colors.put(R.attr.boostyColorTop, 0xfff06e2a);
|
colors.put(R.attr.boostyColorTop, 0xfff06e2a);
|
||||||
colors.put(R.attr.boostyColorBottom, 0xfffce2d4);
|
colors.put(R.attr.boostyColorBottom, 0xff884725);
|
||||||
colors.put(R.attr.telegramColor, 0xff27a7e7);
|
colors.put(R.attr.telegramColor, 0xff27a7e7);
|
||||||
colors.put(R.attr.k3dColor, 0xff039045);
|
colors.put(R.attr.k3dColor, 0xff039045);
|
||||||
colors.put(R.attr.modelHoverColor, 0xffffffff);
|
colors.put(R.attr.modelHoverColor, 0xffffffff);
|
||||||
|
|||||||
@@ -125,6 +125,100 @@ public class Prefs {
|
|||||||
cachedThemeMode = null;
|
cachedThemeMode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCloudAPIToken() {
|
||||||
|
return mPrefs.getString("cloud_api_token", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudAPIToken(String token) {
|
||||||
|
SharedPreferences.Editor e = mPrefs.edit();
|
||||||
|
if (token == null) {
|
||||||
|
e.remove("cloud_api_token");
|
||||||
|
} else {
|
||||||
|
e.putString("cloud_api_token", token);
|
||||||
|
}
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCloudProfileSyncEnabled() {
|
||||||
|
return mPrefs.getBoolean("cloud_profile_sync", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudProfileSyncEnabled(boolean en) {
|
||||||
|
mPrefs.edit().putBoolean("cloud_profile_sync", en).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCloudCachedUserInfo() {
|
||||||
|
return mPrefs.getString("cloud_cached_user_info", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudCachedUserInfo(String info) {
|
||||||
|
SharedPreferences.Editor e = mPrefs.edit();
|
||||||
|
if (info == null) {
|
||||||
|
e.remove("cloud_cached_user_info");
|
||||||
|
} else {
|
||||||
|
e.putString("cloud_cached_user_info", info);
|
||||||
|
}
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCloudCachedUsedModels() {
|
||||||
|
return mPrefs.getInt("cloud_cached_models_used", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCloudCachedMaxModels() {
|
||||||
|
return mPrefs.getInt("cloud_cached_models_max", 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudCachedUsedMaxModels(int used, int max) {
|
||||||
|
mPrefs.edit().putInt("cloud_cached_models_used", used).putInt("cloud_cached_models_max", max).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCloudCachedUserFeatures() {
|
||||||
|
return mPrefs.getString("cloud_cached_user_features", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudCachedUserFeatures(String features) {
|
||||||
|
SharedPreferences.Editor e = mPrefs.edit();
|
||||||
|
if (features == null) {
|
||||||
|
e.remove("cloud_cached_user_features");
|
||||||
|
} else {
|
||||||
|
e.putString("cloud_cached_user_features", features);
|
||||||
|
}
|
||||||
|
e.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getCloudLastFeaturesSync() {
|
||||||
|
return mPrefs.getLong("cloud_last_features_sync", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudLastFeaturesSync(long ls) {
|
||||||
|
mPrefs.edit().putLong("cloud_last_features_sync", ls).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getCloudLastSync() {
|
||||||
|
return mPrefs.getLong("cloud_last_sync", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudLastSync(long ls) {
|
||||||
|
mPrefs.edit().putLong("cloud_last_sync", ls).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getLocalLastModified() {
|
||||||
|
return mPrefs.getLong("cloud_local_last_modified", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLocalLastModified(long lm) {
|
||||||
|
mPrefs.edit().putLong("cloud_local_last_modified", lm).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getRemoteLastModified() {
|
||||||
|
return mPrefs.getLong("cloud_remote_last_modified", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setRemoteLastModified(long lm) {
|
||||||
|
mPrefs.edit().putLong("cloud_remote_last_modified", lm).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public enum ThemeMode {
|
public enum ThemeMode {
|
||||||
SYSTEM(R.string.SettingsInterfaceThemeSystem),
|
SYSTEM(R.string.SettingsInterfaceThemeSystem),
|
||||||
LIGHT(R.string.SettingsInterfaceThemeLight),
|
LIGHT(R.string.SettingsInterfaceThemeLight),
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.utils;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class RandomUtils {
|
||||||
|
|
||||||
|
public final static Random RANDOM = new Random();
|
||||||
|
|
||||||
|
public static float randomf(float min, float max) {
|
||||||
|
return min + RANDOM.nextFloat() * (max - min);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long randoml(long min, long max) {
|
||||||
|
return (long) (min + RANDOM.nextDouble() * (max - min));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,11 @@ public class ViewUtils {
|
|||||||
private static Handler uiHandler = new Handler(Looper.getMainLooper());
|
private static Handler uiHandler = new Handler(Looper.getMainLooper());
|
||||||
private static Map<String, Typeface> typefaceCache = new HashMap<>();
|
private static Map<String, Typeface> typefaceCache = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
public static Handler getUiHandler() {
|
||||||
|
return uiHandler;
|
||||||
|
}
|
||||||
|
|
||||||
public static void postOnMainThread(Runnable runnable) {
|
public static void postOnMainThread(Runnable runnable) {
|
||||||
uiHandler.post(runnable);
|
uiHandler.post(runnable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ public class SegmentsView extends View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int onGetColor(int i) {
|
||||||
|
return mapColor(i);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(@NonNull Canvas canvas) {
|
protected void onDraw(@NonNull Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
@@ -125,7 +129,7 @@ public class SegmentsView extends View {
|
|||||||
for (int i = 1; i < currentValues.length; i++) {
|
for (int i = 1; i < currentValues.length; i++) {
|
||||||
float prev = currentValues[i - 1];
|
float prev = currentValues[i - 1];
|
||||||
float to = currentValues[i];
|
float to = currentValues[i];
|
||||||
paint.setColor(mapColor(i - 1));
|
paint.setColor(onGetColor(i - 1));
|
||||||
canvas.drawRect(l + prev * dw, 0, l + to * dw, getHeight(), paint);
|
canvas.drawRect(l + prev * dw, 0, l + to * dw, getHeight(), paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1243,17 +1243,23 @@ extern "C" {
|
|||||||
JNIEXPORT jint JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_shader_1get_1uniform_1location(JNIEnv* env, jclass, jlong ptr, jstring name) {
|
JNIEXPORT jint JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_shader_1get_1uniform_1location(JNIEnv* env, jclass, jlong ptr, jstring name) {
|
||||||
const char* chars = env->GetStringUTFChars(name, JNI_FALSE);
|
const char* chars = env->GetStringUTFChars(name, JNI_FALSE);
|
||||||
ShaderRef* shader = (ShaderRef*) (intptr_t) ptr;
|
ShaderRef* shader = (ShaderRef*) (intptr_t) ptr;
|
||||||
int location = shader->program.get_uniform_location(chars);
|
if (shader) {
|
||||||
env->ReleaseStringUTFChars(name, chars);
|
int location = shader->program.get_uniform_location(chars);
|
||||||
return location;
|
env->ReleaseStringUTFChars(name, chars);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_shader_1get_1attrib_1location(JNIEnv* env, jclass, jlong ptr, jstring name) {
|
JNIEXPORT jint JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_shader_1get_1attrib_1location(JNIEnv* env, jclass, jlong ptr, jstring name) {
|
||||||
const char* chars = env->GetStringUTFChars(name, JNI_FALSE);
|
const char *chars = env->GetStringUTFChars(name, JNI_FALSE);
|
||||||
ShaderRef* shader = (ShaderRef*) (intptr_t) ptr;
|
ShaderRef *shader = (ShaderRef *) (intptr_t) ptr;
|
||||||
int location = shader->program.get_attrib_location(chars);
|
if (shader) {
|
||||||
env->ReleaseStringUTFChars(name, chars);
|
int location = shader->program.get_attrib_location(chars);
|
||||||
return location;
|
env->ReleaseStringUTFChars(name, chars);
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_shader_1start_1using(JNIEnv* env, jclass, jlong ptr) {
|
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_shader_1start_1using(JNIEnv* env, jclass, jlong ptr) {
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M12.073,2C12.824,2 13.464,2.233 14,2.593C14.536,2.233 15.176,2 15.927,2C17.932,2 19.58,3.618 19.58,5.642C19.58,6.04 19.539,6.493 19.377,6.977C19.215,7.465 18.964,7.884 18.662,8.262C18.115,8.946 17.279,9.63 16.238,10.435L15.523,10.988C14.626,11.681 13.374,11.681 12.477,10.988L11.762,10.435C10.72,9.63 9.885,8.946 9.338,8.262C9.036,7.884 8.785,7.465 8.623,6.977C8.461,6.493 8.42,6.04 8.42,5.642C8.42,3.618 10.068,2 12.073,2ZM12.073,4C11.16,4 10.42,4.735 10.42,5.642C10.42,6.671 10.845,7.199 12.985,8.853L13.7,9.405C13.877,9.541 14.123,9.541 14.3,9.405L15.015,8.853C17.155,7.199 17.58,6.671 17.58,5.642C17.58,4.735 16.84,4 15.927,4C15.375,4 14.873,4.312 14.401,4.99L14.268,5.182C14.165,5.329 13.963,5.366 13.815,5.264C13.783,5.242 13.755,5.214 13.732,5.182L13.599,4.99C13.127,4.312 12.625,4 12.073,4ZM6.885,10.383C7.397,10.175 7.642,9.591 7.434,9.079C7.225,8.568 6.642,8.323 6.13,8.531L4.481,9.203L4.361,9.252C3.811,9.476 3.312,9.678 2.929,10.03C2.594,10.337 2.337,10.719 2.179,11.145C1.998,11.633 1.999,12.171 2,12.765L2,12.895V18.527L2,18.648C1.999,19.185 1.998,19.686 2.163,20.144C2.307,20.543 2.542,20.904 2.849,21.198C3.201,21.534 3.659,21.736 4.151,21.953L4.151,21.953L4.261,22.002L12.083,25.466L12.172,25.505C12.642,25.713 13.03,25.886 13.446,25.956C13.813,26.017 14.187,26.017 14.553,25.956C14.969,25.886 15.358,25.713 15.828,25.505L15.828,25.505L15.916,25.466L23.739,22.002L23.849,21.953C24.341,21.736 24.799,21.534 25.151,21.198C25.458,20.904 25.693,20.543 25.837,20.144C26.002,19.686 26.001,19.185 26,18.648V18.648L26,18.527V12.896L26,12.766C26.001,12.172 26.002,11.634 25.821,11.146C25.663,10.719 25.405,10.337 25.07,10.03C24.687,9.678 24.188,9.476 23.637,9.253L23.516,9.204L21.869,8.533C21.358,8.325 20.774,8.571 20.566,9.083C20.358,9.594 20.604,10.178 21.115,10.386L22.718,11.038L15.108,14.414C14.504,14.682 14.358,14.739 14.222,14.762C14.075,14.787 13.925,14.787 13.778,14.762C13.642,14.739 13.496,14.682 12.891,14.414L9.156,12.757L5.281,11.037L6.885,10.383ZM15.92,16.242L24,12.657C24,12.73 24,12.809 24,12.896V18.527C24,19.263 23.986,19.381 23.955,19.465C23.916,19.574 23.852,19.673 23.768,19.753C23.704,19.814 23.602,19.875 22.929,20.173L15.106,23.637L15,23.684V16.621C15.263,16.535 15.53,16.416 15.831,16.282L15.92,16.242ZM12.169,16.282C12.469,16.415 12.737,16.534 13,16.621V23.684L12.893,23.637L5.071,20.173C4.398,19.875 4.296,19.814 4.232,19.753C4.148,19.673 4.084,19.574 4.045,19.465C4.014,19.381 4,19.263 4,18.527V12.895C4,12.808 4,12.729 4.001,12.658L8.344,14.585L12.08,16.242L12.169,16.282L12.169,16.282Z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="m11.181,6.016c-0.543,-0.1 -1.064,0.26 -1.164,0.803 -0.071,0.387 -0.58,1.206 -1.998,1.18 -0.552,-0.01 -1.008,0.429 -1.018,0.981s0.429,1.008 0.981,1.018c2.349,0.043 3.745,-1.422 4.002,-2.818 0.1,-0.543 -0.26,-1.064 -0.803,-1.164z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m17.265,7.028c0.537,-0.13 1.077,0.201 1.207,0.738 0.158,0.655 0.392,1.266 0.761,1.679 0.318,0.355 0.789,0.63 1.661,0.538 0.549,-0.058 1.041,0.34 1.099,0.889 0.058,0.549 -0.34,1.041 -0.89,1.099 -1.5,0.159 -2.608,-0.35 -3.362,-1.194 -0.703,-0.786 -1.033,-1.789 -1.215,-2.543 -0.13,-0.537 0.201,-1.077 0.738,-1.207z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m10.569,21.498c-0.551,0.038 -1.028,-0.378 -1.067,-0.929 -0.047,-0.683 -0.171,-1.25 -0.479,-1.738 -0.299,-0.475 -0.836,-0.977 -1.896,-1.403 -0.512,-0.206 -0.761,-0.788 -0.555,-1.301 0.206,-0.512 0.788,-0.761 1.301,-0.555 1.364,0.548 2.275,1.29 2.843,2.193 0.56,0.89 0.724,1.836 0.781,2.666 0.038,0.551 -0.378,1.028 -0.929,1.067z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m10.017,6.819c0.1,-0.543 0.621,-0.903 1.164,-0.803 0.543,0.1 0.903,0.621 0.803,1.164 -0.257,1.397 -1.653,2.862 -4.002,2.818 -0.552,-0.01 -0.992,-0.466 -0.981,-1.018s0.466,-0.992 1.018,-0.981c1.418,0.026 1.927,-0.793 1.998,-1.18zM9.502,20.569c0.038,0.551 0.516,0.967 1.067,0.929s0.967,-0.516 0.929,-1.067c-0.057,-0.83 -0.221,-1.776 -0.781,-2.666 -0.569,-0.903 -1.479,-1.645 -2.843,-2.193 -0.512,-0.206 -1.095,0.043 -1.301,0.555 -0.206,0.512 0.043,1.095 0.555,1.301 1.06,0.426 1.597,0.928 1.896,1.403 0.307,0.488 0.431,1.055 0.479,1.738z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m11.624,2c-2.03,0 -3.766,1.209 -4.579,2.938 -2.37,0.718 -4.045,3.017 -4.045,5.67 0,1.206 0.344,2.333 0.939,3.273 -0.53,0.683 -0.842,1.551 -0.842,2.486 0,1.421 0.726,2.701 1.849,3.396 -0.049,0.233 -0.074,0.473 -0.074,0.72 0,1.804 1.46,3.411 3.307,3.352 0.709,1.285 2.062,2.166 3.632,2.166 0.807,0 1.56,-0.234 2.196,-0.637 0.645,0.403 1.408,0.637 2.224,0.637 1.613,0 3.008,-0.908 3.719,-2.237 1.767,-0.023 3.111,-1.521 3.111,-3.262 0,-0.263 -0.03,-0.519 -0.087,-0.765 1.22,-0.672 2.025,-2.004 2.025,-3.498 0,-0.984 -0.348,-1.893 -0.931,-2.591 0.308,-0.536 0.539,-1.125 0.678,-1.749 0.061,-0.276 0.105,-0.558 0.128,-0.847 0.014,-0.175 0.022,-0.352 0.022,-0.531 0,-2.632 -1.611,-4.911 -3.912,-5.749 -0.94,-1.652 -2.709,-2.772 -4.744,-2.772 -0.844,0 -1.645,0.193 -2.359,0.538 -0.68,-0.344 -1.447,-0.538 -2.258,-0.538zM11.624,4c-1.325,0 -2.473,0.868 -2.9,2.111 -0.116,0.339 -0.405,0.59 -0.757,0.657 -1.643,0.315 -2.967,1.876 -2.967,3.84 0,0.73 0.185,1.409 0.502,1.988 0.086,-0.048 0.173,-0.093 0.261,-0.136 1.29,-0.63 2.879,-0.807 4.208,-0.349 0.522,0.18 0.8,0.749 0.62,1.271 -0.18,0.522 -0.749,0.8 -1.271,0.62 -0.752,-0.259 -1.79,-0.18 -2.679,0.255 -0.815,0.398 -1.544,1.156 -1.544,2.11 0,0.96 0.62,1.702 1.362,1.891 0.305,0.078 0.556,0.295 0.676,0.586 0.121,0.291 0.097,0.622 -0.063,0.893 -0.125,0.211 -0.2,0.465 -0.2,0.745 0,0.809 0.6,1.354 1.209,1.354 0.129,0 0.253,-0.022 0.368,-0.064 0.259,-0.093 0.544,-0.075 0.789,0.049 0.245,0.124 0.429,0.343 0.507,0.607 0.276,0.925 1.108,1.573 2.066,1.573 0.438,0 0.846,-0.134 1.189,-0.368v-4.19,-0.007 -8.88,-0.007 -6.215c-0.414,-0.214 -0.881,-0.334 -1.376,-0.334zM16.241,4c1.383,0 2.583,0.82 3.139,2.018 0.124,0.267 0.358,0.465 0.642,0.543 1.626,0.448 2.876,2.026 2.876,3.958 0,0.119 -0.005,0.237 -0.014,0.354 -0.016,0.192 -0.043,0.381 -0.084,0.569 -0.175,0.754 -0.602,1.543 -1.213,2.189 -0.759,0.802 -1.693,1.274 -2.586,1.274 -0.552,0 -1,0.448 -1,1s0.448,1 1,1c1.455,0 2.772,-0.686 3.767,-1.628 0.147,0.281 0.233,0.608 0.233,0.96 0,1.012 -0.697,1.792 -1.527,1.934 -0.338,0.058 -0.623,0.285 -0.754,0.601 -0.132,0.317 -0.092,0.679 0.105,0.959 0.147,0.21 0.238,0.474 0.238,0.768 0,0.73 -0.542,1.262 -1.117,1.262 -0.575,0 -0.833,-0.212 -1.21,-0.547 -0.413,-0.367 -0.546,-0.725 -0.546,-0.882 0,-0.552 -0.448,-1 -1,-1 -0.552,0 -1,0.448 -1,1 0,0.948 0.575,1.805 1.217,2.376 0.174,0.154 0.366,0.3 0.572,0.431 -0.409,0.524 -1.042,0.858 -1.747,0.858 -0.455,0 -0.878,-0.138 -1.232,-0.377v-19.392c0.385,-0.149 0.803,-0.231 1.241,-0.231z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M7,11.5a5.5,5.5 0,0 1,10.853 -1.268,1 1,0 0,0 1.002,0.77L19,11c1.652,0 3.117,0.8 4.03,2.04a1,1 0,0 0,1.61 -1.187,6.994 6.994,0 0,0 -5.059,-2.83A7.502,7.502 0,0 0,5.006 11.8,6 6,0 0,0 8,23h6.502a1,1 0,1 0,0 -2H8a4,4 0,0 1,-1.551 -7.688,1 1,0 0,0 0.602,-1.058A5.558,5.558 0,0 1,7 11.5ZM22,15a1,1 0,0 1,1 1v3h3a1,1 0,1 1,0 2h-3v3a1,1 0,1 1,-2 0v-3h-3a1,1 0,1 1,0 -2h3v-3a1,1 0,0 1,1 -1Z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="m5.146,3.634c0.762,-0.408 1.512,-0.534 3.081,-0.534h1.872c0.497,0 0.9,0.403 0.9,0.9 0,0.497 -0.403,0.9 -0.9,0.9h-1.872c-1.487,0 -1.871,0.128 -2.233,0.322 -0.336,0.18 -0.594,0.438 -0.774,0.774 -0.194,0.362 -0.322,0.746 -0.322,2.233v7.544c0,1.487 0.128,1.871 0.322,2.233 0.18,0.336 0.438,0.594 0.774,0.774 0.362,0.194 0.746,0.322 2.233,0.322h7.544c1.487,0 1.871,-0.128 2.233,-0.322 0.336,-0.18 0.594,-0.438 0.774,-0.774 0.194,-0.362 0.322,-0.746 0.322,-2.233v-1.872c0,-0.497 0.403,-0.9 0.9,-0.9s0.9,0.403 0.9,0.9v1.872c0,1.57 -0.127,2.319 -0.534,3.082 -0.347,0.65 -0.863,1.165 -1.512,1.512 -0.762,0.408 -1.512,0.534 -3.082,0.534h-7.544c-1.57,0 -2.319,-0.127 -3.081,-0.534 -0.65,-0.347 -1.165,-0.863 -1.512,-1.512 -0.408,-0.762 -0.534,-1.512 -0.534,-3.082v-7.544c0,-1.57 0.127,-2.319 0.534,-3.081 0.347,-0.65 0.863,-1.165 1.512,-1.512z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m14,4c0,-0.497 0.403,-0.9 0.9,-0.9h5.1c0.497,0 0.9,0.403 0.9,0.9v5.1c0,0.497 -0.403,0.9 -0.9,0.9 -0.497,0 -0.9,-0.403 -0.9,-0.9v-2.927l-6.564,6.564c-0.351,0.352 -0.921,0.352 -1.273,0 -0.351,-0.352 -0.351,-0.921 0,-1.273l6.564,-6.564h-2.927c-0.497,0 -0.9,-0.403 -0.9,-0.9z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="28dp"
|
|
||||||
android:height="28dp"
|
|
||||||
android:viewportWidth="28"
|
|
||||||
android:viewportHeight="28">
|
|
||||||
<path
|
|
||||||
android:pathData="M11.005,4.145c-0.412,-1.693 -2.25,-2.621 -3.852,-1.896a2.82,2.82 0,0 0,-1.57 3.22l1.704,7.504a2.66,2.66 0,0 0,-3.779 0.57,2.715 2.715,0 0,0 0.042,3.22l4.622,6.094 0.001,0.002 0.036,0.049a6.136,6.136 0,0 0,0.576 0.644c0.398,0.388 0.996,0.877 1.817,1.317 1.658,0.888 4.158,1.536 7.628,0.836 2.844,-0.574 4.844,-2.15 5.902,-4.29 1.041,-2.107 1.121,-4.655 0.37,-7.147l-1.113,-4.743c-0.399,-1.697 -2.197,-2.671 -3.831,-2.03 -0.403,0.159 -0.75,0.398 -1.03,0.693a2.744,2.744 0,0 0,-2.289 -0.52,2.57 2.57,0 0,0 -1.242,0.653 2.498,2.498 0,0 0,-0.216 -0.139c-0.619,-0.355 -1.357,-0.431 -2.095,-0.246a3.037,3.037 0,0 0,-0.636 0.28l-1.045,-4.071ZM14.521,12.088 L14.522,12.09a1,1 0,0 0,1.937 -0.5l-0.001,-0.002 -0.254,-0.991c-0.142,-0.552 0.147,-0.906 0.467,-0.977a0.765,0.765 0,0 1,0.889 0.521l0.432,1.318v0.003a1,1 0,0 0,1.902 -0.619v-0.003l-0.12,-0.367a0.9,0.9 0,0 1,0.515 -1.116,0.864 0.864,0 0,1 1.152,0.625l1.121,4.774a1,1 0,0 0,0.017 0.063c0.646,2.12 0.536,4.14 -0.24,5.709 -0.762,1.543 -2.218,2.755 -4.505,3.216 -3.016,0.61 -5.042,0.03 -6.288,-0.639a6.08,6.08 0,0 1,-1.367 -0.986,4.141 4.141,0 0,1 -0.377,-0.421 0.952,0.952 0,0 0,-0.025 -0.035l-4.634,-6.11a0.715,0.715 0,0 1,-0.01 -0.843,0.66 0.66,0 0,1 1.043,-0.052l2.128,2.061a1,1 0,0 0,1.671 -0.94L7.531,5.02l-0.002,-0.01a0.82,0.82 0,0 1,0.449 -0.94,0.785 0.785,0 0,1 1.088,0.565l2.023,7.878a1,1 0,0 0,1.938 -0.491c-0.29,-1.16 -0.203,-1.683 -0.12,-1.889 0.05,-0.122 0.118,-0.193 0.312,-0.268 0.278,-0.06 0.46,-0.01 0.566,0.052 0.113,0.064 0.222,0.186 0.278,0.4l0.458,1.77ZM9.805,21.703ZM9.805,21.703"
|
|
||||||
android:fillColor="#000"
|
|
||||||
android:fillType="evenOdd"/>
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,10.5a1.5,1.5 0,1 1,-3 0,1.5 1.5,0 0,1 3,0ZM6.07,3.801C7.164,3.216 8.243,3 10.691,3h6.616c2.448,0 3.527,0.216 4.622,0.801A5.465,5.465 0,0 1,24.2 6.07c0.585,1.096 0.801,2.175 0.801,4.623v6.616c0,2.448 -0.216,3.527 -0.801,4.622a5.465,5.465 0,0 1,-2.27 2.269c-1.095,0.585 -2.174,0.801 -4.622,0.801h-6.616c-2.448,0 -3.527,-0.216 -4.623,-0.801a5.465,5.465 0,0 1,-2.268 -2.269C3.216,20.835 3,19.756 3,17.308v-6.616c0,-2.448 0.216,-3.527 0.801,-4.623A5.466,5.466 0,0 1,6.07 3.801ZM10.691,5c-2.335,0 -3.019,0.212 -3.68,0.565a3.466,3.466 0,0 0,-1.447 1.448C5.212,7.673 5,8.357 5,10.692v6.616c0,1.67 0.108,2.495 0.3,3.071L8,17.677c0.37,-0.37 0.69,-0.69 0.975,-0.932 0.301,-0.255 0.628,-0.482 1.03,-0.613a3,3 0,0 1,1.845 -0.007c0.403,0.129 0.731,0.353 1.034,0.607 0.27,0.226 0.57,0.52 0.916,0.861l2.93,-2.91c0.373,-0.37 0.695,-0.69 0.982,-0.932 0.302,-0.255 0.63,-0.481 1.034,-0.611a3,3 0,0 1,1.85 0.003c0.403,0.131 0.73,0.359 1.032,0.615 0.285,0.242 0.606,0.563 0.978,0.935l0.393,0.393v-4.394c0,-2.335 -0.212,-3.019 -0.565,-3.68a3.466,3.466 0,0 0,-1.448 -1.447C20.327,5.212 19.643,5 17.308,5h-6.616ZM22.994,17.909 L21.219,16.134c-0.407,-0.407 -0.67,-0.668 -0.886,-0.852 -0.207,-0.176 -0.304,-0.22 -0.357,-0.237a1,1 0,0 0,-0.617 -0.001c-0.053,0.017 -0.15,0.06 -0.358,0.236 -0.217,0.183 -0.48,0.444 -0.888,0.849l-3.605,3.58a1,1 0,0 1,-1.407 0.003l-0.613,-0.604a16.794,16.794 0,0 0,-0.888 -0.843c-0.208,-0.174 -0.306,-0.218 -0.358,-0.235a1,1 0,0 0,-0.616 0.002c-0.052,0.018 -0.15,0.062 -0.356,0.237 -0.215,0.183 -0.477,0.444 -0.883,0.85l-2.94,2.941c0.174,0.14 0.363,0.265 0.567,0.374 0.66,0.353 1.344,0.565 3.679,0.565h6.616c2.335,0 3.019,-0.212 3.68,-0.565a3.467,3.467 0,0 0,1.447 -1.448c0.321,-0.6 0.525,-1.219 0.56,-3.078Z"
|
||||||
|
android:fillColor="#000"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M19.872,7C21.655,7 22.302,7.186 22.954,7.534C23.606,7.883 24.117,8.394 24.466,9.046C24.814,9.698 25,10.345 25,12.128L25,18.872C25,20.655 24.814,21.302 24.466,21.954C24.117,22.606 23.606,23.117 22.954,23.466C22.302,23.814 21.655,24 19.872,24L8.128,24C6.345,24 5.698,23.814 5.046,23.466C4.394,23.117 3.883,22.606 3.534,21.954C3.204,21.336 3.02,20.723 3.002,19.144L3,12.128C3,10.345 3.186,9.698 3.534,9.046C3.883,8.394 4.394,7.883 5.046,7.534C5.664,7.204 6.277,7.02 7.856,7.002L19.872,7ZM19.999,14.414L15.061,19.354C14.511,19.903 13.642,19.937 13.053,19.457L12.939,19.354L11,17.414L6.517,21.897C6.817,21.963 7.223,21.994 7.89,21.999L19.872,22C21.196,22 21.599,21.922 22.01,21.702C22.314,21.54 22.54,21.314 22.702,21.01L22.778,20.855C22.929,20.51 22.991,20.095 22.999,19.11L22.999,17.414L19.999,14.414ZM20.11,9.001L7.89,9.001L7.474,9.009C6.653,9.034 6.324,9.119 5.99,9.298C5.686,9.46 5.46,9.686 5.298,9.99L5.222,10.145C5.071,10.49 5.009,10.905 5.001,11.89L5.001,19.11L5.009,19.526C5.022,19.957 5.052,20.252 5.103,20.484L9.939,15.646C10.489,15.097 11.358,15.063 11.947,15.543L12.061,15.646L14,17.586L18.939,12.646C19.489,12.097 20.358,12.063 20.947,12.543L21.061,12.646L23,14.585L23,12.128C23,10.804 22.922,10.401 22.702,9.99C22.54,9.686 22.314,9.46 22.01,9.298L21.855,9.222C21.51,9.071 21.095,9.009 20.11,9.001ZM8.5,11C9.328,11 10,11.672 10,12.5C10,13.328 9.328,14 8.5,14C7.672,14 7,13.328 7,12.5C7,11.672 7.672,11 8.5,11ZM17.436,3C18.328,3 18.651,3.093 18.977,3.267C19.303,3.441 19.559,3.697 19.733,4.023C19.864,4.269 19.949,4.513 19.983,4.999L8.017,4.999C8.051,4.513 8.136,4.269 8.267,4.023C8.441,3.697 8.697,3.441 9.023,3.267C9.349,3.093 9.672,3 10.564,3L17.436,3Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M10.119,10.118c1.258,-1.259 2.204,-3.562 2.847,-5.619l0.009,-0.03a33.18,33.18 0,0 0,0.208 -0.692c0.319,-1.09 0.484,-1.637 0.623,-1.716a0.38,0.38 0,0 1,0.403 0c0.14,0.078 0.306,0.625 0.629,1.714l0.033,0.114 0.005,0.018c0.055,0.184 0.112,0.37 0.172,0.56l0.009,0.029c0.65,2.057 1.602,4.363 2.86,5.622 1.26,1.258 3.553,2.202 5.599,2.844l0.027,0.009c0.19,0.06 0.379,0.116 0.564,0.17l0.122,0.036c1.09,0.32 1.638,0.486 1.717,0.625a0.38,0.38 0,0 1,0 0.403c-0.078,0.14 -0.625,0.307 -1.713,0.631l-0.123,0.037a34.148,34.148 0,0 0,-0.59 0.181c-2.047,0.651 -4.343,1.604 -5.602,2.863 -1.26,1.26 -2.212,3.555 -2.863,5.602l-0.009,0.028c-0.06,0.19 -0.118,0.378 -0.173,0.563l-0.036,0.122c-0.325,1.088 -0.492,1.635 -0.632,1.714a0.38,0.38 0,0 1,-0.402 -0.001c-0.14,-0.08 -0.305,-0.627 -0.625,-1.716l-0.002,-0.009 -0.034,-0.114a35.075,35.075 0,0 0,-0.17 -0.563l-0.009,-0.028c-0.642,-2.046 -1.586,-4.34 -2.844,-5.598 -1.26,-1.26 -3.565,-2.211 -5.621,-2.861l-0.03,-0.01a34.262,34.262 0,0 0,-0.56 -0.17l-0.131,-0.04c-1.089,-0.322 -1.636,-0.488 -1.715,-0.628a0.38,0.38 0,0 1,0 -0.403c0.08,-0.14 0.628,-0.304 1.717,-0.623l0.132,-0.039c0.184,-0.054 0.371,-0.11 0.56,-0.17l0.03,-0.008c2.056,-0.643 4.36,-1.588 5.618,-2.847Z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="m16.405,11.793c-0.455,0.994 -1.047,2.02 -1.819,2.792s-1.798,1.364 -2.793,1.819c0.994,0.455 2.021,1.047 2.793,1.819 0.771,0.771 1.364,1.796 1.819,2.788 0.455,-0.993 1.048,-2.017 1.819,-2.788 0.771,-0.771 1.796,-1.364 2.789,-1.819 -0.993,-0.455 -2.018,-1.048 -2.789,-1.819 -0.772,-0.772 -1.365,-1.798 -1.819,-2.792zM15.357,8.959c-0.526,1.608 -1.253,3.281 -2.185,4.213 -0.932,0.932 -2.605,1.658 -4.214,2.185 -0.164,0.054 -0.327,0.105 -0.489,0.155 -0.361,0.111 -0.715,0.211 -1.052,0.301 -0.097,0.026 -0.192,0.051 -0.286,0.075 -0.447,0.115 -0.447,0.92 0,1.035 0.094,0.024 0.189,0.049 0.286,0.075 0.337,0.09 0.69,0.19 1.052,0.301 0.162,0.049 0.325,0.101 0.489,0.155 1.609,0.526 3.282,1.253 4.214,2.185 0.932,0.931 1.658,2.599 2.185,4.202 0.053,0.163 0.105,0.325 0.154,0.485 0.111,0.361 0.211,0.714 0.301,1.05 0.026,0.097 0.051,0.193 0.076,0.287 0.116,0.446 0.919,0.446 1.035,0 0.024,-0.094 0.05,-0.19 0.076,-0.287 0.09,-0.336 0.191,-0.689 0.301,-1.05 0.049,-0.16 0.101,-0.322 0.154,-0.485 0.526,-1.603 1.253,-3.271 2.185,-4.202 0.932,-0.931 2.6,-1.658 4.203,-2.184 0.163,-0.053 0.325,-0.105 0.485,-0.154 0.361,-0.111 0.714,-0.211 1.05,-0.301 0.097,-0.026 0.193,-0.051 0.287,-0.076 0.446,-0.115 0.446,-0.919 0,-1.035 -0.094,-0.024 -0.19,-0.05 -0.287,-0.076 -0.336,-0.09 -0.689,-0.191 -1.05,-0.301 -0.161,-0.049 -0.322,-0.101 -0.485,-0.154 -1.603,-0.526 -3.272,-1.253 -4.203,-2.184 -0.932,-0.932 -1.659,-2.604 -2.185,-4.213 -0.054,-0.164 -0.105,-0.327 -0.155,-0.489 -0.111,-0.361 -0.211,-0.715 -0.301,-1.051 -0.026,-0.097 -0.051,-0.192 -0.075,-0.286 -0.115,-0.447 -0.92,-0.447 -1.035,0 -0.024,0.094 -0.049,0.189 -0.075,0.286 -0.09,0.336 -0.19,0.69 -0.301,1.051 -0.049,0.162 -0.101,0.325 -0.155,0.489z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m7.297,2.363c-0.164,-0.49 -0.857,-0.49 -1.021,0l-0.651,1.953c-0.205,0.615 -0.688,1.098 -1.303,1.303l-1.954,0.651c-0.491,0.163 -0.491,0.857 0,1.021l1.949,0.649c0.618,0.206 1.102,0.692 1.306,1.311l0.653,1.984c0.162,0.493 0.86,0.493 1.022,0l0.651,-1.979c0.204,-0.621 0.692,-1.109 1.313,-1.313l1.98,-0.651c0.493,-0.162 0.493,-0.86 0,-1.022l-1.985,-0.653c-0.619,-0.204 -1.105,-0.688 -1.311,-1.305z"
|
||||||
|
android:fillColor="#000"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="M14,2C20.627,2 26,7.373 26,14C26,20.627 20.627,26 14,26C7.373,26 2,20.627 2,14C2,7.373 7.373,2 14,2ZM14,20.5C11.914,20.5 9.92,21.082 8.203,22.149C9.838,23.315 11.839,24 14,24C16.161,24 18.161,23.315 19.796,22.15C18.079,21.082 16.086,20.5 14,20.5ZM14,4C8.477,4 4,8.477 4,14C4,16.616 5.004,18.997 6.648,20.779C8.786,19.308 11.331,18.5 14,18.5C16.669,18.5 19.215,19.308 21.353,20.777C22.996,18.996 24,16.615 24,14C24,8.477 19.523,4 14,4ZM14,7.5C16.624,7.5 18.75,9.626 18.75,12.25C18.75,14.874 16.624,17 14,17C11.376,17 9.25,14.874 9.25,12.25C9.25,9.626 11.376,7.5 14,7.5ZM14,9.5C12.48,9.5 11.25,10.73 11.25,12.25C11.25,13.77 12.48,15 14,15C15.52,15 16.75,13.77 16.75,12.25C16.75,10.73 15.52,9.5 14,9.5Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="28dp"
|
||||||
|
android:height="28dp"
|
||||||
|
android:viewportWidth="28"
|
||||||
|
android:viewportHeight="28">
|
||||||
|
<path
|
||||||
|
android:pathData="m5.231,9.995c-0.661,0.708 -1.231,1.941 -1.231,4.005s0.57,3.297 1.231,4.005c0.665,0.712 1.514,0.995 2.269,0.995s1.604,-0.282 2.269,-0.995c0.661,-0.708 1.231,-1.941 1.231,-4.005s-0.57,-3.297 -1.231,-4.005c-0.665,-0.712 -1.514,-0.995 -2.269,-0.995s-1.604,0.282 -2.269,0.995zM3.769,8.63c1.085,-1.163 2.486,-1.63 3.731,-1.63s2.646,0.468 3.731,1.63c1.089,1.167 1.769,2.934 1.769,5.37s-0.68,4.203 -1.769,5.37c-1.085,1.163 -2.486,1.63 -3.731,1.63s-2.646,-0.468 -3.731,-1.63c-1.089,-1.167 -1.769,-2.934 -1.769,-5.37s0.68,-4.203 1.769,-5.37zM17,8c0,-0.552 0.448,-1 1,-1h3.5c2.485,0 4.5,2.015 4.5,4.5 0,2.485 -2.015,4.5 -4.5,4.5h-2.5v1h2c0.552,0 1,0.448 1,1s-0.448,1 -1,1h-2v1c0,0.552 -0.448,1 -1,1s-1,-0.448 -1,-1v-1h-1c-0.552,0 -1,-0.448 -1,-1s0.448,-1 1,-1h1v-1h-1c-0.552,0 -1,-0.448 -1,-1s0.448,-1 1,-1h1zM19,14h2.5c1.381,0 2.5,-1.119 2.5,-2.5s-1.119,-2.5 -2.5,-2.5h-2.5z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -16,6 +16,18 @@
|
|||||||
<string name="MenuFileOpenFileBigObject">Файл содержит более 500к треугольников. Нарезка может быть медленной.</string>
|
<string name="MenuFileOpenFileBigObject">Файл содержит более 500к треугольников. Нарезка может быть медленной.</string>
|
||||||
<string name="MenuFileOpenFileLoading">Загрузка файла…</string>
|
<string name="MenuFileOpenFileLoading">Загрузка файла…</string>
|
||||||
<string name="MenuFileDelete">Убрать модель</string>
|
<string name="MenuFileDelete">Убрать модель</string>
|
||||||
|
<string name="MenuFileAIGenerator">Модель\nпо фото</string>
|
||||||
|
<string name="MenuFileAIGeneratorPleaseWaitSetup">Пожалуйста, подождите…</string>
|
||||||
|
<string name="MenuFileAIGeneratorErrorNotLoadedUserAccount">Ошибка: данные о пользователе пока не загружены.</string>
|
||||||
|
<string name="MenuFileAIGeneratorFromCamera">Сделать фото</string>
|
||||||
|
<string name="MenuFileAIGeneratorFromGallery">Выбрать из галереи</string>
|
||||||
|
<string name="MenuFileAIGeneratorRemaining">Осталось: %d / %d генераций</string>
|
||||||
|
<string name="MenuFileAIGeneratorUploading">Загрузка изображения…</string>
|
||||||
|
<string name="MenuFileAIGeneratorProcessing">Обработка изображения…</string>
|
||||||
|
<string name="MenuFileAIGeneratorDownloading">Скачивание модели…</string>
|
||||||
|
<string name="MenuFileAIGeneratorError">Не удалось сгенерировать модель</string>
|
||||||
|
<string name="MenuFileAIGeneratorSavedAs">Модель сохранена как %s.</string>
|
||||||
|
<string name="MenuFileAIGeneratorNoGenerationsLeft">Не осталось генераций.</string>
|
||||||
<string name="MenuFileCalibrations">Калибров.</string>
|
<string name="MenuFileCalibrations">Калибров.</string>
|
||||||
<string name="MenuFileCalibrationsLA">K3D Linear Advance</string>
|
<string name="MenuFileCalibrationsLA">K3D Linear Advance</string>
|
||||||
<string name="MenuFileCalibrationsLADescription">Калибровка Linear/Pressure Advance</string>
|
<string name="MenuFileCalibrationsLADescription">Калибровка Linear/Pressure Advance</string>
|
||||||
@@ -155,6 +167,31 @@
|
|||||||
<string name="SettingsProfileCopy">%s - Копия</string>
|
<string name="SettingsProfileCopy">%s - Копия</string>
|
||||||
<string name="SettingsCloneProfile">Клон. текущий</string>
|
<string name="SettingsCloneProfile">Клон. текущий</string>
|
||||||
<string name="SettingsDeleteProfile">Удалить текущий</string>
|
<string name="SettingsDeleteProfile">Удалить текущий</string>
|
||||||
|
<string name="SettingsCloudNotLoggedIn">Не авторизовано</string>
|
||||||
|
<string name="SettingsCloudLoading">Загрузка…</string>
|
||||||
|
<string name="SettingsCloudTapToManage">Нажмите для управления</string>
|
||||||
|
<string name="SettingsCloudTapToShowMore">Нажмите чтобы узнать больше</string>
|
||||||
|
<string name="SettingsCloudManageTitle">Аккаунт Beam 3D</string>
|
||||||
|
<string name="SettingsCloudManageDescription">Даёт следующие преимущества:</string>
|
||||||
|
<string name="SettingsCloudManageFeatureCloudSync">Облачная синхронизация профилей</string>
|
||||||
|
<string name="SettingsCloudManageFeatureCloudSyncDescription">Храните свои профили в облаке Beam</string>
|
||||||
|
<string name="SettingsCloudManageFeatureAIGenerator">ИИ генератор моделей</string>
|
||||||
|
<string name="SettingsCloudManageFeatureAIGeneratorDescription">%1$d моделей по фото в месяц</string>
|
||||||
|
<string name="SettingsCloudManageFeatureFreeForAll">Slice Beam может оставаться бесплатным для всех</string>
|
||||||
|
<string name="SettingsCloudManageFeatureFreeForAllDescription">Спасибо за вашу поддержку!</string>
|
||||||
|
<string name="SettingsCloudManageLevelRedirectMessage">При подписке на данный уровень вы соглашаетесь с условиями обслуживания.</string>
|
||||||
|
<string name="SettingsCloudManageLevelRedirectAlreadySubscribed">Уже подписаны?</string>
|
||||||
|
<string name="SettingsCloudManageFree">Бесплатно</string>
|
||||||
|
<string name="SettingsCloudManageSubscribed">Вы подписаны</string>
|
||||||
|
<string name="SettingsCloudManageWillBeLater">Будет позже</string>
|
||||||
|
<string name="SettingsCloudManageTermsOfService">Условия обслуживания</string>
|
||||||
|
<string name="SettingsCloudManageButtonManage">Настройки аккаунта</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogIn">Войти</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogInCancelTitle">Отмена</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogInCancel">Отменить авторизацию?</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogOut">Выйти</string>
|
||||||
|
<string name="SettingsCloudManageLoggedInAs">Вошли как «%1$s»</string>
|
||||||
|
<string name="SettingsCloudManageSubscription">Управление подпиской</string>
|
||||||
<string name="Changelog">Список изменений</string>
|
<string name="Changelog">Список изменений</string>
|
||||||
<string name="ChangelogBoostyDescription">Выход данного обновления поддержали:</string>
|
<string name="ChangelogBoostyDescription">Выход данного обновления поддержали:</string>
|
||||||
<string name="ChangelogNext">Далее</string>
|
<string name="ChangelogNext">Далее</string>
|
||||||
@@ -162,9 +199,19 @@
|
|||||||
<string name="OrcaConversionPleaseWait">Конвертация профилей, пожалуйста, подождите…</string>
|
<string name="OrcaConversionPleaseWait">Конвертация профилей, пожалуйста, подождите…</string>
|
||||||
<string name="OrcaConversionNotAConfigBundle">Это не пакет конфигураций</string>
|
<string name="OrcaConversionNotAConfigBundle">Это не пакет конфигураций</string>
|
||||||
<string name="AppCrashed">Что-то пошло не так</string>
|
<string name="AppCrashed">Что-то пошло не так</string>
|
||||||
<string name="AppCrashedDesc">Версия Android: %s\nУстройство: %s\nЛог: \n%s</string>
|
<string name="AppCrashedDesc">Версия Android: %1$s\nУстройство: %2$s\nЛог: \n%3$s</string>
|
||||||
<string name="AppCrashedShare">Поделиться</string>
|
<string name="AppCrashedShare">Поделиться</string>
|
||||||
<string name="AppCrashedRestart">Попытаться запустить приложение ещё раз</string>
|
<string name="AppCrashedRestart">Попытаться запустить приложение ещё раз</string>
|
||||||
<string name="BedConfigurationError">Ошибка конфигурации стола</string>
|
<string name="BedConfigurationError">Ошибка конфигурации стола</string>
|
||||||
<string name="BedConfigurationErrorDesc">Вам необходимо исправить конфигурацию стола перед использованием.</string>
|
<string name="BedConfigurationErrorDesc">Вам необходимо исправить конфигурацию стола перед использованием.</string>
|
||||||
|
<string name="CloudSyncInProgress">Синхронизация с облаком…</string>
|
||||||
|
<string name="CloudSyncSuccess">Успешно синхронизировали профили.</string>
|
||||||
|
<string name="CloudSyncError">Не удалось синхронизировать профили, повторим попытку позже.</string>
|
||||||
|
<string name="CloudSyncConflict">Конфликт облачных профилей.</string>
|
||||||
|
<string name="CloudSyncConflictResolve">Разрешить</string>
|
||||||
|
<string name="CloudSyncConflictResolveMessage">Конфликт облачных профилей, пожалуйста, выберите какие профили вы хотите оставить.\n\nПоследнее изменение в облаке: %1$s\nПоследнее изменение на устройстве: %2$s</string>
|
||||||
|
<string name="CloudSyncConflictChooseRemote">Оставить облачные профили</string>
|
||||||
|
<string name="CloudSyncConflictChooseLocal">Оставить локальные профили</string>
|
||||||
|
<string name="Yes">Да</string>
|
||||||
|
<string name="No">Нет</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -17,6 +17,18 @@
|
|||||||
<string name="MenuFileOpenFileBigObject">File has more than 500k triangles. Processing could be slow.</string>
|
<string name="MenuFileOpenFileBigObject">File has more than 500k triangles. Processing could be slow.</string>
|
||||||
<string name="MenuFileOpenFileLoading">Loading file…</string>
|
<string name="MenuFileOpenFileLoading">Loading file…</string>
|
||||||
<string name="MenuFileDelete">Remove model</string>
|
<string name="MenuFileDelete">Remove model</string>
|
||||||
|
<string name="MenuFileAIGenerator">Model\nfrom photo</string>
|
||||||
|
<string name="MenuFileAIGeneratorPleaseWaitSetup">Please wait…</string>
|
||||||
|
<string name="MenuFileAIGeneratorErrorNotLoadedUserAccount">Error: user info not fetched yet.</string>
|
||||||
|
<string name="MenuFileAIGeneratorFromCamera">Take a photo</string>
|
||||||
|
<string name="MenuFileAIGeneratorFromGallery">Choose from gallery</string>
|
||||||
|
<string name="MenuFileAIGeneratorRemaining">Remaining: %d / %d generations</string>
|
||||||
|
<string name="MenuFileAIGeneratorUploading">Uploading image…</string>
|
||||||
|
<string name="MenuFileAIGeneratorProcessing">Processing image…</string>
|
||||||
|
<string name="MenuFileAIGeneratorDownloading">Downloading model…</string>
|
||||||
|
<string name="MenuFileAIGeneratorError">Failed to generate model</string>
|
||||||
|
<string name="MenuFileAIGeneratorSavedAs">Saved model as %s.</string>
|
||||||
|
<string name="MenuFileAIGeneratorNoGenerationsLeft">No generations left.</string>
|
||||||
<string name="MenuFileCalibrations">Calibrat.</string>
|
<string name="MenuFileCalibrations">Calibrat.</string>
|
||||||
<string name="MenuFileCalibrationsLA">K3D Linear Advance</string>
|
<string name="MenuFileCalibrationsLA">K3D Linear Advance</string>
|
||||||
<string name="MenuFileCalibrationsLADescription">Linear/Pressure Advance Calibration</string>
|
<string name="MenuFileCalibrationsLADescription">Linear/Pressure Advance Calibration</string>
|
||||||
@@ -157,6 +169,31 @@
|
|||||||
<string name="SettingsProfileCopy">%s - Copy</string>
|
<string name="SettingsProfileCopy">%s - Copy</string>
|
||||||
<string name="SettingsCloneProfile">Clone current</string>
|
<string name="SettingsCloneProfile">Clone current</string>
|
||||||
<string name="SettingsDeleteProfile">Delete current</string>
|
<string name="SettingsDeleteProfile">Delete current</string>
|
||||||
|
<string name="SettingsCloudNotLoggedIn">Not logged in</string>
|
||||||
|
<string name="SettingsCloudLoading">Loading…</string>
|
||||||
|
<string name="SettingsCloudTapToManage">Tap to manage</string>
|
||||||
|
<string name="SettingsCloudTapToShowMore">Tap to learn more</string>
|
||||||
|
<string name="SettingsCloudManageTitle">Beam 3D Account</string>
|
||||||
|
<string name="SettingsCloudManageDescription">Provides the following benefits:</string>
|
||||||
|
<string name="SettingsCloudManageFeatureCloudSync">Cloud profiles sync</string>
|
||||||
|
<string name="SettingsCloudManageFeatureCloudSyncDescription">Store your profiles in Beam Cloud</string>
|
||||||
|
<string name="SettingsCloudManageFeatureAIGenerator">AI model generator</string>
|
||||||
|
<string name="SettingsCloudManageFeatureAIGeneratorDescription">%1$d models from photo per month</string>
|
||||||
|
<string name="SettingsCloudManageFeatureFreeForAll">Slice Beam can remain free for all</string>
|
||||||
|
<string name="SettingsCloudManageFeatureFreeForAllDescription">Thanks for your support!</string>
|
||||||
|
<string name="SettingsCloudManageLevelRedirectMessage">By subscribing to this level you accept terms of service.</string>
|
||||||
|
<string name="SettingsCloudManageLevelRedirectAlreadySubscribed">Already subscribed?</string>
|
||||||
|
<string name="SettingsCloudManageFree">Free</string>
|
||||||
|
<string name="SettingsCloudManageSubscribed">You are subscribed</string>
|
||||||
|
<string name="SettingsCloudManageWillBeLater">Will be later</string>
|
||||||
|
<string name="SettingsCloudManageTermsOfService">Terms of service</string>
|
||||||
|
<string name="SettingsCloudManageButtonManage">Account settings</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogIn">Login</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogInCancelTitle">Cancel</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogInCancel">Cancel login?</string>
|
||||||
|
<string name="SettingsCloudManageButtonLogOut">Log out</string>
|
||||||
|
<string name="SettingsCloudManageLoggedInAs">Logged in as «%1$s»</string>
|
||||||
|
<string name="SettingsCloudManageSubscription">Manage subscription</string>
|
||||||
<string name="Changelog">Changelog</string>
|
<string name="Changelog">Changelog</string>
|
||||||
<string name="ChangelogBoosty" translatable="false">Boosty</string>
|
<string name="ChangelogBoosty" translatable="false">Boosty</string>
|
||||||
<string name="ChangelogBoostyDescription">The release of this update was supported by:</string>
|
<string name="ChangelogBoostyDescription">The release of this update was supported by:</string>
|
||||||
@@ -165,9 +202,19 @@
|
|||||||
<string name="OrcaConversionPleaseWait">Converting profiles, please wait…</string>
|
<string name="OrcaConversionPleaseWait">Converting profiles, please wait…</string>
|
||||||
<string name="OrcaConversionNotAConfigBundle">Not a config bundle</string>
|
<string name="OrcaConversionNotAConfigBundle">Not a config bundle</string>
|
||||||
<string name="AppCrashed">Something went wrong</string>
|
<string name="AppCrashed">Something went wrong</string>
|
||||||
<string name="AppCrashedDesc">Android version: %s\nDevice: %s\nLogs:\n%s</string>
|
<string name="AppCrashedDesc">Android version: %1$s\nDevice: %2$s\nLogs:\n%3$s</string>
|
||||||
<string name="AppCrashedShare">Share</string>
|
<string name="AppCrashedShare">Share</string>
|
||||||
<string name="AppCrashedRestart">Try to start app again</string>
|
<string name="AppCrashedRestart">Try to start app again</string>
|
||||||
<string name="BedConfigurationError">Bed config error</string>
|
<string name="BedConfigurationError">Bed config error</string>
|
||||||
<string name="BedConfigurationErrorDesc">You should fix your bed configuration before usage.</string>
|
<string name="BedConfigurationErrorDesc">You should fix your bed configuration before usage.</string>
|
||||||
|
<string name="CloudSyncInProgress">Cloud synchronization in progress…</string>
|
||||||
|
<string name="CloudSyncSuccess">Successfully synchronized profiles.</string>
|
||||||
|
<string name="CloudSyncError">Failed to synchronize profiles, will retry later.</string>
|
||||||
|
<string name="CloudSyncConflict">Cloud profiles conflict.</string>
|
||||||
|
<string name="CloudSyncConflictResolve">Resolve</string>
|
||||||
|
<string name="CloudSyncConflictResolveMessage">Cloud profiles conflict, please select which profiles you want to keep.\n\nLast changed cloud: %1$s\nLast changed on device: %2$s</string>
|
||||||
|
<string name="CloudSyncConflictChooseRemote">Keep cloud profiles</string>
|
||||||
|
<string name="CloudSyncConflictChooseLocal">Keep local profiles</string>
|
||||||
|
<string name="Yes">Yes</string>
|
||||||
|
<string name="No">No</string>
|
||||||
</resources>
|
</resources>
|
||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
rootProject.name = "Slice Beam"
|
rootProject.name = "Slice Beam"
|
||||||
include ':app', ':eventbus', ':eventbus_api', ':eventbus_processor'
|
include ':app', ':eventbus', ':eventbus_api', ':eventbus_processor', ':sapil'
|
||||||
|
|
||||||
project(':eventbus').projectDir = file('EventBus/eventbus')
|
project(':eventbus').projectDir = file('EventBus/eventbus')
|
||||||
project(':eventbus_api').projectDir = file('EventBus/eventbus_api')
|
project(':eventbus_api').projectDir = file('EventBus/eventbus_api')
|
||||||
project(':eventbus_processor').projectDir = file('EventBus/eventbus_processor')
|
project(':eventbus_processor').projectDir = file('EventBus/eventbus_processor')
|
||||||
|
project(':sapil').projectDir = file('SAPIL/sapil')
|
||||||
|
|||||||
Reference in New Issue
Block a user