mirror of
https://github.com/Dark98/SliceBeam.git
synced 2026-07-03 00:38:53 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12b370ce30 | |||
| dd7a6ddf1d | |||
| 9b32fe68d7 | |||
| 33487afdc9 | |||
| bc1007aa59 | |||
| 4c4469b1fd | |||
| e4320be0ce | |||
| 0933adf1b9 | |||
| 70f4d08a8c | |||
| a27a8c1d5d | |||
| abf53f1c43 | |||
| 5f13961d05 | |||
| 264e742d3a | |||
| 462f0a4c55 | |||
| 6bb8926fb7 | |||
| e1fe683154 | |||
| 8529d94899 | |||
| df09f8ef39 | |||
| 8b200e689c | |||
| 838cedbf1a | |||
| e52a1ad949 | |||
| 2c1d45153b | |||
| 6ede5931e8 | |||
| 1976ac4899 | |||
| 58b24d5137 | |||
| 662018834e | |||
| 6092f123b5 |
@@ -0,0 +1,93 @@
|
|||||||
|
<svg width="270" height="80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#a)">
|
||||||
|
<mask id="b" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#b)">
|
||||||
|
<path d="M260 80H10A10 10 0 0 1 0 70V10A10 10 0 0 1 10 0h250a10 10 0 0 1 10 10v60a10 10 0 0 1-10 10Z" fill="#000"/>
|
||||||
|
</g>
|
||||||
|
<mask id="c" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#c)">
|
||||||
|
<path d="M94.8 20.5c0 1.7-.5 3-1.5 4a5.8 5.8 0 0 1-4.4 1.8 6 6 0 0 1-4.4-1.8 6 6 0 0 1-1.8-4.5 6 6 0 0 1 1.8-4.5 6 6 0 0 1 4.4-1.8 7 7 0 0 1 2.5.5 5 5 0 0 1 1.9 1.4l-1 1a4 4 0 0 0-3.4-1.4 4.6 4.6 0 0 0-4.7 4.8c0 1.4.5 2.6 1.5 3.5.9.9 2 1.3 3.2 1.3a5 5 0 0 0 3.4-1.3c.6-.6.9-1.4 1-2.5h-4.4v-1.4h5.9v.9Z" fill="#fff" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="d" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#d)">
|
||||||
|
<path d="M104 15.5h-5.4v3.8h5v1.4h-5v3.8h5.5V26h-7V14h7v1.5Z" fill="#fff" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="e" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#e)">
|
||||||
|
<path d="M110.6 26H109V15.5h-3.3V14h8.2v1.5h-3.3V26Z" fill="#fff" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="f" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#f)">
|
||||||
|
<path d="M119.9 14h1.5v12H120V14Z" fill="#fff" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="g" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#g)">
|
||||||
|
<path d="M128.3 26h-1.6V15.5h-3.3V14h8.2v1.5h-3.3V26Z" fill="#fff" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="h" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#h)">
|
||||||
|
<path d="M139.6 23.4a4 4 0 0 0 3.2 1.4 4.7 4.7 0 0 0 4.6-4.8c0-1.4-.4-2.5-1.3-3.4-1-1-2-1.4-3.3-1.4-1.3 0-2.4.5-3.2 1.4-1 .9-1.4 2-1.4 3.4s.5 2.5 1.4 3.4Zm7.6 1a5.9 5.9 0 0 1-4.4 1.9c-1.7 0-3.2-.6-4.4-1.9a6.1 6.1 0 0 1-1.7-4.4 6.1 6.1 0 0 1 6.1-6.3c1.8 0 3.2.6 4.4 1.9A6.1 6.1 0 0 1 149 20c0 1.8-.6 3.2-1.8 4.4Z" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
<mask id="i" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#i)">
|
||||||
|
<path d="M139.6 23.4a4 4 0 0 0 3.2 1.4 4.7 4.7 0 0 0 4.6-4.8c0-1.4-.4-2.5-1.3-3.4-1-1-2-1.4-3.3-1.4-1.3 0-2.4.5-3.2 1.4-1 .9-1.4 2-1.4 3.4s.5 2.5 1.4 3.4Zm7.6 1a5.9 5.9 0 0 1-4.4 1.9c-1.7 0-3.2-.6-4.4-1.9a6.1 6.1 0 0 1-1.7-4.4 6.1 6.1 0 0 1 6.1-6.3c1.8 0 3.2.6 4.4 1.9A6.1 6.1 0 0 1 149 20c0 1.8-.6 3.2-1.8 4.4Z" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="j" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#j)">
|
||||||
|
<path d="M151.2 26V14h1.8l5.9 9.3V14h1.5v12h-1.6l-6.1-9.8V26h-1.5Z" fill="#fff" stroke="#fff" stroke-width=".3" stroke-miterlimit="10"/>
|
||||||
|
</g>
|
||||||
|
<mask id="k" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#k)">
|
||||||
|
<path d="M213.9 60h3.7V35h-3.7v25Zm33.6-16-4.3 10.8h-.1L238.6 44h-4l6.7 15.2-3.8 8.4h3.9L251.6 44h-4.1Zm-21.2 13.2c-1.2 0-3-.6-3-2.2 0-1.9 2.2-2.6 4-2.6 1.7 0 2.5.3 3.5.8-.3 2.3-2.3 4-4.5 4Zm.5-13.7c-2.7 0-5.5 1.1-6.7 3.8l3.3 1.4c.7-1.4 2-1.9 3.4-1.9 2 0 4 1.2 4 3.3v.2c-.7-.4-2.2-1-4-1-3.5 0-7.1 2-7.1 5.7 0 3.3 2.9 5.5 6.2 5.5 2.5 0 3.9-1.2 4.7-2.5h.2v2h3.6v-9.6c0-4.5-3.3-7-7.6-7Zm-23 3.5h-5.4v-8.5h5.3c2.8 0 4.4 2.3 4.4 4.3 0 1.9-1.6 4.2-4.4 4.2Zm-.2-12h-9v25h3.8v-9.5h5.2c4.1 0 8.2-3 8.2-7.7 0-4.8-4-7.8-8.2-7.8Zm-48.8 22.2c-2.5 0-4.7-2.2-4.7-5.2s2.2-5.1 4.8-5.1c2.5 0 4.5 2.1 4.5 5.1 0 3-2 5.2-4.6 5.2Zm4.3-11.8c-1-1-2.5-1.9-4.6-1.9a8.5 8.5 0 0 0-8.1 8.5c0 4.8 3.9 8.5 8.1 8.5 2 0 3.7-.9 4.5-2h.1v1.3c0 3.3-1.7 5-4.5 5a4.7 4.7 0 0 1-4.3-3l-3.2 1.3a8 8 0 0 0 7.5 5c4.4 0 8-2.5 8-8.8V44h-3.5v1.4Zm6.2 14.6h3.7V35h-3.7v25Zm9.2-8.2c0-3.3 2.6-5 4.4-5 1.5 0 2.8.7 3.2 1.8l-7.6 3.2Zm11.6-2.9c-.7-1.9-2.9-5.4-7.3-5.4s-8 3.5-8 8.5c0 4.8 3.6 8.5 8.4 8.5 4 0 6.2-2.4 7.1-3.8l-2.9-1.9a4.9 4.9 0 0 1-4.2 2.4c-1.9 0-3.2-1-4-2.6l11.3-4.7-.4-1Zm-90.6-2.8v3.6h8.6c-.2 2-1 3.5-2 4.6a8.8 8.8 0 0 1-6.6 2.6 9.5 9.5 0 0 1-9.5-9.6 9.5 9.5 0 0 1 16-7l2.5-2.6A13.4 13.4 0 0 0 82 47.3c0 7.3 6.1 13.2 13.4 13.2 4 0 6.9-1.3 9.2-3.7a12 12 0 0 0 3.1-8.4c0-.9 0-1.6-.2-2.3h-12Zm22.1 11c-2.5 0-4.8-2-4.8-5 0-3.2 2.3-5.2 4.8-5.2 2.6 0 4.8 2 4.8 5.1 0 3-2.2 5.2-4.8 5.2Zm0-13.6a8.4 8.4 0 0 0-8.5 8.5c0 5 3.8 8.5 8.5 8.5 4.8 0 8.6-3.6 8.6-8.5 0-5-3.9-8.5-8.6-8.5Zm18.7 13.7c-2.6 0-4.8-2.2-4.8-5.2s2.2-5.1 4.8-5.1c2.5 0 4.8 2 4.8 5.1 0 3-2.3 5.2-4.8 5.2Zm0-13.7a8.4 8.4 0 0 0-8.6 8.5c0 5 3.9 8.5 8.6 8.5 4.7 0 8.5-3.6 8.5-8.5 0-5-3.8-8.5-8.5-8.5Z" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
<mask id="l" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#l)">
|
||||||
|
<path d="M41.4 38.8 20.1 61.4a5.7 5.7 0 0 0 8.5 3.5l24-13.8-11.2-12.3Z" fill="#EA4335"/>
|
||||||
|
</g>
|
||||||
|
<mask id="m" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#m)">
|
||||||
|
<path d="m63 35-10.4-6L41 39.4 52.7 51l10.2-6a5.8 5.8 0 0 0 .1-10Z" fill="#FBBC04"/>
|
||||||
|
</g>
|
||||||
|
<mask id="n" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#n)">
|
||||||
|
<path d="M20.1 18.6 20 20v40l.2 1.4 22-22-22-20.8Z" fill="#4285F4"/>
|
||||||
|
</g>
|
||||||
|
<mask id="o" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-21" y="-20" width="311" height="121">
|
||||||
|
<path d="M-20-20h310v120H-20V-20Z" fill="#fff"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#o)">
|
||||||
|
<path d="m41.6 40 11-11-24-13.9a5.8 5.8 0 0 0-8.5 3.4L41.6 40Z" fill="#34A853"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a">
|
||||||
|
<path fill="#fff" transform="matrix(1 0 0 -1 0 80)" d="M0 0h270v80H0z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.9 KiB |
@@ -30,6 +30,9 @@ app/src/main/jni/occt_wrapper/occtwrapper_export.h
|
|||||||
app/release/
|
app/release/
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings
|
||||||
|
|
||||||
app/src/main/jniImports/boost/
|
app/src/main/jniImports/boost/
|
||||||
app/src/main/jniImports/oneTBB/
|
app/src/main/jniImports/oneTBB/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ It is based on PrusaSlicer's core and well optimized for Android touchscreen int
|
|||||||
- K3D Chat for discussion & support (Russian language only): https://t.me/K_3_D
|
- K3D Chat for discussion & support (Russian language only): https://t.me/K_3_D
|
||||||
|
|
||||||
# Quick Start
|
# Quick Start
|
||||||
Just download APK from [Releases tab](https://github.com/utkabobr/SliceBeam/releases/latest) and follow setup instructions. Google Play builds will be available later.
|
[<img src="/.github/img/getongp.svg">](https://play.google.com/store/apps/details?id=ru.ytkab0bp.slicebeam)
|
||||||
|
|
||||||
|
Or download APK from [Releases tab](https://github.com/utkabobr/SliceBeam/releases/latest) and follow setup instructions.
|
||||||
|
|
||||||
# Where to get printer profiles?
|
# Where to get printer profiles?
|
||||||
|
|
||||||
|
|||||||
Submodule
+1
Submodule SAPIL added at d0c6422d79
+8
-5
@@ -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,11 +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 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,6 +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="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"
|
||||||
@@ -16,6 +25,8 @@
|
|||||||
android:theme="@style/Theme.SliceBeam"
|
android:theme="@style/Theme.SliceBeam"
|
||||||
android:name=".SliceBeam"
|
android:name=".SliceBeam"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
|
android:largeHeap="true"
|
||||||
|
android:isGame="false"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import cz.msebera.android.httpclient.Header;
|
import cz.msebera.android.httpclient.Header;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
import ru.ytkab0bp.slicebeam.events.BeamServerDataUpdatedEvent;
|
||||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
|
||||||
@@ -45,6 +46,10 @@ public class BeamServerData {
|
|||||||
return !BuildConfig.IS_GOOGLE_PLAY || Prefs.isRussianIP();
|
return !BuildConfig.IS_GOOGLE_PLAY || Prefs.isRussianIP();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isCloudAvailable() {
|
||||||
|
return isBoostyAvailable() && CloudController.hasAccountFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
public static void load() {
|
public static void load() {
|
||||||
client.get(DATA_URL, new AsyncHttpResponseHandler() {
|
client.get(DATA_URL, new AsyncHttpResponseHandler() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -2,20 +2,24 @@ package ru.ytkab0bp.slicebeam;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.database.Cursor;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.os.Process;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -28,6 +32,7 @@ import org.json.JSONArray;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -40,27 +45,39 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
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.NeedSnackbarEvent;
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
import ru.ytkab0bp.slicebeam.events.ObjectsListChangedEvent;
|
||||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||||
|
import ru.ytkab0bp.slicebeam.navigation.Fragment;
|
||||||
import ru.ytkab0bp.slicebeam.navigation.MobileNavigationDelegate;
|
import ru.ytkab0bp.slicebeam.navigation.MobileNavigationDelegate;
|
||||||
import ru.ytkab0bp.slicebeam.navigation.NavigationDelegate;
|
import ru.ytkab0bp.slicebeam.navigation.NavigationDelegate;
|
||||||
|
import ru.ytkab0bp.slicebeam.slic3r.Model;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rRuntimeError;
|
||||||
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
import ru.ytkab0bp.slicebeam.utils.IOUtils;
|
import ru.ytkab0bp.slicebeam.utils.IOUtils;
|
||||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
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_AI_GENERATOR_TAKE_PHOTO = 6, REQUEST_CODE_AI_GENERATOR_CHOOSE_PHOTO = 7;
|
||||||
|
|
||||||
private static MainActivity activeInstance;
|
private static MainActivity activeInstance;
|
||||||
|
|
||||||
@@ -68,6 +85,10 @@ 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 boolean IS_GENERATING_AI_MODEL;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -192,12 +213,39 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
setIntent(null);
|
setIntent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @noinspection ResultOfMethodCallIgnored*/
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (requestCode == MainActivity.REQUEST_CODE_EXPORT_GCODE) {
|
if (requestCode == MainActivity.REQUEST_CODE_EXPORT_3MF) {
|
||||||
|
Fragment fragment = getNavigationDelegate().getCurrentFragment();
|
||||||
|
if (fragment instanceof BedFragment) {
|
||||||
|
try {
|
||||||
|
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
||||||
|
Model model = ((BedFragment) fragment).getGlView().getRenderer().getModel();
|
||||||
|
File tempFile = File.createTempFile("temp_project", ".3mf");
|
||||||
|
SliceBeam.genCurrentConfig();
|
||||||
|
File cfg = SliceBeam.getCurrentConfigFile();
|
||||||
|
model.export3mf(cfg.getAbsolutePath(), tempFile.getAbsolutePath());
|
||||||
|
|
||||||
|
InputStream in = new FileInputStream(tempFile);
|
||||||
|
byte[] buffer = new byte[10240];
|
||||||
|
int c;
|
||||||
|
while ((c = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, c);
|
||||||
|
}
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
tempFile.delete();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileExport3mfSuccess));
|
||||||
|
} catch (IOException | Slic3rRuntimeError e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (requestCode == MainActivity.REQUEST_CODE_EXPORT_GCODE) {
|
||||||
try {
|
try {
|
||||||
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
||||||
InputStream in = new FileInputStream(BedFragment.getTempGCodePath());
|
InputStream in = new FileInputStream(BedFragment.getTempGCodePath());
|
||||||
@@ -238,25 +286,14 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
OutputStream out = getContentResolver().openOutputStream(data.getData());
|
||||||
out.write(w.serialize().getBytes(StandardCharsets.UTF_8));
|
out.write(w.serialize().getBytes(StandardCharsets.UTF_8));
|
||||||
out.close();
|
out.close();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileExportProfilesSuccess));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
} else if (requestCode == MainActivity.REQUEST_CODE_IMPORT_PROFILES) {
|
} else if (requestCode == MainActivity.REQUEST_CODE_IMPORT_PROFILES) {
|
||||||
Uri uri = data.getData();
|
Uri uri = data.getData();
|
||||||
ContentResolver resolver = getContentResolver();
|
String fileName = IOUtils.getDisplayName(uri);
|
||||||
|
|
||||||
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
|
||||||
Cursor metaCursor = resolver.query(uri, projection, null, null, null);
|
|
||||||
String fileName = null;
|
|
||||||
if (metaCursor != null) {
|
|
||||||
try {
|
|
||||||
if (metaCursor.moveToFirst()) {
|
|
||||||
fileName = metaCursor.getString(0);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
metaCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
new BeamAlertDialogBuilder(this)
|
new BeamAlertDialogBuilder(this)
|
||||||
@@ -268,136 +305,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileName.endsWith(".orca_printer")) {
|
if (fileName.endsWith(".orca_printer")) {
|
||||||
Toast.makeText(MainActivity.this, R.string.OrcaConversionPleaseWait, Toast.LENGTH_SHORT).show();
|
loadConvertedProfile(uri);
|
||||||
|
|
||||||
File f = new File(SliceBeam.getModelCacheDir(), "orca_conv.zip");
|
|
||||||
new Thread(()->{
|
|
||||||
try {
|
|
||||||
InputStream in = resolver.openInputStream(uri);
|
|
||||||
FileOutputStream fos = new FileOutputStream(f);
|
|
||||||
byte[] buffer = new byte[10240]; int c;
|
|
||||||
while ((c = in.read(buffer)) != -1) {
|
|
||||||
fos.write(buffer, 0, c);
|
|
||||||
}
|
|
||||||
fos.close();
|
|
||||||
in.close();
|
|
||||||
|
|
||||||
ZipFile zf = new ZipFile(f);
|
|
||||||
JSONObject bundle = new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry("bundle_structure.json"))));
|
|
||||||
if (!bundle.get("bundle_type").equals("printer config bundle")) {
|
|
||||||
zf.close();
|
|
||||||
|
|
||||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
|
||||||
.setMessage(R.string.OrcaConversionNotAConfigBundle)
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Slic3rConfigWrapper w = new Slic3rConfigWrapper();
|
|
||||||
if (bundle.has("process_config")) {
|
|
||||||
JSONArray arr = bundle.getJSONArray("process_config");
|
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
List<String> stripped = new ArrayList<>();
|
|
||||||
for (int i = 0; i < arr.length(); i++) {
|
|
||||||
String v = arr.getString(i);
|
|
||||||
names.add(v);
|
|
||||||
stripped.add(v.substring(v.indexOf('/') + 1, v.length() - 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String name : names) {
|
|
||||||
w.printConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "process", Slic3rConfigWrapper.PRINT_CONFIG_KEYS, stripped));
|
|
||||||
}
|
|
||||||
for (ConfigObject obj : w.printConfigs) {
|
|
||||||
String inherit = obj.get("inherits");
|
|
||||||
while (inherit != null) {
|
|
||||||
ConfigObject _obj = w.findPrint(inherit);
|
|
||||||
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
|
||||||
|
|
||||||
obj.values.remove("inherits");
|
|
||||||
HashMap<String, String> newMap = new HashMap<>();
|
|
||||||
newMap.putAll(_obj.values);
|
|
||||||
newMap.putAll(obj.values);
|
|
||||||
obj.values = newMap;
|
|
||||||
|
|
||||||
inherit = obj.values.get("inherits");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bundle.has("filament_config")) {
|
|
||||||
JSONArray arr = bundle.getJSONArray("filament_config");
|
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
List<String> stripped = new ArrayList<>();
|
|
||||||
for (int i = 0; i < arr.length(); i++) {
|
|
||||||
String v = arr.getString(i);
|
|
||||||
names.add(v);
|
|
||||||
stripped.add(v.substring(v.indexOf('/') + 1, v.length() - 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String name : names) {
|
|
||||||
w.filamentConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "filament", Slic3rConfigWrapper.FILAMENT_CONFIG_KEYS, stripped));
|
|
||||||
}
|
|
||||||
for (ConfigObject obj : w.filamentConfigs) {
|
|
||||||
String inherit = obj.get("inherits");
|
|
||||||
while (inherit != null) {
|
|
||||||
ConfigObject _obj = w.findFilament(inherit);
|
|
||||||
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
|
||||||
|
|
||||||
obj.values.remove("inherits");
|
|
||||||
HashMap<String, String> newMap = new HashMap<>();
|
|
||||||
newMap.putAll(_obj.values);
|
|
||||||
newMap.putAll(obj.values);
|
|
||||||
obj.values = newMap;
|
|
||||||
|
|
||||||
inherit = obj.values.get("inherits");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bundle.has("printer_config")) {
|
|
||||||
JSONArray arr = bundle.getJSONArray("printer_config");
|
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
List<String> stripped = new ArrayList<>();
|
|
||||||
for (int i = 0; i < arr.length(); i++) {
|
|
||||||
String v = arr.getString(i);
|
|
||||||
names.add(v);
|
|
||||||
stripped.add(v.substring(v.indexOf('/') + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String name : names) {
|
|
||||||
w.printerConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "machine", Slic3rConfigWrapper.PRINTER_CONFIG_KEYS, stripped));
|
|
||||||
}
|
|
||||||
for (ConfigObject obj : w.printerConfigs) {
|
|
||||||
String inherit = obj.get("inherits");
|
|
||||||
while (inherit != null) {
|
|
||||||
ConfigObject _obj = w.findPrinter(inherit);
|
|
||||||
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
|
||||||
|
|
||||||
obj.values.remove("inherits");
|
|
||||||
HashMap<String, String> newMap = new HashMap<>();
|
|
||||||
newMap.putAll(_obj.values);
|
|
||||||
newMap.putAll(obj.values);
|
|
||||||
obj.values = newMap;
|
|
||||||
|
|
||||||
inherit = obj.values.get("inherits");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zf.close();
|
|
||||||
|
|
||||||
loadIniForImport(new ByteArrayInputStream(w.serialize().getBytes(StandardCharsets.UTF_8)));
|
|
||||||
} catch (Exception e) {
|
|
||||||
ViewUtils.postOnMainThread(() -> {
|
|
||||||
new BeamAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
|
||||||
.setMessage(e.toString())
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,7 +319,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loadIniForImport(resolver.openInputStream(uri));
|
loadIniForImport(getContentResolver().openInputStream(uri));
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
new BeamAlertDialogBuilder(this)
|
new BeamAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.MenuFileImportProfilesFailed)
|
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||||
@@ -419,12 +327,275 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadConvertedProfile(Uri uri) {
|
||||||
|
String tag = UUID.randomUUID().toString();
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.OrcaConversionPleaseWait).tag(tag));
|
||||||
|
File f = new File(SliceBeam.getModelCacheDir(), "orca_conv.zip");
|
||||||
|
IOUtils.IO_POOL.submit(()->{
|
||||||
|
try {
|
||||||
|
InputStream in = getContentResolver().openInputStream(uri);
|
||||||
|
FileOutputStream fos = new FileOutputStream(f);
|
||||||
|
byte[] buffer = new byte[10240];
|
||||||
|
int c;
|
||||||
|
while ((c = in.read(buffer)) != -1) {
|
||||||
|
fos.write(buffer, 0, c);
|
||||||
|
}
|
||||||
|
fos.close();
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
ZipFile zf = new ZipFile(f);
|
||||||
|
JSONObject bundle = new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry("bundle_structure.json"))));
|
||||||
|
if (!bundle.get("bundle_type").equals("printer config bundle")) {
|
||||||
|
zf.close();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||||
|
.setMessage(R.string.OrcaConversionNotAConfigBundle)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Slic3rConfigWrapper w = new Slic3rConfigWrapper();
|
||||||
|
if (bundle.has("process_config")) {
|
||||||
|
JSONArray arr = bundle.getJSONArray("process_config");
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
List<String> stripped = new ArrayList<>();
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
String v = arr.getString(i);
|
||||||
|
names.add(v);
|
||||||
|
stripped.add(v.substring(v.indexOf('/') + 1, v.length() - 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String name : names) {
|
||||||
|
w.printConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "process", Slic3rConfigWrapper.PRINT_CONFIG_KEYS, stripped));
|
||||||
|
}
|
||||||
|
for (ConfigObject obj : w.printConfigs) {
|
||||||
|
String inherit = obj.get("inherits");
|
||||||
|
while (inherit != null) {
|
||||||
|
ConfigObject _obj = w.findPrint(inherit);
|
||||||
|
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
||||||
|
|
||||||
|
obj.values.remove("inherits");
|
||||||
|
HashMap<String, String> newMap = new HashMap<>();
|
||||||
|
newMap.putAll(_obj.values);
|
||||||
|
newMap.putAll(obj.values);
|
||||||
|
obj.values = newMap;
|
||||||
|
|
||||||
|
inherit = obj.values.get("inherits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bundle.has("filament_config")) {
|
||||||
|
JSONArray arr = bundle.getJSONArray("filament_config");
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
List<String> stripped = new ArrayList<>();
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
String v = arr.getString(i);
|
||||||
|
names.add(v);
|
||||||
|
stripped.add(v.substring(v.indexOf('/') + 1, v.length() - 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String name : names) {
|
||||||
|
w.filamentConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "filament", Slic3rConfigWrapper.FILAMENT_CONFIG_KEYS, stripped));
|
||||||
|
}
|
||||||
|
for (ConfigObject obj : w.filamentConfigs) {
|
||||||
|
String inherit = obj.get("inherits");
|
||||||
|
while (inherit != null) {
|
||||||
|
ConfigObject _obj = w.findFilament(inherit);
|
||||||
|
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
||||||
|
|
||||||
|
obj.values.remove("inherits");
|
||||||
|
HashMap<String, String> newMap = new HashMap<>();
|
||||||
|
newMap.putAll(_obj.values);
|
||||||
|
newMap.putAll(obj.values);
|
||||||
|
obj.values = newMap;
|
||||||
|
|
||||||
|
inherit = obj.values.get("inherits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bundle.has("printer_config")) {
|
||||||
|
JSONArray arr = bundle.getJSONArray("printer_config");
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
List<String> stripped = new ArrayList<>();
|
||||||
|
for (int i = 0; i < arr.length(); i++) {
|
||||||
|
String v = arr.getString(i);
|
||||||
|
names.add(v);
|
||||||
|
stripped.add(v.substring(v.indexOf('/') + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String name : names) {
|
||||||
|
w.printerConfigs.add(IOUtils.configJsonToIni(new JSONObject(IOUtils.readString(zf.getInputStream(zf.getEntry(name)))), "machine", Slic3rConfigWrapper.PRINTER_CONFIG_KEYS, stripped));
|
||||||
|
}
|
||||||
|
for (ConfigObject obj : w.printerConfigs) {
|
||||||
|
String inherit = obj.get("inherits");
|
||||||
|
while (inherit != null) {
|
||||||
|
ConfigObject _obj = w.findPrinter(inherit);
|
||||||
|
if (_obj == null) throw new IOUtils.MissingProfileException(inherit);
|
||||||
|
|
||||||
|
obj.values.remove("inherits");
|
||||||
|
HashMap<String, String> newMap = new HashMap<>();
|
||||||
|
newMap.putAll(_obj.values);
|
||||||
|
newMap.putAll(obj.values);
|
||||||
|
obj.values = newMap;
|
||||||
|
|
||||||
|
inherit = obj.values.get("inherits");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zf.close();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
loadIniForImport(new ByteArrayInputStream(w.serialize().getBytes(StandardCharsets.UTF_8)));
|
||||||
|
} catch (IOUtils.MissingProfileException ep) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||||
|
.setMessage(getString(R.string.MenuFileImportProfilesFailedBaseProfileNotFound, ep.profile))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show());
|
||||||
|
} catch (Exception e) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||||
|
.setMessage(e.toString())
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateAiModel(Bitmap bm) {
|
||||||
|
IS_GENERATING_AI_MODEL = true;
|
||||||
|
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();
|
||||||
|
IS_GENERATING_AI_MODEL = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(processTag));
|
||||||
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(MainActivity.this)
|
||||||
|
.setTitle(R.string.MenuFileAIGeneratorError)
|
||||||
|
.setMessage(e.toString())
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show());
|
||||||
|
IS_GENERATING_AI_MODEL = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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) {
|
||||||
new Thread(()->{
|
IOUtils.IO_POOL.submit(()->{
|
||||||
try {
|
try {
|
||||||
Slic3rConfigWrapper w = new Slic3rConfigWrapper(in);
|
Slic3rConfigWrapper w = new Slic3rConfigWrapper(in);
|
||||||
|
|
||||||
@@ -450,39 +621,68 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
enabledPrinters[i] = true;
|
enabledPrinters[i] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
new BeamAlertDialogBuilder(this)
|
if (prints.length == 0 && filaments.length == 0 && printers.length == 0) {
|
||||||
.setTitle(R.string.MenuFileExportProfilesPrints)
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||||
.setMultiChoiceItems(prints, enabledPrints, (dialog, which, isChecked) -> enabledPrints[which] = isChecked)
|
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||||
.setPositiveButton(android.R.string.ok, (d1, w1) -> new BeamAlertDialogBuilder(this)
|
.setMessage(R.string.MenuFileImportProfilesFailedEmpty)
|
||||||
.setTitle(R.string.MenuFileExportProfilesFilaments)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.setMultiChoiceItems(filaments, enabledFilaments, (dialog, which, isChecked) -> enabledFilaments[which] = isChecked)
|
.show());
|
||||||
.setPositiveButton(android.R.string.ok, (d2, w2) -> new BeamAlertDialogBuilder(this)
|
return;
|
||||||
.setTitle(R.string.MenuFileExportProfilesPrinters)
|
}
|
||||||
.setMultiChoiceItems(printers, enabledPrinters, (dialog, which, isChecked) -> enabledPrinters[which] = isChecked)
|
|
||||||
.setPositiveButton(android.R.string.ok, (d3, w3) -> {
|
Runnable finish = () -> {
|
||||||
for (int i = 0; i < enabledPrints.length; i++) {
|
for (int i = 0; i < enabledPrints.length; i++) {
|
||||||
if (enabledPrints[i]) {
|
if (enabledPrints[i]) {
|
||||||
SliceBeam.CONFIG.importPrint(w.printConfigs.get(i));
|
SliceBeam.CONFIG.importPrint(w.printConfigs.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < enabledFilaments.length; i++) {
|
for (int i = 0; i < enabledFilaments.length; i++) {
|
||||||
if (enabledFilaments[i]) {
|
if (enabledFilaments[i]) {
|
||||||
SliceBeam.CONFIG.importFilament(w.filamentConfigs.get(i));
|
SliceBeam.CONFIG.importFilament(w.filamentConfigs.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < enabledPrinters.length; i++) {
|
for (int i = 0; i < enabledPrinters.length; i++) {
|
||||||
if (enabledPrinters[i]) {
|
if (enabledPrinters[i]) {
|
||||||
SliceBeam.CONFIG.importPrinter(w.printerConfigs.get(i));
|
SliceBeam.CONFIG.importPrinter(w.printerConfigs.get(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SliceBeam.saveConfig();
|
SliceBeam.saveConfig();
|
||||||
})
|
};
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
Runnable printersRun = () -> {
|
||||||
.show())
|
if (printers.length == 0) {
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
finish.run();
|
||||||
.show())
|
return;
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
}
|
||||||
.show();
|
|
||||||
|
new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileExportProfilesPrinters)
|
||||||
|
.setMultiChoiceItems(printers, enabledPrinters, (dialog, which, isChecked) -> enabledPrinters[which] = isChecked)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d3, w3) -> finish.run())
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
};
|
||||||
|
Runnable filamentsRun = () -> {
|
||||||
|
if (filaments.length == 0) {
|
||||||
|
printersRun.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileExportProfilesFilaments)
|
||||||
|
.setMultiChoiceItems(filaments, enabledFilaments, (dialog, which, isChecked) -> enabledFilaments[which] = isChecked)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d2, w2) -> printersRun.run())
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
};
|
||||||
|
if (prints.length == 0) {
|
||||||
|
filamentsRun.run();
|
||||||
|
} else {
|
||||||
|
new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileExportProfilesPrints)
|
||||||
|
.setMultiChoiceItems(prints, enabledPrints, (dialog, which, isChecked) -> enabledPrints[which] = isChecked)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d1, w1) -> filamentsRun.run())
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("MainActivity", "Failed to read file", e);
|
Log.e("MainActivity", "Failed to read file", e);
|
||||||
@@ -493,27 +693,68 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show());
|
.show());
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFile(File f, boolean autoorient) {
|
||||||
|
String tag = UUID.randomUUID().toString();
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuFileOpenFileLoading).tag(tag));
|
||||||
|
IOUtils.IO_POOL.submit(() -> {
|
||||||
|
Process.setThreadPriority(-20);
|
||||||
|
if (delegate.getCurrentFragment() instanceof BedFragment) {
|
||||||
|
BedFragment fragment = (BedFragment) delegate.getCurrentFragment();
|
||||||
|
try {
|
||||||
|
boolean gcode = f.getName().endsWith(".gcode");
|
||||||
|
if (gcode) {
|
||||||
|
fragment.loadGCode(f);
|
||||||
|
} else {
|
||||||
|
fragment.loadModel(f);
|
||||||
|
}
|
||||||
|
fragment.getGlView().queueEvent(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Model model = fragment.getGlView().getRenderer().getModel();
|
||||||
|
if (model == null || fragment.getGlView().getRenderer().getBed() == null) {
|
||||||
|
fragment.getGlView().queueEvent(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gcode) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new ObjectsListChangedEvent());
|
||||||
|
}
|
||||||
|
int i = model.getObjectsCount() - 1;
|
||||||
|
if (autoorient) {
|
||||||
|
model.autoOrient(i);
|
||||||
|
fragment.getGlView().getRenderer().invalidateGlModel(i);
|
||||||
|
fragment.getGlView().requestRender();
|
||||||
|
}
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileOpenFileLoaded));
|
||||||
|
if (model.isBigObject(i)) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.WARNING, R.string.MenuFileOpenFileBigObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Slic3rRuntimeError e) {
|
||||||
|
Log.e("MainActivity", "Failed to load model", e);
|
||||||
|
f.delete();
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileOpenFileFailed)
|
||||||
|
.setMessage(e.toString())
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFile(Uri uri) {
|
private void loadFile(Uri uri) {
|
||||||
if (uri == null) return;
|
if (uri == null) return;
|
||||||
|
|
||||||
ContentResolver resolver = getContentResolver();
|
ContentResolver resolver = getContentResolver();
|
||||||
|
String fileName = IOUtils.getDisplayName(uri);
|
||||||
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
|
||||||
Cursor metaCursor = resolver.query(uri, projection, null, null, null);
|
|
||||||
String fileName = null;
|
|
||||||
if (metaCursor != null) {
|
|
||||||
try {
|
|
||||||
if (metaCursor.moveToFirst()) {
|
|
||||||
fileName = metaCursor.getString(0);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
metaCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
new BeamAlertDialogBuilder(this)
|
new BeamAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.MenuFileOpenFileFailed)
|
.setTitle(R.string.MenuFileOpenFileFailed)
|
||||||
@@ -522,9 +763,26 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
.show();
|
.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (fileName.endsWith(".orca_printer")) {
|
||||||
|
loadConvertedProfile(uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileName.endsWith(".ini")) {
|
||||||
|
try {
|
||||||
|
loadIniForImport(resolver.openInputStream(uri));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
new BeamAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.MenuFileImportProfilesFailed)
|
||||||
|
.setMessage(e.toString())
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
File f = new File(SliceBeam.getModelCacheDir(), fileName);
|
File f = new File(SliceBeam.getModelCacheDir(), fileName);
|
||||||
// TODO: Check if file already exists
|
// TODO: Check if file already exists
|
||||||
new Thread(()->{
|
IOUtils.IO_POOL.submit(()->{
|
||||||
try {
|
try {
|
||||||
InputStream in = resolver.openInputStream(uri);
|
InputStream in = resolver.openInputStream(uri);
|
||||||
FileOutputStream fos = new FileOutputStream(f);
|
FileOutputStream fos = new FileOutputStream(f);
|
||||||
@@ -534,30 +792,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
fos.close();
|
fos.close();
|
||||||
in.close();
|
in.close();
|
||||||
|
loadFile(f, false);
|
||||||
ViewUtils.postOnMainThread(() -> {
|
|
||||||
if (delegate.getCurrentFragment() instanceof BedFragment) {
|
|
||||||
BedFragment fragment = (BedFragment) delegate.getCurrentFragment();
|
|
||||||
try {
|
|
||||||
if (f.getName().endsWith(".gcode")) {
|
|
||||||
fragment.loadGCode(f);
|
|
||||||
} else {
|
|
||||||
fragment.loadModel(f);
|
|
||||||
SliceBeam.EVENT_BUS.fireEvent(new ObjectsListChangedEvent());
|
|
||||||
}
|
|
||||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuFileOpenFileLoaded));
|
|
||||||
} catch (Slic3rRuntimeError e) {
|
|
||||||
Log.e("MainActivity", "Failed to load model", e);
|
|
||||||
f.delete();
|
|
||||||
|
|
||||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(this)
|
|
||||||
.setTitle(R.string.MenuFileOpenFileFailed)
|
|
||||||
.setMessage(e.toString())
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("MainActivity", "Failed to write cache file", e);
|
Log.e("MainActivity", "Failed to write cache file", e);
|
||||||
|
|
||||||
@@ -568,7 +803,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.show());
|
.show());
|
||||||
}
|
}
|
||||||
}).start();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,21 +1,35 @@
|
|||||||
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.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.opengl.GLSurfaceView;
|
import android.opengl.GLSurfaceView;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
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;
|
||||||
@@ -30,12 +44,14 @@ import androidx.activity.EdgeToEdge;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.ColorUtils;
|
import androidx.core.graphics.ColorUtils;
|
||||||
import androidx.core.graphics.Insets;
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.view.ViewCompat;
|
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;
|
||||||
|
|
||||||
@@ -73,10 +89,17 @@ 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.CloudFeaturesUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudLoginStateUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudSyncFinishedEvent;
|
||||||
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;
|
||||||
@@ -94,9 +117,13 @@ import ru.ytkab0bp.slicebeam.view.BeamSwitch;
|
|||||||
import ru.ytkab0bp.slicebeam.view.BoostySubsView;
|
import ru.ytkab0bp.slicebeam.view.BoostySubsView;
|
||||||
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
import ru.ytkab0bp.slicebeam.view.FadeRecyclerView;
|
||||||
import ru.ytkab0bp.slicebeam.view.MiniColorView;
|
import ru.ytkab0bp.slicebeam.view.MiniColorView;
|
||||||
|
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_CLOUD_PROFILE = "cloud_profile";
|
||||||
|
public final static String EXTRA_CLOUD_IMPORT_FROM_SETUP = "cloud_import_from_setup";
|
||||||
|
|
||||||
private final static String TAG = "SetupActivity";
|
private final static String TAG = "SetupActivity";
|
||||||
|
|
||||||
@@ -117,6 +144,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private GLSurfaceView backgroundView;
|
private GLSurfaceView backgroundView;
|
||||||
private GLModel backgroundModel;
|
private GLModel backgroundModel;
|
||||||
|
private GLShadersManager shadersManager;
|
||||||
|
|
||||||
private int titleY;
|
private int titleY;
|
||||||
private float backgroundProgress;
|
private float backgroundProgress;
|
||||||
@@ -129,6 +157,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;
|
||||||
@@ -137,6 +166,9 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
private Map<ProfilesRepo, List<Slic3rConfigWrapper>> profilesMap = new HashMap<>();
|
private Map<ProfilesRepo, List<Slic3rConfigWrapper>> profilesMap = new HashMap<>();
|
||||||
private boolean isProfilesLoaded;
|
private boolean isProfilesLoaded;
|
||||||
private boolean about;
|
private boolean about;
|
||||||
|
private boolean boostyOnly;
|
||||||
|
private boolean cloudProfile;
|
||||||
|
private boolean cloudImport;
|
||||||
|
|
||||||
private List<ConfigObject> enabledPrinters = new ArrayList<>();
|
private List<ConfigObject> enabledPrinters = new ArrayList<>();
|
||||||
|
|
||||||
@@ -153,8 +185,11 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
SliceBeam.EVENT_BUS.registerListener(this);
|
SliceBeam.EVENT_BUS.registerListener(this);
|
||||||
|
|
||||||
about = getIntent().getBooleanExtra(EXTRA_ABOUT, false);
|
about = getIntent().getBooleanExtra(EXTRA_ABOUT, false);
|
||||||
|
boostyOnly = getIntent().getBooleanExtra(EXTRA_BOOSTY_ONLY, false);
|
||||||
|
cloudProfile = getIntent().getBooleanExtra(EXTRA_CLOUD_PROFILE, false);
|
||||||
|
cloudImport = getIntent().getBooleanExtra(EXTRA_CLOUD_IMPORT_FROM_SETUP, false);
|
||||||
|
|
||||||
if (!about) {
|
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)
|
||||||
@@ -162,11 +197,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 ? 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();
|
||||||
@@ -197,13 +236,15 @@ 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) {
|
if (position == 0 && !boostyOnly && !cloudProfile) {
|
||||||
backgroundProgress = positionOffset;
|
backgroundProgress = positionOffset;
|
||||||
} else {
|
} else {
|
||||||
backgroundProgress = 1f;
|
backgroundProgress = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (position == BOOSTY_INDEX) {
|
if (boostyOnly) {
|
||||||
|
boostyProgress = 1f;
|
||||||
|
} else if (position == BOOSTY_INDEX) {
|
||||||
boostyProgress = 1f - positionOffset;
|
boostyProgress = 1f - positionOffset;
|
||||||
} else if (position == BOOSTY_INDEX - 1) {
|
} else if (position == BOOSTY_INDEX - 1) {
|
||||||
boostyProgress = positionOffset;
|
boostyProgress = positionOffset;
|
||||||
@@ -333,14 +374,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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -353,7 +387,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);
|
||||||
@@ -364,20 +398,23 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
super.surfaceDestroyed(holder);
|
super.surfaceDestroyed(holder);
|
||||||
backgroundModel.release();
|
backgroundModel.release();
|
||||||
backgroundModel = null;
|
backgroundModel = null;
|
||||||
GLShadersManager.clearShaders();
|
shadersManager.clearShaders();
|
||||||
|
shadersManager = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
backgroundView.setEGLContextClientVersion(3);
|
backgroundView.setEGLContextClientVersion(3);
|
||||||
backgroundView.setRenderer(new GLSurfaceView.Renderer() {
|
backgroundView.setRenderer(new GLSurfaceView.Renderer() {
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
public void onSurfaceCreated(GL10 gl, EGLConfig config) {}
|
||||||
backgroundModel = new GLModel();
|
|
||||||
backgroundModel.initBackgroundTriangles();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||||
glViewport(0, 0, width, height);
|
glViewport(0, 0, width, height);
|
||||||
|
if (backgroundModel == null) {
|
||||||
|
backgroundModel = new GLModel();
|
||||||
|
backgroundModel.initBackgroundTriangles();
|
||||||
|
shadersManager = new GLShadersManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float time;
|
private float time;
|
||||||
@@ -393,7 +430,7 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
if (backgroundModel != null) {
|
if (backgroundModel != null) {
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
GLShaderProgram shader = GLShadersManager.get(GLShadersManager.SHADER_BEAM_INTRO);
|
GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_BEAM_INTRO);
|
||||||
shader.startUsing();
|
shader.startUsing();
|
||||||
int topColor = ThemesRepo.getColor(android.R.attr.colorAccent);
|
int topColor = ThemesRepo.getColor(android.R.attr.colorAccent);
|
||||||
int bottomColor = ThemesRepo.getColor(android.R.attr.windowBackground);
|
int bottomColor = ThemesRepo.getColor(android.R.attr.windowBackground);
|
||||||
@@ -401,10 +438,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();
|
||||||
@@ -418,7 +458,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));
|
||||||
@@ -443,6 +483,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();
|
||||||
@@ -452,7 +503,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();
|
||||||
@@ -460,8 +511,43 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onCloudSyncFinished(CloudSyncFinishedEvent e) {
|
||||||
|
if (cloudProfile && Prefs.getCloudAPIToken() != null && cloudImport) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
if (!about && !boostyOnly && !cloudProfile) {
|
||||||
|
if (Prefs.getCloudAPIToken() != null) {
|
||||||
|
limitRepoFragmentCount = false;
|
||||||
|
limitProfileFragmentCount = false;
|
||||||
|
pager.getAdapter().notifyDataSetChanged();
|
||||||
|
pager.setCurrentItem(pager.getAdapter().getItemCount() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onCloudAuthStateUpdated(CloudLoginStateUpdatedEvent e) {
|
||||||
|
if (cloudProfile) {
|
||||||
|
cloudItem.bindLoginButton(true);
|
||||||
|
cloudItem.bindFeatures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onCloudFeaturesUpdated(CloudFeaturesUpdatedEvent e) {
|
||||||
|
if (!about && !boostyOnly && !cloudProfile) {
|
||||||
|
reposItem.onCloudInfoUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setItems() {
|
private void setItems() {
|
||||||
if (about) {
|
if (cloudProfile){
|
||||||
|
adapter.setItems(Collections.singletonList(cloudItem = new CloudProfileItem()));
|
||||||
|
} else if (boostyOnly) {
|
||||||
|
adapter.setItems(Collections.singletonList(new BoostyItem()));
|
||||||
|
} else if (about) {
|
||||||
adapter.setItems(Collections.singletonList(new AboutItem()));
|
adapter.setItems(Collections.singletonList(new AboutItem()));
|
||||||
} else {
|
} else {
|
||||||
List<SimpleRecyclerItem> items = new ArrayList<>(Arrays.asList(
|
List<SimpleRecyclerItem> items = new ArrayList<>(Arrays.asList(
|
||||||
@@ -595,6 +681,331 @@ 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 FadeRecyclerView 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 FadeRecyclerView(ctx);
|
||||||
|
recyclerView.setBitmapMode();
|
||||||
|
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 (!BuildConfig.IS_GOOGLE_PLAY && features.earlyAccessLevel != -1 && lvl.level >= features.earlyAccessLevel) {
|
||||||
|
items.add(new PreferenceItem()
|
||||||
|
.setForceDark(true)
|
||||||
|
.setPaddings(ViewUtils.dp(8))
|
||||||
|
.setIcon(R.drawable.clock_circle_dashed_outline_24)
|
||||||
|
.setTitle(ctx.getString(R.string.SettingsCloudManageFeatureEarlyAccess))
|
||||||
|
.setSubtitle(ctx.getString(R.string.SettingsCloudManageFeatureEarlyAccessDescription)));
|
||||||
|
}
|
||||||
|
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
|
||||||
@@ -724,6 +1135,8 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
private ProgressBar progressBar;
|
private ProgressBar progressBar;
|
||||||
private FrameLayout loadedLayout;
|
private FrameLayout loadedLayout;
|
||||||
private SimpleRecyclerAdapter adapter;
|
private SimpleRecyclerAdapter adapter;
|
||||||
|
private TextView cloudImportView;
|
||||||
|
private TextView cloudOrView;
|
||||||
private TextView customProfileView;
|
private TextView customProfileView;
|
||||||
private TextView buttonView;
|
private TextView buttonView;
|
||||||
|
|
||||||
@@ -740,10 +1153,29 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
LinearLayout ll = new LinearLayout(ctx);
|
LinearLayout ll = new LinearLayout(ctx);
|
||||||
ll.setOrientation(LinearLayout.VERTICAL);
|
ll.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
cloudImportView = new TextView(ctx);
|
||||||
|
cloudImportView.setVisibility(View.GONE);
|
||||||
|
cloudImportView.setText(R.string.IntroImportFromCloud);
|
||||||
|
cloudImportView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||||
|
cloudImportView.setGravity(Gravity.CENTER);
|
||||||
|
cloudImportView.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
||||||
|
cloudImportView.setOnClickListener(v -> startActivity(new Intent(v.getContext(), SetupActivity.class).putExtra(SetupActivity.EXTRA_CLOUD_PROFILE, true).putExtra(SetupActivity.EXTRA_CLOUD_IMPORT_FROM_SETUP, true)));
|
||||||
|
ll.addView(cloudImportView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(48)) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
}});
|
||||||
|
|
||||||
|
cloudOrView = new TextView(ctx);
|
||||||
|
cloudOrView.setVisibility(View.GONE);
|
||||||
|
cloudOrView.setText(R.string.IntroImportOr);
|
||||||
|
cloudOrView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||||
|
cloudOrView.setGravity(Gravity.CENTER);
|
||||||
|
ll.addView(cloudOrView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
}});
|
||||||
|
|
||||||
customProfileView = new TextView(ctx);
|
customProfileView = new TextView(ctx);
|
||||||
customProfileView.setText(R.string.IntroCustomProfile);
|
customProfileView.setText(R.string.IntroCustomProfile);
|
||||||
customProfileView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
customProfileView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||||
customProfileView.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
|
||||||
customProfileView.setGravity(Gravity.CENTER);
|
customProfileView.setGravity(Gravity.CENTER);
|
||||||
customProfileView.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
customProfileView.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
||||||
customProfileView.setOnClickListener(v -> {
|
customProfileView.setOnClickListener(v -> {
|
||||||
@@ -752,8 +1184,9 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
SetupActivity.this.adapter.notifyItemRangeInserted(REPOS_INDEX + 1, SetupActivity.this.adapter.getItemCount() - REPOS_INDEX - 1);
|
SetupActivity.this.adapter.notifyItemRangeInserted(REPOS_INDEX + 1, SetupActivity.this.adapter.getItemCount() - REPOS_INDEX - 1);
|
||||||
scrollToNext();
|
scrollToNext();
|
||||||
});
|
});
|
||||||
ll.addView(customProfileView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
ll.addView(customProfileView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(48)) {{
|
||||||
bottomMargin = ViewUtils.dp(12);
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
bottomMargin = ViewUtils.dp(6);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
buttonView = new TextView(ctx);
|
buttonView = new TextView(ctx);
|
||||||
@@ -800,7 +1233,13 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onBindView(View view) {
|
public void onBindView(View view) {
|
||||||
progressBar.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.colorAccent)));
|
progressBar.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.colorAccent)));
|
||||||
|
cloudImportView.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||||
|
cloudImportView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||||
|
cloudImportView.setVisibility(BeamServerData.isCloudAvailable() ? View.VISIBLE : View.GONE);
|
||||||
|
cloudOrView.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
cloudOrView.setVisibility(BeamServerData.isCloudAvailable() ? View.VISIBLE : View.GONE);
|
||||||
customProfileView.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
customProfileView.setTextColor(ThemesRepo.getColor(android.R.attr.colorAccent));
|
||||||
|
customProfileView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||||
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(android.R.attr.colorAccent), 16));
|
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(android.R.attr.colorAccent), 16));
|
||||||
|
|
||||||
if (adapter.getItemCount() == 0 && isReposLoaded) {
|
if (adapter.getItemCount() == 0 && isReposLoaded) {
|
||||||
@@ -823,6 +1262,13 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onCloudInfoUpdated() {
|
||||||
|
if (cloudImportView != null) {
|
||||||
|
cloudImportView.setVisibility(BeamServerData.isCloudAvailable() ? View.VISIBLE : View.GONE);
|
||||||
|
cloudOrView.setVisibility(BeamServerData.isCloudAvailable() ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onReposLoaded() {
|
public void onReposLoaded() {
|
||||||
List<SimpleRecyclerItem> items = new ArrayList<>(repos);
|
List<SimpleRecyclerItem> items = new ArrayList<>(repos);
|
||||||
items.add(new TextHintRecyclerItem(SliceBeam.INSTANCE.getString(R.string.IntroSelectRepos)));
|
items.add(new TextHintRecyclerItem(SliceBeam.INSTANCE.getString(R.string.IntroSelectRepos)));
|
||||||
@@ -1007,25 +1453,42 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}});
|
}});
|
||||||
|
|
||||||
TextView subscribeButton = new TextView(ctx);
|
TextView subscribeButton = new TextView(ctx);
|
||||||
subscribeButton.setText(R.string.IntroBoostySupport);
|
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(ctx.getString(R.string.IntroBoostySupport)).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);
|
||||||
|
subscribeButton.setText(sb);
|
||||||
subscribeButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
subscribeButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
|
||||||
subscribeButton.setTextColor(ThemesRepo.getColor(R.attr.boostyColorTop));
|
subscribeButton.setTextColor(Color.WHITE);
|
||||||
subscribeButton.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
subscribeButton.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
subscribeButton.setGravity(Gravity.CENTER);
|
subscribeButton.setGravity(Gravity.CENTER);
|
||||||
subscribeButton.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
subscribeButton.setPadding(ViewUtils.dp(12), ViewUtils.dp(8), ViewUtils.dp(12), ViewUtils.dp(8));
|
||||||
|
subscribeButton.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), 16));
|
||||||
subscribeButton.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp"))));
|
subscribeButton.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp"))));
|
||||||
ll.addView(subscribeButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
ll.addView(subscribeButton, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||||
bottomMargin = ViewUtils.dp(12);
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
bottomMargin = ViewUtils.dp(8);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
TextView buttonView = new TextView(ctx);
|
TextView buttonView = new TextView(ctx);
|
||||||
buttonView.setText(R.string.IntroNext);
|
if (boostyOnly) {
|
||||||
|
buttonView.setText(android.R.string.ok);
|
||||||
|
} else {
|
||||||
|
buttonView.setText(R.string.IntroNext);
|
||||||
|
}
|
||||||
buttonView.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
buttonView.setTextColor(ThemesRepo.getColor(R.attr.textColorOnAccent));
|
||||||
buttonView.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
buttonView.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
buttonView.setGravity(Gravity.CENTER);
|
buttonView.setGravity(Gravity.CENTER);
|
||||||
buttonView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
buttonView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
|
||||||
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(R.attr.boostyColorTop), 16));
|
buttonView.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ThemesRepo.getColor(R.attr.boostyColorTop), 16));
|
||||||
buttonView.setOnClickListener(v-> scrollToNext());
|
buttonView.setOnClickListener(v-> {
|
||||||
|
if (boostyOnly) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scrollToNext();
|
||||||
|
});
|
||||||
ll.addView(buttonView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
ll.addView(buttonView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(52)) {{
|
||||||
leftMargin = rightMargin = ViewUtils.dp(16);
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
bottomMargin = ViewUtils.dp(16);
|
bottomMargin = ViewUtils.dp(16);
|
||||||
@@ -1166,11 +1629,13 @@ public class SetupActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
SliceBeam.getCurrentConfigFile().delete();
|
if (Prefs.getCloudAPIToken() == null || SliceBeam.CONFIG == null) {
|
||||||
SliceBeam.CONFIG = cfg;
|
SliceBeam.getCurrentConfigFile().delete();
|
||||||
FileOutputStream fos = new FileOutputStream(SliceBeam.getConfigFile());
|
SliceBeam.CONFIG = cfg;
|
||||||
fos.write(cfg.serialize().getBytes(StandardCharsets.UTF_8));
|
FileOutputStream fos = new FileOutputStream(SliceBeam.getConfigFile());
|
||||||
fos.close();
|
fos.write(cfg.serialize().getBytes(StandardCharsets.UTF_8));
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
|
||||||
startActivity(new Intent(SetupActivity.this, MainActivity.class));
|
startActivity(new Intent(SetupActivity.this, MainActivity.class));
|
||||||
finish();
|
finish();
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.WebView;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
import com.instacart.truetime.time.TrueTimeImpl;
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -15,19 +13,32 @@ import java.io.IOException;
|
|||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import ru.ytkab0bp.eventbus.EventBus;
|
import ru.ytkab0bp.eventbus.EventBus;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.AppBoot;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.BeamServerDataTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.CheckUpdateJsonTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.ClearModelCacheTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.CloudInitTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.EventBusTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.LoadSlic3rConfigTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.PrefsTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.PrintConfigWarmupTask;
|
||||||
|
import ru.ytkab0bp.slicebeam.boot.TrueTimeTask;
|
||||||
|
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;
|
||||||
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||||
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
import ru.ytkab0bp.slicebeam.utils.VibrationUtils;
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -38,36 +49,18 @@ public class SliceBeam extends Application {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
INSTANCE = this;
|
INSTANCE = this;
|
||||||
EventBus.registerImpl(this);
|
AppBoot.run(Arrays.asList(
|
||||||
Prefs.init(this);
|
new EventBusTask(),
|
||||||
VibrationUtils.init(this);
|
new PrefsTask(),
|
||||||
tryCheckInfo();
|
new VibrationUtilsTask(),
|
||||||
PrintConfigDef.getInstance();
|
new TrueTimeTask(),
|
||||||
try {
|
new BeamServerDataTask(),
|
||||||
getAssets().open("update.json").close();
|
new PrintConfigWarmupTask(),
|
||||||
hasUpdateInfo = true;
|
new CheckUpdateJsonTask(),
|
||||||
} catch (IOException e) {
|
new ClearModelCacheTask(),
|
||||||
hasUpdateInfo = false;
|
new LoadSlic3rConfigTask(),
|
||||||
}
|
new CloudInitTask()
|
||||||
|
));
|
||||||
File cache = SliceBeam.getModelCacheDir();
|
|
||||||
if (cache.exists()) {
|
|
||||||
for (File f : cache.listFiles()) {
|
|
||||||
f.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
File cfgFile = getConfigFile();
|
|
||||||
getCurrentConfigFile().delete();
|
|
||||||
if (cfgFile.exists()) {
|
|
||||||
try {
|
|
||||||
CONFIG = new Slic3rConfigWrapper(cfgFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG);
|
|
||||||
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||||
StringWriter sw = new StringWriter();
|
StringWriter sw = new StringWriter();
|
||||||
PrintWriter pw = new PrintWriter(sw);
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
@@ -81,17 +74,6 @@ public class SliceBeam extends Application {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void tryCheckInfo() {
|
|
||||||
try {
|
|
||||||
SERVER_DATA = new BeamServerData(new JSONObject(Prefs.getBeamServerData()));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
if (System.currentTimeMillis() - Prefs.getLastCheckedInfo() >= 86400000L) {
|
|
||||||
BeamServerData.load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveConfig() {
|
public static void saveConfig() {
|
||||||
SliceBeam.CONFIG_UID++;
|
SliceBeam.CONFIG_UID++;
|
||||||
File f = getConfigFile();
|
File f = getConfigFile();
|
||||||
@@ -104,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() {
|
||||||
@@ -122,6 +105,7 @@ public class SliceBeam extends Application {
|
|||||||
if (SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print")) != null) {
|
if (SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print")) != null) {
|
||||||
singleObject.values.putAll(SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print")).values);
|
singleObject.values.putAll(SliceBeam.CONFIG.findPrint(SliceBeam.CONFIG.presets.get("print")).values);
|
||||||
}
|
}
|
||||||
|
// TODO: MMU. Detect by printerConfig#getExtruderCount()
|
||||||
if (SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")) != null) {
|
if (SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")) != null) {
|
||||||
singleObject.values.putAll(SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")).values);
|
singleObject.values.putAll(SliceBeam.CONFIG.findFilament(SliceBeam.CONFIG.presets.get("filament")).values);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseBooleanArray;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||||
|
|
||||||
|
public class AppBoot {
|
||||||
|
private final static String TAG = "boot";
|
||||||
|
|
||||||
|
static ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
static List<BootTask> tasks;
|
||||||
|
static List<Runnable> pendingMain = new ArrayList<>();
|
||||||
|
static List<BootTask> pendingTasks = new ArrayList<>();
|
||||||
|
static SparseBooleanArray completed = new SparseBooleanArray();
|
||||||
|
static CountDownLatch latch;
|
||||||
|
|
||||||
|
public static void run(List<BootTask> tasks) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
AppBoot.tasks = tasks;
|
||||||
|
int size = tasks.size();
|
||||||
|
for (int i = 0, s = tasks.size(); i < s; i++) {
|
||||||
|
BootTask task = tasks.get(i);
|
||||||
|
if (task.nonCritical) {
|
||||||
|
if (!task.workerThread) {
|
||||||
|
throw new IllegalArgumentException("Can't schedule non-critical task on main thread");
|
||||||
|
}
|
||||||
|
size--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppBoot.latch = new CountDownLatch(size);
|
||||||
|
|
||||||
|
for (int i = 0, s = tasks.size(); i < s; i++) {
|
||||||
|
BootTask task = tasks.get(i);
|
||||||
|
task.index = i;
|
||||||
|
tryRunTask(task, true, false);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
while (!latch.await(10, TimeUnit.MILLISECONDS)) {
|
||||||
|
if (!pendingMain.isEmpty()) {
|
||||||
|
List<Runnable> clone = new ArrayList<>(pendingMain);
|
||||||
|
for (Runnable r : clone) {
|
||||||
|
r.run();
|
||||||
|
}
|
||||||
|
pendingMain.removeAll(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "Boot in " + (System.currentTimeMillis() - start) + "ms");
|
||||||
|
}
|
||||||
|
tryShutdown();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void tryShutdown() {
|
||||||
|
if (completed.size() == tasks.size()) {
|
||||||
|
executor.shutdown();
|
||||||
|
executor = null;
|
||||||
|
tasks = null;
|
||||||
|
pendingMain = null;
|
||||||
|
pendingTasks = null;
|
||||||
|
completed = null;
|
||||||
|
latch = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void tryRunTask(BootTask task, boolean fromMain, boolean isContinue) {
|
||||||
|
if (checkDependencies(task.dependencies)) {
|
||||||
|
Runnable r = () -> {
|
||||||
|
try {
|
||||||
|
task.run.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error while executing boot task", e);
|
||||||
|
}
|
||||||
|
completed.put(task.index, true);
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(TAG, "Finish " + task);
|
||||||
|
}
|
||||||
|
if (!task.nonCritical) {
|
||||||
|
latch.countDown();
|
||||||
|
} else {
|
||||||
|
tryShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isContinue) {
|
||||||
|
continueTasks(fromMain);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (task.workerThread) {
|
||||||
|
executor.submit(r);
|
||||||
|
} else {
|
||||||
|
if (fromMain) {
|
||||||
|
r.run();
|
||||||
|
} else {
|
||||||
|
pendingMain.add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingTasks.add(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void continueTasks(boolean fromMain) {
|
||||||
|
for (Iterator<BootTask> it = pendingTasks.iterator(); it.hasNext();) {
|
||||||
|
BootTask task = it.next();
|
||||||
|
if (checkDependencies(task.dependencies)) {
|
||||||
|
tryRunTask(task, fromMain, true);
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkDependencies(List<Class<?>> clzs) {
|
||||||
|
if (clzs.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, s = tasks.size(); i < s; i++) {
|
||||||
|
if (clzs.contains(tasks.get(i).getClass())) {
|
||||||
|
if (!completed.get(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.BeamServerData;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
|
||||||
|
public class BeamServerDataTask extends BootTask {
|
||||||
|
public BeamServerDataTask() {
|
||||||
|
super(() -> {
|
||||||
|
try {
|
||||||
|
SliceBeam.SERVER_DATA = new BeamServerData(new JSONObject(Prefs.getBeamServerData()));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
if (System.currentTimeMillis() - Prefs.getLastCheckedInfo() >= 86400000L) {
|
||||||
|
ViewUtils.postOnMainThread(BeamServerData::load);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BootTask {
|
||||||
|
public final List<Class<?>> dependencies;
|
||||||
|
public final Runnable run;
|
||||||
|
public boolean workerThread;
|
||||||
|
public int priority;
|
||||||
|
public boolean nonCritical;
|
||||||
|
|
||||||
|
/* package */ int index;
|
||||||
|
|
||||||
|
public BootTask(Runnable run) {
|
||||||
|
this.dependencies = Collections.emptyList();
|
||||||
|
this.run = run;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BootTask(List<Class<?>> dependencies, Runnable run) {
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
this.run = run;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BootTask onWorker() {
|
||||||
|
return onWorker(-20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BootTask onWorker(int priority) {
|
||||||
|
this.workerThread = true;
|
||||||
|
this.priority = priority;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
|
||||||
|
public class CheckUpdateJsonTask extends BootTask {
|
||||||
|
public CheckUpdateJsonTask() {
|
||||||
|
super(() -> {
|
||||||
|
try {
|
||||||
|
SliceBeam.INSTANCE.getAssets().open("update.json").close();
|
||||||
|
SliceBeam.hasUpdateInfo = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
SliceBeam.hasUpdateInfo = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
|
||||||
|
public class ClearModelCacheTask extends BootTask {
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
public ClearModelCacheTask() {
|
||||||
|
super(()->{
|
||||||
|
File cache = SliceBeam.getModelCacheDir();
|
||||||
|
if (cache.exists()) {
|
||||||
|
for (File f : cache.listFiles()) {
|
||||||
|
f.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
nonCritical = true;
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
|
|
||||||
|
public class CloudCachedInitTask extends BootTask {
|
||||||
|
public CloudCachedInitTask() {
|
||||||
|
super(Collections.singletonList(PrefsTask.class), CloudController::initCached);
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
|
|
||||||
|
public class CloudInitTask extends BootTask {
|
||||||
|
public CloudInitTask() {
|
||||||
|
super(Arrays.asList(PrefsTask.class, TrueTimeTask.class, LoadSlic3rConfigTask.class, CloudCachedInitTask.class), CloudController::init);
|
||||||
|
onWorker();
|
||||||
|
nonCritical = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.EventBus;
|
||||||
|
import ru.ytkab0bp.slicebeam.BuildConfig;
|
||||||
|
|
||||||
|
public class EventBusTask extends BootTask {
|
||||||
|
|
||||||
|
public EventBusTask() {
|
||||||
|
super(() -> EventBus.registerImpl(BuildConfig.APPLICATION_ID));
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
|
public class LoadSlic3rConfigTask extends BootTask {
|
||||||
|
public LoadSlic3rConfigTask() {
|
||||||
|
super(() -> {
|
||||||
|
File cfgFile = SliceBeam.getConfigFile();
|
||||||
|
SliceBeam.getCurrentConfigFile().delete();
|
||||||
|
if (cfgFile.exists()) {
|
||||||
|
try {
|
||||||
|
SliceBeam.CONFIG = new Slic3rConfigWrapper(cfgFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
|
||||||
|
public class PrefsTask extends BootTask {
|
||||||
|
public PrefsTask() {
|
||||||
|
super(()->Prefs.init(SliceBeam.INSTANCE));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.slic3r.PrintConfigDef;
|
||||||
|
|
||||||
|
public class PrintConfigWarmupTask extends BootTask {
|
||||||
|
public PrintConfigWarmupTask() {
|
||||||
|
super(PrintConfigDef::getInstance);
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
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 java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
|
||||||
|
public class TrueTimeTask extends BootTask {
|
||||||
|
public TrueTimeTask() {
|
||||||
|
super(() -> {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
SliceBeam.TRUE_TIME = new TrueTimeImpl(new TrueTimeParameters.Builder().buildParams(), Dispatchers.getIO(), new TrueTimeEventListener() {
|
||||||
|
@Override
|
||||||
|
public void initialize(@NonNull TrueTimeParameters trueTimeParameters) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initializeSuccess(@NonNull long[] longs) {
|
||||||
|
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(300, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
});
|
||||||
|
onWorker();
|
||||||
|
nonCritical = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.boot;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.VibrationUtils;
|
||||||
|
|
||||||
|
public class VibrationUtilsTask extends BootTask {
|
||||||
|
|
||||||
|
public VibrationUtilsTask() {
|
||||||
|
super(() -> VibrationUtils.init(SliceBeam.INSTANCE));
|
||||||
|
onWorker();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
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(requestType = RequestType.POST, value = "sync/upload")
|
||||||
|
void syncUpload(@Arg("") String data, @Header("Content-Type") String type, 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 early access
|
||||||
|
*/
|
||||||
|
public int earlyAccessLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,449 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.cloud;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.sapil.APICallback;
|
||||||
|
import ru.ytkab0bp.sapil.APIRequestHandle;
|
||||||
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudFeaturesUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudLoginStateUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudModelsRemainingCountUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudSyncFinishedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.CloudUserInfoUpdatedEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.slic3r.Slic3rConfigWrapper;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.IOUtils;
|
||||||
|
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";
|
||||||
|
public 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 initCached() {
|
||||||
|
if (Prefs.getCloudCachedUserFeatures() != null) {
|
||||||
|
userFeatures = gson.fromJson(Prefs.getCloudCachedUserFeatures(), CloudAPI.UserFeatures.class);
|
||||||
|
}
|
||||||
|
if (Prefs.getCloudAPIToken() != null) {
|
||||||
|
if (Prefs.getCloudCachedUserInfo() != null) {
|
||||||
|
userInfo = gson.fromJson(Prefs.getCloudCachedUserInfo(), CloudAPI.UserInfo.class);
|
||||||
|
modelsUsed = Prefs.getCloudCachedUsedModels();
|
||||||
|
modelsMaxGenerations = Prefs.getCloudCachedMaxModels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
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 (needSyncInfo || userInfo == null) {
|
||||||
|
loadUserInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needSyncInfo && userInfo != null && isSyncAvailable() && Prefs.isCloudProfileSyncEnabled()) {
|
||||||
|
if (now - Prefs.getCloudLastSync() > MIN_SYNC_DELTA) {
|
||||||
|
syncData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
syncData();
|
||||||
|
}
|
||||||
|
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 hasAccountFeatures() {
|
||||||
|
return userFeatures != null && userFeatures.levels != null && !userFeatures.levels.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 downloadData(long lastModified) {
|
||||||
|
CloudAPI.INSTANCE.syncGet(new APICallback<String>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(String response) {
|
||||||
|
IOUtils.IO_POOL.submit(() -> {
|
||||||
|
try {
|
||||||
|
File f = SliceBeam.getConfigFile();
|
||||||
|
byte[] data = Base64.decode(response, 0);
|
||||||
|
FileOutputStream fos = new FileOutputStream(f);
|
||||||
|
fos.write(data);
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
SliceBeam.CONFIG = new Slic3rConfigWrapper(f);
|
||||||
|
|
||||||
|
Prefs.setCloudLocalLastModified(lastModified);
|
||||||
|
Prefs.setCloudLocalLastSentModified(lastModified);
|
||||||
|
Prefs.setCloudRemoteLastModified(lastModified);
|
||||||
|
Prefs.setCloudLastSync(SliceBeam.TRUE_TIME.now().getTime());
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.CloudSyncSuccess));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to write 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));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to download 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));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void syncData() {
|
||||||
|
if (isSyncInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long modified = Prefs.getCloudLocalLastModified();
|
||||||
|
isSyncInProgress = true;
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.CloudSyncInProgress).tag(CLOUD_SYNC_TAG));
|
||||||
|
|
||||||
|
CloudAPI.INSTANCE.syncGetState(new APICallback<CloudAPI.SyncState>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.SyncState response) {
|
||||||
|
if (SliceBeam.CONFIG == null && response.usedSize != 0) {
|
||||||
|
// Setup screen, no config yet
|
||||||
|
downloadData(response.lastUpdatedDate);
|
||||||
|
} else if (response.usedSize == 0) {
|
||||||
|
if (SliceBeam.CONFIG == null) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No data on server yet, send anyway
|
||||||
|
uploadData(modified);
|
||||||
|
} else if (response.lastUpdatedDate != Prefs.getCloudRemoteLastModified()) {
|
||||||
|
if (Prefs.getCloudLocalLastSentModified() == modified) {
|
||||||
|
// Modified only on server
|
||||||
|
downloadData(response.lastUpdatedDate);
|
||||||
|
} else {
|
||||||
|
// Modified on client and on server
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.WARNING, R.string.CloudSyncConflict).button(R.string.CloudSyncConflictResolve, v -> {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm", Locale.getDefault());
|
||||||
|
new BeamAlertDialogBuilder(v.getContext())
|
||||||
|
.setTitle(R.string.CloudSyncConflict)
|
||||||
|
.setMessage(v.getContext().getString(R.string.CloudSyncConflictResolveMessage, format.format(new Date(response.lastUpdatedDate)), format.format(new Date(Prefs.getCloudLocalLastModified()))))
|
||||||
|
.setPositiveButton(R.string.CloudSyncConflictChooseRemote, (dialog, which) -> downloadData(response.lastUpdatedDate))
|
||||||
|
.setNegativeButton(R.string.CloudSyncConflictChooseLocal, (dialog, which) -> uploadData(modified))
|
||||||
|
.show();
|
||||||
|
}).tag(CLOUD_SYNC_TAG));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Prefs.getCloudLocalLastSentModified() != modified) {
|
||||||
|
// Modified only on client
|
||||||
|
uploadData(modified);
|
||||||
|
} else {
|
||||||
|
// Not modified on server and on client
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException(Exception e) {
|
||||||
|
Log.e(TAG, "Failed to get sync state", e);
|
||||||
|
isSyncInProgress = false;
|
||||||
|
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.ERROR, R.string.CloudSyncError));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void uploadData(long modified) {
|
||||||
|
IOUtils.IO_POOL.submit(() -> {
|
||||||
|
try {
|
||||||
|
File f = SliceBeam.getConfigFile();
|
||||||
|
FileInputStream fis = new FileInputStream(f);
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[10240];
|
||||||
|
int c;
|
||||||
|
while ((c = fis.read(buffer)) != -1) {
|
||||||
|
bos.write(buffer, 0, c);
|
||||||
|
}
|
||||||
|
bos.close();
|
||||||
|
fis.close();
|
||||||
|
|
||||||
|
CloudAPI.INSTANCE.syncUpload(Base64.encodeToString(bos.toByteArray(), Base64.NO_WRAP), "application/ini", new APICallback<CloudAPI.SyncState>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(CloudAPI.SyncState response) {
|
||||||
|
isSyncInProgress = false;
|
||||||
|
if (Prefs.getCloudLocalLastModified() != modified) { // Re-send otherwise
|
||||||
|
syncData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Prefs.setCloudRemoteLastModified(response.lastUpdatedDate);
|
||||||
|
Prefs.setCloudLocalLastSentModified(modified);
|
||||||
|
Prefs.setCloudLastSync(SliceBeam.TRUE_TIME.now().getTime());
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CLOUD_SYNC_TAG));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.CloudSyncSuccess));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to read 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));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new CloudSyncFinishedEvent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void notifyDataChanged() {
|
||||||
|
long now = SliceBeam.TRUE_TIME.now().getTime();
|
||||||
|
Prefs.setCloudLocalLastModified(now);
|
||||||
|
if (!isSyncAvailable() || !Prefs.isCloudProfileSyncEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (now - Prefs.getCloudLastSync() > MIN_SYNC_DELTA) {
|
||||||
|
syncData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
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.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudAPI;
|
||||||
|
import ru.ytkab0bp.slicebeam.cloud.CloudController;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
|
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();
|
||||||
|
} else {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(CloudController.CLOUD_SYNC_TAG));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
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(16);
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(16);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -204,10 +204,13 @@ public abstract class UnfoldMenu {
|
|||||||
}
|
}
|
||||||
fragment.getGlView().invalidate();
|
fragment.getGlView().invalidate();
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.addEndListener((animation, canceled, value, velocity) -> onShown());
|
||||||
spring.start();
|
spring.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onShown() {}
|
||||||
|
|
||||||
public void relayout() {
|
public void relayout() {
|
||||||
FrameLayout into = containerLayout;
|
FrameLayout into = containerLayout;
|
||||||
boolean portrait = into.getWidth() < into.getHeight();
|
boolean portrait = into.getWidth() < into.getHeight();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
package ru.ytkab0bp.slicebeam.components.bed_menu;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.dynamicanimation.animation.FloatValueHolder;
|
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||||
@@ -10,6 +11,7 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ru.ytkab0bp.slicebeam.R;
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.components.BeamAlertDialogBuilder;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
import ru.ytkab0bp.slicebeam.recycler.SpaceItem;
|
||||||
import ru.ytkab0bp.slicebeam.render.Camera;
|
import ru.ytkab0bp.slicebeam.render.Camera;
|
||||||
@@ -122,7 +124,20 @@ public class CameraMenu extends ListBedMenu {
|
|||||||
animateTo(toOrigin, toPosition);
|
animateTo(toOrigin, toPosition);
|
||||||
}),
|
}),
|
||||||
new SpaceItem(portrait ? ViewUtils.dp(8) : 0, portrait ? 0 : ViewUtils.dp(8)),
|
new SpaceItem(portrait ? ViewUtils.dp(8) : 0, portrait ? 0 : ViewUtils.dp(8)),
|
||||||
new BedMenuItem(R.string.MenuCameraEnableRotation, R.drawable.sync_outline_28).setCheckable((buttonView, isChecked) -> Prefs.setRotationEnabled(isChecked), Prefs.isRotationEnabled()),
|
new BedMenuItem(R.string.MenuCameraControlMode, R.drawable.rectangle_hand_point_up_28).onClick(v -> {
|
||||||
|
Context ctx = v.getContext();
|
||||||
|
new BeamAlertDialogBuilder(v.getContext())
|
||||||
|
.setTitle(R.string.MenuCameraControlModeFull)
|
||||||
|
.setSingleChoiceItems(new CharSequence[] {
|
||||||
|
ctx.getString(R.string.MenuCameraControlModeOne),
|
||||||
|
ctx.getString(R.string.MenuCameraControlModeTwo),
|
||||||
|
ctx.getString(R.string.MenuCameraControlModeThree)
|
||||||
|
}, Prefs.getCameraControlMode(), (dialog, which) -> {
|
||||||
|
Prefs.setCameraControlMode(which);
|
||||||
|
dialog.dismiss();
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}),
|
||||||
new BedMenuItem(R.string.MenuCameraOrtho, R.drawable.image_format_32).setCheckable((buttonView, isChecked) -> {
|
new BedMenuItem(R.string.MenuCameraOrtho, R.drawable.image_format_32).setCheckable((buttonView, isChecked) -> {
|
||||||
Prefs.setOrthoProjectionEnabled(isChecked);
|
Prefs.setOrthoProjectionEnabled(isChecked);
|
||||||
fragment.getGlView().getRenderer().updateProjection();
|
fragment.getGlView().getRenderer().updateProjection();
|
||||||
|
|||||||
@@ -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,152 @@ 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 (MainActivity.IS_GENERATING_AI_MODEL) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.WARNING, R.string.MenuFileAIGeneratorAlreadyGenerating));
|
||||||
|
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 (MainActivity.IS_GENERATING_AI_MODEL) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.WARNING, R.string.MenuFileAIGeneratorAlreadyGenerating));
|
||||||
|
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
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import ru.ytkab0bp.slicebeam.SliceBeam;
|
|||||||
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.events.FlattenModeResetEvent;
|
import ru.ytkab0bp.slicebeam.events.FlattenModeResetEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.LongClickTranslationEvent;
|
||||||
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;
|
||||||
@@ -139,7 +140,7 @@ public class OrientationMenu extends ListBedMenu {
|
|||||||
private Vec3d tempVec = new Vec3d();
|
private Vec3d tempVec = new Vec3d();
|
||||||
private int startedScrollObject;
|
private int startedScrollObject;
|
||||||
|
|
||||||
private void translateVisual(Double x, Double y, Double z) {
|
public void translateVisual(Double x, Double y, Double z) {
|
||||||
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||||
if (j == -1) return;
|
if (j == -1) return;
|
||||||
startedScrollObject = j;
|
startedScrollObject = j;
|
||||||
@@ -395,6 +396,24 @@ public class OrientationMenu extends ListBedMenu {
|
|||||||
startedScrollObject = -1;
|
startedScrollObject = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onLongClickTranslation(LongClickTranslationEvent e) {
|
||||||
|
if (e.visual) {
|
||||||
|
int j = fragment.getGlView().getRenderer().getSelectedObject();
|
||||||
|
if (j == -1) return;
|
||||||
|
|
||||||
|
Model model = fragment.getGlView().getRenderer().getModel();
|
||||||
|
model.getTranslation(j, tempVec);
|
||||||
|
|
||||||
|
xTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionXValue, tempVec.x + e.x));
|
||||||
|
yTitle.setText(formatTrackTitle(R.string.MenuOrientationPositionYValue, tempVec.y + e.y));
|
||||||
|
xTrack.setCurrentPosition((int) (tempVec.x + e.x));
|
||||||
|
yTrack.setCurrentPosition((int) (tempVec.y + e.y));
|
||||||
|
} else {
|
||||||
|
setSelectionValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler(runOnMainThread = true)
|
@EventHandler(runOnMainThread = true)
|
||||||
public void onSelectedObjectChanged(SelectedObjectChangedEvent e) {
|
public void onSelectedObjectChanged(SelectedObjectChangedEvent e) {
|
||||||
stopScroll();
|
stopScroll();
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import cz.msebera.android.httpclient.Header;
|
import cz.msebera.android.httpclient.Header;
|
||||||
import cz.msebera.android.httpclient.entity.ContentType;
|
import cz.msebera.android.httpclient.entity.ContentType;
|
||||||
@@ -52,6 +53,7 @@ import ru.ytkab0bp.slicebeam.SliceBeam;
|
|||||||
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.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
import ru.ytkab0bp.slicebeam.fragment.BedFragment;
|
||||||
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
import ru.ytkab0bp.slicebeam.recycler.SimpleRecyclerItem;
|
||||||
@@ -64,13 +66,14 @@ import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
|||||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||||
import ru.ytkab0bp.slicebeam.view.PositionScrollView;
|
import ru.ytkab0bp.slicebeam.view.PositionScrollView;
|
||||||
import ru.ytkab0bp.slicebeam.view.SegmentsView;
|
import ru.ytkab0bp.slicebeam.view.SegmentsView;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
||||||
|
|
||||||
public class SliceMenu extends ListBedMenu {
|
public class SliceMenu extends ListBedMenu {
|
||||||
private AsyncHttpClient client = new AsyncHttpClient();
|
private AsyncHttpClient client = new AsyncHttpClient();
|
||||||
|
|
||||||
{
|
{
|
||||||
client.setLoggingEnabled(true);
|
client.setLoggingEnabled(true);
|
||||||
client.setMaxRetriesAndTimeout(0, 5000);
|
client.setMaxRetriesAndTimeout(0, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static List<String> SUPPORTED_SEND = Collections.singletonList("octoprint");
|
private final static List<String> SUPPORTED_SEND = Collections.singletonList("octoprint");
|
||||||
@@ -132,7 +135,8 @@ public class SliceMenu extends ListBedMenu {
|
|||||||
if (!host.startsWith("http://")) {
|
if (!host.startsWith("http://")) {
|
||||||
host = "http://" + host;
|
host = "http://" + host;
|
||||||
}
|
}
|
||||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(R.string.MenuSliceSendToPrinterStarted));
|
String tag = UUID.randomUUID().toString();
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(SnackbarsLayout.Type.LOADING, R.string.MenuSliceSendToPrinterLoading).tag(tag));
|
||||||
Header[] headers = TextUtils.isEmpty(apiKey) ? new Header[0] : new Header[] {new BasicHeader("X-Api-Key", apiKey)};
|
Header[] headers = TextUtils.isEmpty(apiKey) ? new Header[0] : new Header[] {new BasicHeader("X-Api-Key", apiKey)};
|
||||||
RequestParams params = new RequestParams();
|
RequestParams params = new RequestParams();
|
||||||
try {
|
try {
|
||||||
@@ -151,7 +155,8 @@ public class SliceMenu extends ListBedMenu {
|
|||||||
if (!obj.has("action") && !obj.has("files")) {
|
if (!obj.has("action") && !obj.has("files")) {
|
||||||
throw new JSONException(obj.toString());
|
throw new JSONException(obj.toString());
|
||||||
}
|
}
|
||||||
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(print ? R.string.MenuSliceSendToPrinterPrintStarted : R.string.MenuSliceSendToPrinterOK));
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedSnackbarEvent(print ? SnackbarsLayout.Type.INFO : SnackbarsLayout.Type.DONE, print ? R.string.MenuSliceSendToPrinterPrintStarted : R.string.MenuSliceSendToPrinterOK));
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
onFailure(statusCode, headers, responseBody, e);
|
onFailure(statusCode, headers, responseBody, e);
|
||||||
}
|
}
|
||||||
@@ -159,6 +164,7 @@ public class SliceMenu extends ListBedMenu {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
|
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new NeedDismissSnackbarEvent(tag));
|
||||||
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(fragment.getContext())
|
ViewUtils.postOnMainThread(() -> new BeamAlertDialogBuilder(fragment.getContext())
|
||||||
.setTitle(R.string.MenuSliceSendToPrinterFailed)
|
.setTitle(R.string.MenuSliceSendToPrinterFailed)
|
||||||
.setMessage(error.toString())
|
.setMessage(error.toString())
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import androidx.core.content.ContextCompat;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import ru.ytkab0bp.eventbus.EventHandler;
|
import ru.ytkab0bp.eventbus.EventHandler;
|
||||||
import ru.ytkab0bp.slicebeam.R;
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
@@ -152,18 +153,21 @@ public class TransformMenu extends ListBedMenu {
|
|||||||
if (j == -1) return;
|
if (j == -1) return;
|
||||||
startedScrollObject = j;
|
startedScrollObject = j;
|
||||||
|
|
||||||
|
Model model = fragment.getGlView().getRenderer().getModel();
|
||||||
|
model.getScale(j, tempVec);
|
||||||
|
double scaleX = tempVec.x, scaleY = tempVec.y, scaleZ = tempVec.z;
|
||||||
|
model.getBoundingBoxExact(j, tempVec, tempVec2);
|
||||||
|
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, x * 100));
|
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, x * 100, (tempVec2.x - tempVec.x) / scaleX * x));
|
||||||
}
|
}
|
||||||
if (y != null) {
|
if (y != null) {
|
||||||
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, y * 100));
|
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, y * 100, (tempVec2.y - tempVec.y) / scaleY * y));
|
||||||
}
|
}
|
||||||
if (z != null) {
|
if (z != null) {
|
||||||
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, z * 100));
|
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, z * 100, (tempVec2.z - tempVec.z) / scaleZ * z));
|
||||||
}
|
}
|
||||||
|
|
||||||
Model model = fragment.getGlView().getRenderer().getModel();
|
|
||||||
|
|
||||||
model.getRotation(j, tempVec);
|
model.getRotation(j, tempVec);
|
||||||
DoubleMatrix.setIdentityM(tempMatrix, 0);
|
DoubleMatrix.setIdentityM(tempMatrix, 0);
|
||||||
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.x), 1, 0, 0);
|
DoubleMatrix.rotateM(tempMatrix, 0, Math.toDegrees(tempVec.x), 1, 0, 0);
|
||||||
@@ -197,19 +201,22 @@ public class TransformMenu extends ListBedMenu {
|
|||||||
|
|
||||||
fragment.getGlView().queueEvent(() -> {
|
fragment.getGlView().queueEvent(() -> {
|
||||||
Model model = fragment.getGlView().getRenderer().getModel();
|
Model model = fragment.getGlView().getRenderer().getModel();
|
||||||
|
model.getScale(j, tempVec);
|
||||||
|
double scaleX = tempVec.x, scaleY = tempVec.y, scaleZ = tempVec.z;
|
||||||
|
model.getBoundingBoxExact(j, tempVec, tempVec2);
|
||||||
|
|
||||||
double dx = 1f, dy = 1f, dz = 1f;
|
double dx = 1f, dy = 1f, dz = 1f;
|
||||||
if (x != null) {
|
if (x != null) {
|
||||||
dx = x;
|
dx = x;
|
||||||
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, x * 100));
|
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, x * 100, (tempVec2.x - tempVec.x) / scaleX * x));
|
||||||
}
|
}
|
||||||
if (y != null) {
|
if (y != null) {
|
||||||
dy = y;
|
dy = y;
|
||||||
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, y * 100));
|
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, y * 100, (tempVec2.y - tempVec.y) / scaleY * y));
|
||||||
}
|
}
|
||||||
if (z != null) {
|
if (z != null) {
|
||||||
dz = z;
|
dz = z;
|
||||||
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, z * 100));
|
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, z * 100, (tempVec2.z - tempVec.z) / scaleZ * z));
|
||||||
}
|
}
|
||||||
|
|
||||||
model.getRotation(j, tempVec);
|
model.getRotation(j, tempVec);
|
||||||
@@ -233,9 +240,7 @@ public class TransformMenu extends ListBedMenu {
|
|||||||
|
|
||||||
model.getScale(j, tempVec);
|
model.getScale(j, tempVec);
|
||||||
model.scale(j, dx, dy, dz);
|
model.scale(j, dx, dy, dz);
|
||||||
|
model.ensureOnBed(j);
|
||||||
model.getBoundingBoxExact(j, tempVec, tempVec2);
|
|
||||||
model.translate(j, 0, 0, -tempVec.z);
|
|
||||||
|
|
||||||
fragment.getGlView().getRenderer().invalidateSelectionObject();
|
fragment.getGlView().getRenderer().invalidateSelectionObject();
|
||||||
fragment.getGlView().getRenderer().setSelectionScale(1, 1, 1);
|
fragment.getGlView().getRenderer().setSelectionScale(1, 1, 1);
|
||||||
@@ -245,8 +250,8 @@ public class TransformMenu extends ListBedMenu {
|
|||||||
fragment.getGlView().requestRender();
|
fragment.getGlView().requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CharSequence formatTrackTitle(int res, double value) {
|
private CharSequence formatTrackTitle(int res, double value, double mm) {
|
||||||
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(SliceBeam.INSTANCE.getString(res, value));
|
SpannableStringBuilder sb = SpannableStringBuilder.valueOf(SliceBeam.INSTANCE.getString(res, value, mm));
|
||||||
sb.append(" d");
|
sb.append(" d");
|
||||||
int size = ViewUtils.dp(14);
|
int size = ViewUtils.dp(14);
|
||||||
Drawable dr = ContextCompat.getDrawable(SliceBeam.INSTANCE, R.drawable.edit_outline_28);
|
Drawable dr = ContextCompat.getDrawable(SliceBeam.INSTANCE, R.drawable.edit_outline_28);
|
||||||
@@ -263,33 +268,108 @@ public class TransformMenu extends ListBedMenu {
|
|||||||
Model model = fragment.getGlView().getRenderer().getModel();
|
Model model = fragment.getGlView().getRenderer().getModel();
|
||||||
model.getScale(j, tempVec);
|
model.getScale(j, tempVec);
|
||||||
|
|
||||||
double current;
|
Context ctx = getView().getContext();
|
||||||
|
LinearLayout ll = new LinearLayout(ctx);
|
||||||
|
ll.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
AtomicBoolean inputInMM = new AtomicBoolean(Prefs.isScaleInputInMM());
|
||||||
|
|
||||||
|
double cur;
|
||||||
if (x) {
|
if (x) {
|
||||||
current = tempVec.x * 100;
|
cur = tempVec.x * 100;
|
||||||
} else if (y) {
|
} else if (y) {
|
||||||
current = tempVec.y * 100;
|
cur = tempVec.y * 100;
|
||||||
} else {
|
} else {
|
||||||
current = tempVec.z * 100;
|
cur = tempVec.z * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context ctx = getView().getContext();
|
model.getScale(j, tempVec);
|
||||||
FrameLayout fl = new FrameLayout(ctx);
|
double scaleX = tempVec.x, scaleY = tempVec.y, scaleZ = tempVec.z;
|
||||||
|
model.getBoundingBoxExact(j, tempVec, tempVec2);
|
||||||
|
if (inputInMM.get()) {
|
||||||
|
cur /= 100.0;
|
||||||
|
|
||||||
|
if (x) {
|
||||||
|
cur *= (tempVec2.x - tempVec.x) / scaleX;
|
||||||
|
} else if (y) {
|
||||||
|
cur *= (tempVec2.y - tempVec.y) / scaleY;
|
||||||
|
} else {
|
||||||
|
cur *= (tempVec2.z - tempVec.z) / scaleZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double current = cur;
|
||||||
|
|
||||||
EditText text = new EditText(ctx);
|
EditText text = new EditText(ctx);
|
||||||
text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
text.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||||
text.setText(String.format(Locale.ROOT, "%.2f", current));
|
text.setText(String.format(Locale.ROOT, "%.2f", current));
|
||||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||||
fl.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
ll.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
PreferenceSwitchItem.SwitchPreferenceHolderView holderView = new PreferenceSwitchItem.SwitchPreferenceHolderView(ctx);
|
||||||
|
holderView.title.setText(R.string.MenuTransformScaleMM);
|
||||||
|
holderView.title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
holderView.matSwitch.setChecked(inputInMM.get());
|
||||||
|
holderView.subtitle.setVisibility(View.GONE);
|
||||||
|
holderView.setOnClickListener(v -> {
|
||||||
|
inputInMM.set(!inputInMM.get());
|
||||||
|
|
||||||
|
double value;
|
||||||
|
try {
|
||||||
|
value = Double.parseDouble(text.getText().toString());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
value = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputInMM.get()) {
|
||||||
|
value /= 100.0;
|
||||||
|
|
||||||
|
if (x) {
|
||||||
|
value *= (tempVec2.x - tempVec.x) / scaleX;
|
||||||
|
} else if (y) {
|
||||||
|
value *= (tempVec2.y - tempVec.y) / scaleY;
|
||||||
|
} else {
|
||||||
|
value *= (tempVec2.z - tempVec.z) / scaleZ;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (x) {
|
||||||
|
value /= (tempVec2.x - tempVec.x) / scaleX;
|
||||||
|
} else if (y) {
|
||||||
|
value /= (tempVec2.y - tempVec.y) / scaleY;
|
||||||
|
} else {
|
||||||
|
value /= (tempVec2.z - tempVec.z) / scaleZ;
|
||||||
|
}
|
||||||
|
value *= 100.0;
|
||||||
|
}
|
||||||
|
text.setText(String.format(Locale.ROOT, "%.2f", value));
|
||||||
|
holderView.matSwitch.setChecked(inputInMM.get());
|
||||||
|
Prefs.setScaleInputInMM(inputInMM.get());
|
||||||
|
});
|
||||||
|
ll.addView(holderView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
leftMargin = rightMargin = ViewUtils.dp(8);
|
||||||
|
}});
|
||||||
|
|
||||||
new BeamAlertDialogBuilder(ctx)
|
new BeamAlertDialogBuilder(ctx)
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setView(fl)
|
.setView(ll)
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
double value;
|
double value;
|
||||||
try {
|
try {
|
||||||
value = Double.parseDouble(text.getText().toString()) / 100.0;
|
value = Double.parseDouble(text.getText().toString());
|
||||||
|
|
||||||
|
if (inputInMM.get()) {
|
||||||
|
if (x) {
|
||||||
|
value /= (tempVec2.x - tempVec.x) / scaleX;
|
||||||
|
} else if (y) {
|
||||||
|
value /= (tempVec2.y - tempVec.y) / scaleY;
|
||||||
|
} else {
|
||||||
|
value /= (tempVec2.z - tempVec.z) / scaleZ;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value /= 100.0;
|
||||||
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
value = current;
|
value = current;
|
||||||
}
|
}
|
||||||
@@ -449,14 +529,16 @@ public class TransformMenu extends ListBedMenu {
|
|||||||
|
|
||||||
Model model = fragment.getGlView().getRenderer().getModel();
|
Model model = fragment.getGlView().getRenderer().getModel();
|
||||||
model.getScale(j, tempVec);
|
model.getScale(j, tempVec);
|
||||||
|
double scaleX = tempVec.x, scaleY = tempVec.y, scaleZ = tempVec.z;
|
||||||
|
|
||||||
xTrack.setCurrentPosition((int) Math.round(tempVec.x * 100));
|
xTrack.setCurrentPosition((int) Math.round(scaleX * 100));
|
||||||
yTrack.setCurrentPosition((int) Math.round(tempVec.y * 100));
|
yTrack.setCurrentPosition((int) Math.round(scaleY * 100));
|
||||||
zTrack.setCurrentPosition((int) Math.round(tempVec.z * 100));
|
zTrack.setCurrentPosition((int) Math.round(scaleZ * 100));
|
||||||
|
|
||||||
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, tempVec.x * 100));
|
model.getBoundingBoxExact(j, tempVec, tempVec2);
|
||||||
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, tempVec.y * 100));
|
xTitle.setText(formatTrackTitle(R.string.MenuTransformScaleXValue, scaleX * 100, (tempVec2.x - tempVec.x)));
|
||||||
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, tempVec.z * 100));
|
yTitle.setText(formatTrackTitle(R.string.MenuTransformScaleYValue, scaleY * 100, (tempVec2.y - tempVec.y)));
|
||||||
|
zTitle.setText(formatTrackTitle(R.string.MenuTransformScaleZValue, scaleZ * 100, (tempVec2.z - tempVec.z)));
|
||||||
|
|
||||||
xTrack.updateSyncDeltas();
|
xTrack.updateSyncDeltas();
|
||||||
yTrack.updateSyncDeltas();
|
yTrack.updateSyncDeltas();
|
||||||
|
|||||||
@@ -34,10 +34,25 @@ public class ConfigObject implements ProfileListFragment.ProfileListItem {
|
|||||||
this.values.putAll(from.values);
|
this.values.putAll(from.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: suitable only from "printer" config
|
||||||
|
*/
|
||||||
|
public int getExtruderCount() {
|
||||||
|
return get("nozzle_diameter") != null ? get("nozzle_diameter").replaceAll("[^.]+", "").length() : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean has(String key) {
|
||||||
|
return values.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
public String get(String key) {
|
public String get(String key) {
|
||||||
return values.get(key);
|
return values.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void remove(String key) {
|
||||||
|
values.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
public void put(String key, String value) {
|
public void put(String key, String value) {
|
||||||
values.put(key, value);
|
values.put(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,6 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class CloudSyncFinishedEvent {}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class CloudUserInfoUpdatedEvent {
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class LongClickTranslationEvent {
|
||||||
|
public final double x;
|
||||||
|
public final double y;
|
||||||
|
public final boolean visual;
|
||||||
|
|
||||||
|
public LongClickTranslationEvent(double x, double y, boolean visual) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.visual = visual;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class NeedDismissAIGeneratorMenu {}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
|
|
||||||
|
@Event
|
||||||
|
public class NeedDismissSnackbarEvent {
|
||||||
|
public final String tag;
|
||||||
|
|
||||||
|
public NeedDismissSnackbarEvent(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,24 @@
|
|||||||
package ru.ytkab0bp.slicebeam.events;
|
package ru.ytkab0bp.slicebeam.events;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import ru.ytkab0bp.eventbus.Event;
|
import ru.ytkab0bp.eventbus.Event;
|
||||||
import ru.ytkab0bp.slicebeam.SliceBeam;
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
||||||
|
|
||||||
@Event
|
@Event
|
||||||
public class NeedSnackbarEvent {
|
public class NeedSnackbarEvent {
|
||||||
public final CharSequence title;
|
public final CharSequence title;
|
||||||
|
public SnackbarsLayout.Type type = SnackbarsLayout.Type.DONE;
|
||||||
|
public String tag;
|
||||||
|
|
||||||
|
public CharSequence buttonTitle;
|
||||||
|
public View.OnClickListener buttonClick;
|
||||||
|
|
||||||
|
public NeedSnackbarEvent(SnackbarsLayout.Type type, CharSequence title) {
|
||||||
|
this.type = type;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
public NeedSnackbarEvent(CharSequence title) {
|
public NeedSnackbarEvent(CharSequence title) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -14,4 +27,20 @@ public class NeedSnackbarEvent {
|
|||||||
public NeedSnackbarEvent(int title, Object... args) {
|
public NeedSnackbarEvent(int title, Object... args) {
|
||||||
this.title = SliceBeam.INSTANCE.getString(title, args);
|
this.title = SliceBeam.INSTANCE.getString(title, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NeedSnackbarEvent(SnackbarsLayout.Type type, int title, Object... args) {
|
||||||
|
this.type = type;
|
||||||
|
this.title = SliceBeam.INSTANCE.getString(title, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeedSnackbarEvent tag(String tag) {
|
||||||
|
this.tag = tag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeedSnackbarEvent button(int title, View.OnClickListener click) {
|
||||||
|
this.buttonTitle = SliceBeam.INSTANCE.getString(title);
|
||||||
|
this.buttonClick = click;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
package ru.ytkab0bp.slicebeam.fragment;
|
package ru.ytkab0bp.slicebeam.fragment;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
||||||
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 com.google.android.material.navigation.NavigationBarView;
|
import com.google.android.material.navigation.NavigationBarView;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -38,6 +45,7 @@ import ru.ytkab0bp.slicebeam.components.bed_menu.SliceMenu;
|
|||||||
import ru.ytkab0bp.slicebeam.components.bed_menu.TransformMenu;
|
import ru.ytkab0bp.slicebeam.components.bed_menu.TransformMenu;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
import ru.ytkab0bp.slicebeam.events.FlattenModeResetEvent;
|
import ru.ytkab0bp.slicebeam.events.FlattenModeResetEvent;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.NeedDismissSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
import ru.ytkab0bp.slicebeam.events.NeedSnackbarEvent;
|
||||||
import ru.ytkab0bp.slicebeam.events.SlicingProgressEvent;
|
import ru.ytkab0bp.slicebeam.events.SlicingProgressEvent;
|
||||||
import ru.ytkab0bp.slicebeam.navigation.Fragment;
|
import ru.ytkab0bp.slicebeam.navigation.Fragment;
|
||||||
@@ -51,6 +59,7 @@ import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
|||||||
import ru.ytkab0bp.slicebeam.view.BedSwipeDownLayout;
|
import ru.ytkab0bp.slicebeam.view.BedSwipeDownLayout;
|
||||||
import ru.ytkab0bp.slicebeam.view.DividerView;
|
import ru.ytkab0bp.slicebeam.view.DividerView;
|
||||||
import ru.ytkab0bp.slicebeam.view.GLView;
|
import ru.ytkab0bp.slicebeam.view.GLView;
|
||||||
|
import ru.ytkab0bp.slicebeam.view.SnackbarsLayout;
|
||||||
import ru.ytkab0bp.slicebeam.view.ThemeBottomNavigationView;
|
import ru.ytkab0bp.slicebeam.view.ThemeBottomNavigationView;
|
||||||
import ru.ytkab0bp.slicebeam.view.ThemeRailNavigationView;
|
import ru.ytkab0bp.slicebeam.view.ThemeRailNavigationView;
|
||||||
|
|
||||||
@@ -59,7 +68,7 @@ public class BedFragment extends Fragment {
|
|||||||
private final static int MENU_SIZE_DP = 80;
|
private final static int MENU_SIZE_DP = 80;
|
||||||
|
|
||||||
private FrameLayout overlayLayout;
|
private FrameLayout overlayLayout;
|
||||||
private CoordinatorLayout snackbarsLayout;
|
private SnackbarsLayout snackbarsLayout;
|
||||||
private GLView glView;
|
private GLView glView;
|
||||||
private NavigationBarView navigationView;
|
private NavigationBarView navigationView;
|
||||||
|
|
||||||
@@ -104,7 +113,12 @@ 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 ImageView webViewErrIcon;
|
||||||
|
private TextView webViewErrDescription;
|
||||||
|
private ProgressBar webViewProgressBar;
|
||||||
|
|
||||||
private static String tempFileName;
|
private static String tempFileName;
|
||||||
private static File tempExportingFile;
|
private static File tempExportingFile;
|
||||||
@@ -126,7 +140,21 @@ public class BedFragment extends Fragment {
|
|||||||
|
|
||||||
@EventHandler(runOnMainThread = true)
|
@EventHandler(runOnMainThread = true)
|
||||||
public void onNeedSnackbar(NeedSnackbarEvent e) {
|
public void onNeedSnackbar(NeedSnackbarEvent e) {
|
||||||
Snackbar.make(snackbarsLayout, e.title, Snackbar.LENGTH_SHORT).show();
|
SnackbarsLayout.Snackbar s = new SnackbarsLayout.Snackbar(e.type, e.title);
|
||||||
|
if (e.tag != null) {
|
||||||
|
s.tag(e.tag);
|
||||||
|
}
|
||||||
|
if (e.buttonTitle != null) {
|
||||||
|
s.lifetime = 0;
|
||||||
|
s.buttonTitle = e.buttonTitle;
|
||||||
|
s.buttonClick = e.buttonClick;
|
||||||
|
}
|
||||||
|
snackbarsLayout.show(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(runOnMainThread = true)
|
||||||
|
public void onDismissSnackbar(NeedDismissSnackbarEvent e) {
|
||||||
|
snackbarsLayout.dismiss(e.tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showUnfoldMenu(UnfoldMenu menu, View from) {
|
public void showUnfoldMenu(UnfoldMenu menu, View from) {
|
||||||
@@ -204,7 +232,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");
|
||||||
@@ -219,7 +247,16 @@ public class BedFragment extends Fragment {
|
|||||||
if (!host.startsWith("http://")) {
|
if (!host.startsWith("http://")) {
|
||||||
host = "http://" + host;
|
host = "http://" + host;
|
||||||
}
|
}
|
||||||
|
webViewProgressBar.animate().alpha(1).setDuration(150).start();
|
||||||
|
panelWebView.setAlpha(0f);
|
||||||
|
hasWebError = false;
|
||||||
panelWebView.loadUrl(host);
|
panelWebView.loadUrl(host);
|
||||||
|
panelWebViewError.animate().alpha(0).setDuration(150).setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
panelWebViewError.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,8 +298,58 @@ public class BedFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
swipeDownLayout = new BedSwipeDownLayout(ctx);
|
swipeDownLayout = new BedSwipeDownLayout(ctx);
|
||||||
panelWebView = new WebView(ctx);
|
FrameLayout wfl = new FrameLayout(ctx);
|
||||||
panelWebView.getSettings().setJavaScriptEnabled(true);
|
try {
|
||||||
|
panelWebView = new WebView(ctx);
|
||||||
|
panelWebView.getSettings().setJavaScriptEnabled(true);
|
||||||
|
panelWebView.setWebViewClient(new WebViewClient() {
|
||||||
|
@Override
|
||||||
|
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
|
||||||
|
hasWebError = true;
|
||||||
|
webViewErrDescription.setText(description);
|
||||||
|
panelWebViewError.setVisibility(View.VISIBLE);
|
||||||
|
panelWebViewError.setAlpha(0f);
|
||||||
|
panelWebViewError.animate().alpha(1).setDuration(150).setListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
panelWebView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
webViewProgressBar.animate().alpha(0).setDuration(150).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageFinished(WebView view, String url) {
|
||||||
|
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);
|
||||||
@@ -271,10 +358,10 @@ public class BedFragment extends Fragment {
|
|||||||
|
|
||||||
inner.addView(glView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
inner.addView(glView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||||
swipeDownLayout.addView(inner);
|
swipeDownLayout.addView(inner);
|
||||||
swipeDownLayout.addView(panelWebView);
|
swipeDownLayout.addView(wfl);
|
||||||
} else {
|
} else {
|
||||||
swipeDownLayout.addView(glView);
|
swipeDownLayout.addView(glView);
|
||||||
swipeDownLayout.addView(panelWebView);
|
swipeDownLayout.addView(wfl);
|
||||||
ll.addView(swipeDownLayout, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
ll.addView(swipeDownLayout, new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,7 +467,7 @@ public class BedFragment extends Fragment {
|
|||||||
} else {
|
} else {
|
||||||
overlayLayout.addView(contentView = ll);
|
overlayLayout.addView(contentView = ll);
|
||||||
}
|
}
|
||||||
overlayLayout.addView(snackbarsLayout = new CoordinatorLayout(ctx), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) {{
|
overlayLayout.addView(snackbarsLayout = new SnackbarsLayout(ctx), new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) {{
|
||||||
if (portrait) {
|
if (portrait) {
|
||||||
bottomMargin = ViewUtils.dp(80 * 2);
|
bottomMargin = ViewUtils.dp(80 * 2);
|
||||||
} else {
|
} else {
|
||||||
@@ -390,7 +477,7 @@ public class BedFragment extends Fragment {
|
|||||||
return overlayLayout;
|
return overlayLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CoordinatorLayout getSnackbarsLayout() {
|
public SnackbarsLayout getSnackbarsLayout() {
|
||||||
return snackbarsLayout;
|
return snackbarsLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +585,6 @@ public class BedFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
glView.getRenderer().setModel(model = m);
|
|
||||||
glView.queueEvent(new Runnable() {
|
glView.queueEvent(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -507,6 +593,8 @@ public class BedFragment extends Fragment {
|
|||||||
ViewUtils.postOnMainThread(()-> glView.queueEvent(this));
|
ViewUtils.postOnMainThread(()-> glView.queueEvent(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
glView.getRenderer().setModel(model = m);
|
||||||
|
|
||||||
Vec3d center = bed.getVolumeMin().center(bed.getVolumeMax());
|
Vec3d center = bed.getVolumeMin().center(bed.getVolumeMax());
|
||||||
Vec3d objMin = new Vec3d(), objMax = new Vec3d();
|
Vec3d objMin = new Vec3d(), objMax = new Vec3d();
|
||||||
Vec3d objTranslate = new Vec3d();
|
Vec3d objTranslate = new Vec3d();
|
||||||
@@ -526,6 +614,11 @@ public class BedFragment extends Fragment {
|
|||||||
public void onApplyTheme() {
|
public void onApplyTheme() {
|
||||||
super.onApplyTheme();
|
super.onApplyTheme();
|
||||||
|
|
||||||
|
if (panelWebView != null) {
|
||||||
|
webViewErrIcon.setImageTintList(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) {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class PrinterConfigFragment extends ProfileListFragment {
|
|||||||
// TODO: m_supports_min_feedrates? <= marlin/marlin legacy
|
// TODO: m_supports_min_feedrates? <= marlin/marlin legacy
|
||||||
));
|
));
|
||||||
|
|
||||||
int count = currentConfig.get("nozzle_diameter") != null ? currentConfig.get("nozzle_diameter").replaceAll("[^.]+", "").length() : 1;
|
int count = currentConfig.getExtruderCount();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int j = count == 1 ? -1 : i;
|
int j = count == 1 ? -1 : i;
|
||||||
list.addAll(Arrays.asList(
|
list.addAll(Arrays.asList(
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -75,7 +84,7 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
protected ImageView resetButton;
|
protected ImageView resetButton;
|
||||||
protected BeamButton saveButton;
|
protected BeamButton saveButton;
|
||||||
|
|
||||||
protected boolean changedConfig;
|
protected ConfigObject diffObject = new ConfigObject();
|
||||||
|
|
||||||
private List<OptionWrapper> currentList = Collections.emptyList();
|
private List<OptionWrapper> currentList = Collections.emptyList();
|
||||||
private SparseArray<List<OptionWrapper>> categoryElements = new SparseArray<>();
|
private SparseArray<List<OptionWrapper>> categoryElements = new SparseArray<>();
|
||||||
@@ -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) {
|
||||||
@@ -332,21 +388,45 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
saveButton.setPadding(ViewUtils.dp(21), ViewUtils.dp(12), ViewUtils.dp(21), ViewUtils.dp(12));
|
saveButton.setPadding(ViewUtils.dp(21), ViewUtils.dp(12), ViewUtils.dp(21), ViewUtils.dp(12));
|
||||||
|
|
||||||
saveButton.setOnClickListener(v -> {
|
saveButton.setOnClickListener(v -> {
|
||||||
FrameLayout fl = new FrameLayout(ctx);
|
LinearLayout linear = new LinearLayout(ctx);
|
||||||
|
linear.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Map.Entry<String, String> en : diffObject.values.entrySet()) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
ConfigOptionDef def = PrintConfigDef.getInstance().options.get(en.getKey());
|
||||||
|
sb.append(Slic3rLocalization.getString(def.getFullLabel())).append(" - ").append(opt(def, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
ScrollView scrollView = new ScrollView(ctx);
|
||||||
|
TextView subtitle = new TextView(ctx);
|
||||||
|
subtitle.setTextAppearance(ctx, com.google.android.material.R.style.MaterialAlertDialog_Material3_Body_Text);
|
||||||
|
subtitle.setTextColor(ThemesRepo.getColor(android.R.attr.textColorSecondary));
|
||||||
|
subtitle.setText(sb.toString());
|
||||||
|
subtitle.setPadding(ViewUtils.dp(24), ViewUtils.dp(12), ViewUtils.dp(24), ViewUtils.dp(12));
|
||||||
|
scrollView.addView(subtitle, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||||
|
linear.addView(scrollView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
|
||||||
|
}
|
||||||
|
|
||||||
EditText text = new EditText(ctx);
|
EditText text = new EditText(ctx);
|
||||||
text.setText(getCurrentConfig().getTitle());
|
text.setText(getCurrentConfig().getTitle());
|
||||||
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
text.setTextColor(ThemesRepo.getColor(android.R.attr.textColorPrimary));
|
||||||
fl.addView(text, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
linear.addView(text, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
leftMargin = rightMargin = ViewUtils.dp(21);
|
leftMargin = rightMargin = ViewUtils.dp(21);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
AlertDialog dialog = new BeamAlertDialogBuilder(ctx)
|
AlertDialog dialog = new BeamAlertDialogBuilder(ctx)
|
||||||
.setTitle(R.string.SettingsSaveTitle)
|
.setTitle(R.string.SettingsSaveTitle)
|
||||||
// TODO: Draw settings delta
|
.setView(linear)
|
||||||
.setView(fl)
|
|
||||||
.setPositiveButton(android.R.string.ok, (d, which) -> {
|
.setPositiveButton(android.R.string.ok, (d, which) -> {
|
||||||
|
getCurrentConfig().values.putAll(diffObject.values);
|
||||||
|
diffObject.values.clear();
|
||||||
|
|
||||||
onApplyConfig(text.getText().toString());
|
onApplyConfig(text.getText().toString());
|
||||||
resetButton.animate().alpha(0.6f).start();
|
resetButton.animate().alpha(0.4f).setDuration(150).start();
|
||||||
resetButton.setClickable(false);
|
resetButton.setClickable(false);
|
||||||
onUpdateConfigItems();
|
onUpdateConfigItems();
|
||||||
})
|
})
|
||||||
@@ -395,13 +475,14 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
resetButton.setImageResource(R.drawable.refresh_outline_28);
|
resetButton.setImageResource(R.drawable.refresh_outline_28);
|
||||||
resetButton.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorPrimary)));
|
resetButton.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(android.R.attr.textColorPrimary)));
|
||||||
resetButton.setScaleX(-1f);
|
resetButton.setScaleX(-1f);
|
||||||
resetButton.setAlpha(0.6f);
|
resetButton.setAlpha(0.4f);
|
||||||
resetButton.setOnClickListener(v -> new BeamAlertDialogBuilder(ctx)
|
resetButton.setOnClickListener(v -> new BeamAlertDialogBuilder(ctx)
|
||||||
.setTitle(R.string.SettingsResetProfileTitle)
|
.setTitle(R.string.SettingsResetProfileTitle)
|
||||||
.setMessage(R.string.SettingsResetProfileDescription)
|
.setMessage(R.string.SettingsResetProfileDescription)
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
onResetConfig();
|
onResetConfig();
|
||||||
resetButton.animate().alpha(0.6f).start();
|
diffObject.values.clear();
|
||||||
|
resetButton.animate().alpha(0.4f).setDuration(150).start();
|
||||||
resetButton.setClickable(false);
|
resetButton.setClickable(false);
|
||||||
onUpdateConfigItems();
|
onUpdateConfigItems();
|
||||||
})
|
})
|
||||||
@@ -433,6 +514,7 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
SliceBeam.EVENT_BUS.unregisterListener(this);
|
SliceBeam.EVENT_BUS.unregisterListener(this);
|
||||||
|
unfolded.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@@ -443,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++;
|
||||||
@@ -474,10 +561,16 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String opt(ConfigOptionDef def, int i) {
|
private String opt(ConfigOptionDef def, int i) {
|
||||||
String v = getCurrentConfig().get(def.key);
|
return opt(def, i, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String opt(ConfigOptionDef def, int i, boolean ignoreDiff) {
|
||||||
|
String v = ignoreDiff || !diffObject.has(def.key) ? getCurrentConfig().get(def.key) : diffObject.get(def.key);
|
||||||
if (i != -1) {
|
if (i != -1) {
|
||||||
try {
|
try {
|
||||||
v = v.split(",")[i];
|
String ch = ",";
|
||||||
|
if (def.guiType == ConfigOptionDef.GUIType.COLOR) ch = ";";
|
||||||
|
v = v.split(ch)[i];
|
||||||
} catch (ArrayIndexOutOfBoundsException e) {
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||||||
Log.w("ProfileListFragment", "Failed to parse mm option", e);
|
Log.w("ProfileListFragment", "Failed to parse mm option", e);
|
||||||
}
|
}
|
||||||
@@ -487,17 +580,27 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
|
|
||||||
protected void updateConfigField(ConfigOptionDef def, int i, String value) {
|
protected void updateConfigField(ConfigOptionDef def, int i, String value) {
|
||||||
if (i != -1) {
|
if (i != -1) {
|
||||||
String[] vals = opt(def, -1).split(",");
|
String ch = ",";
|
||||||
|
if (def.guiType == ConfigOptionDef.GUIType.COLOR) ch = ";";
|
||||||
|
String[] vals = opt(def, -1, true).split(ch);
|
||||||
vals[i] = value;
|
vals[i] = value;
|
||||||
value = TextUtils.join(",", vals);
|
value = TextUtils.join(ch, vals);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Objects.equals(opt(def, i), value)) {
|
boolean wasEmpty = diffObject.values.isEmpty();
|
||||||
changedConfig = true;
|
if (Objects.equals(opt(def, i, true), value)) {
|
||||||
resetButton.animate().alpha(1).start();
|
diffObject.remove(def.key);
|
||||||
resetButton.setClickable(true);
|
} else {
|
||||||
|
diffObject.put(def.key, value);
|
||||||
|
}
|
||||||
|
boolean empty = diffObject.values.isEmpty();
|
||||||
|
if (wasEmpty && !empty) {
|
||||||
|
resetButton.animate().alpha(1.0f).setDuration(150).start();
|
||||||
|
resetButton.setClickable(true);
|
||||||
|
} else if (!wasEmpty && empty) {
|
||||||
|
resetButton.animate().alpha(0.4f).setDuration(150).start();
|
||||||
|
resetButton.setClickable(false);
|
||||||
}
|
}
|
||||||
getCurrentConfig().put(def.key, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
@@ -521,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;
|
||||||
@@ -697,6 +802,13 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ref.set(builder.show());
|
ref.set(builder.show());
|
||||||
|
}).setOnLongClickListener(v -> {
|
||||||
|
new BeamAlertDialogBuilder(getContext())
|
||||||
|
.setTitle(Slic3rLocalization.getString(def.getFullLabel()))
|
||||||
|
.setMessage(Slic3rLocalization.getString(def.tooltip))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (def.type == ConfigOptionDef.ConfigOptionType.STRING || def.type == ConfigOptionDef.ConfigOptionType.STRINGS) {
|
if (def.type == ConfigOptionDef.ConfigOptionType.STRING || def.type == ConfigOptionDef.ConfigOptionType.STRINGS) {
|
||||||
@@ -727,11 +839,23 @@ public abstract class ProfileListFragment extends Fragment {
|
|||||||
case BOOLS:
|
case BOOLS:
|
||||||
simpleItem = new PreferenceSwitchItem().setTitle(Slic3rLocalization.getString(def.label))
|
simpleItem = new PreferenceSwitchItem().setTitle(Slic3rLocalization.getString(def.label))
|
||||||
.setValueProvider(() -> "1".equals(opt(def, eIndex)))
|
.setValueProvider(() -> "1".equals(opt(def, eIndex)))
|
||||||
.setChangeListener((buttonView, isChecked) -> updateConfigField(def, eIndex, String.valueOf(isChecked ? 1 : 0)));
|
.setChangeListener((buttonView, isChecked) -> updateConfigField(def, eIndex, String.valueOf(isChecked ? 1 : 0)))
|
||||||
|
.setLongClickListener(v -> {
|
||||||
|
new BeamAlertDialogBuilder(getContext())
|
||||||
|
.setTitle(Slic3rLocalization.getString(def.getFullLabel()))
|
||||||
|
.setMessage(Slic3rLocalization.getString(def.tooltip))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OptionElement(int specialType) {
|
||||||
|
this.specialType = specialType;
|
||||||
|
}
|
||||||
|
|
||||||
public OptionElement(int icon, String title) {
|
public OptionElement(int icon, String title) {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
@@ -855,10 +979,13 @@ 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);
|
||||||
v.setBackgroundColor(Color.GREEN);
|
|
||||||
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(32)) {{
|
v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewUtils.dp(32)) {{
|
||||||
bottomMargin = ViewUtils.dp(16);
|
bottomMargin = ViewUtils.dp(16);
|
||||||
}});
|
}});
|
||||||
@@ -866,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();
|
||||||
})),
|
})),
|
||||||
@@ -144,7 +149,7 @@ public class SettingsFragment extends ProfileListFragment {
|
|||||||
}),
|
}),
|
||||||
BeamServerData.isBoostyAvailable() ? new OptionElement(R.drawable.boosty, getContext().getString(R.string.SettingsBoosty)).setColor(R.attr.boostyColorTop, true).setOnClick(() -> {
|
BeamServerData.isBoostyAvailable() ? new OptionElement(R.drawable.boosty, getContext().getString(R.string.SettingsBoosty)).setColor(R.attr.boostyColorTop, true).setOnClick(() -> {
|
||||||
Activity act = (Activity) getContext();
|
Activity act = (Activity) getContext();
|
||||||
act.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://boosty.to/ytkab0bp")));
|
act.startActivity(new Intent(act, SetupActivity.class).putExtra(SetupActivity.EXTRA_BOOSTY_ONLY, true));
|
||||||
}) : null,
|
}) : null,
|
||||||
new OptionElement(R.drawable.k3d_logo_new_14, getContext().getString(R.string.SettingsK3D)).setColor(R.attr.k3dColor, true).setOnClick(() -> {
|
new OptionElement(R.drawable.k3d_logo_new_14, getContext().getString(R.string.SettingsK3D)).setColor(R.attr.k3dColor, true).setOnClick(() -> {
|
||||||
Activity act = (Activity) getContext();
|
Activity act = (Activity) getContext();
|
||||||
@@ -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() {}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,18 @@ public class MobileNavigationDelegate extends DelegateSlotImpl {
|
|||||||
return root = fl;
|
return root = fl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onBackPressed() {
|
||||||
|
if (super.onBackPressed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (currentSlot != 0) {
|
||||||
|
switchSlot(0, () -> navigationView.setSelectedItemId(0));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FrameLayout getOverlayView() {
|
public FrameLayout getOverlayView() {
|
||||||
return root;
|
return root;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -30,10 +31,13 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
private CharSequence mTitle;
|
private CharSequence mTitle;
|
||||||
private ValueProvider mSubtitle;
|
private ValueProvider mSubtitle;
|
||||||
private View.OnClickListener onClickListener;
|
private View.OnClickListener onClickListener;
|
||||||
|
private View.OnLongClickListener onLongClickListener;
|
||||||
private int textColorRes;
|
private int textColorRes;
|
||||||
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;
|
||||||
@@ -45,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;
|
||||||
@@ -90,6 +104,11 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PreferenceItem setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
|
||||||
|
this.onLongClickListener = onLongClickListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PreferenceHolderView onCreateView(Context ctx) {
|
public PreferenceHolderView onCreateView(Context ctx) {
|
||||||
return new PreferenceHolderView(ctx);
|
return new PreferenceHolderView(ctx);
|
||||||
@@ -106,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);
|
||||||
|
|
||||||
@@ -159,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);
|
||||||
|
|
||||||
@@ -189,6 +210,7 @@ public class PreferenceItem extends SimpleRecyclerItem<PreferenceItem.Preference
|
|||||||
} else {
|
} else {
|
||||||
setClickable(false);
|
setClickable(false);
|
||||||
}
|
}
|
||||||
|
setOnLongClickListener(item.onLongClickListener);
|
||||||
|
|
||||||
if (item.textColorRes != 0) {
|
if (item.textColorRes != 0) {
|
||||||
title.setTextColor(ThemesRepo.getColor(item.textColorRes));
|
title.setTextColor(ThemesRepo.getColor(item.textColorRes));
|
||||||
@@ -210,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@@ -28,6 +29,7 @@ public class PreferenceSwitchItem extends SimpleRecyclerItem<PreferenceSwitchIte
|
|||||||
private String mKey;
|
private String mKey;
|
||||||
private boolean mDefaultValue;
|
private boolean mDefaultValue;
|
||||||
private CompoundButton.OnCheckedChangeListener mChangeListener;
|
private CompoundButton.OnCheckedChangeListener mChangeListener;
|
||||||
|
private View.OnLongClickListener mLongClickListener;
|
||||||
private ValueProvider valueProvider;
|
private ValueProvider valueProvider;
|
||||||
|
|
||||||
public PreferenceSwitchItem setValueProvider(ValueProvider valueProvider) {
|
public PreferenceSwitchItem setValueProvider(ValueProvider valueProvider) {
|
||||||
@@ -46,6 +48,11 @@ public class PreferenceSwitchItem extends SimpleRecyclerItem<PreferenceSwitchIte
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PreferenceSwitchItem setLongClickListener(View.OnLongClickListener longClickListener) {
|
||||||
|
mLongClickListener = longClickListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PreferenceSwitchItem setTitle(CharSequence title) {
|
public PreferenceSwitchItem setTitle(CharSequence title) {
|
||||||
mTitle = title;
|
mTitle = title;
|
||||||
return this;
|
return this;
|
||||||
@@ -89,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);
|
||||||
@@ -154,13 +162,14 @@ public class PreferenceSwitchItem extends SimpleRecyclerItem<PreferenceSwitchIte
|
|||||||
item.mChangeListener.onCheckedChanged(matSwitch, check);
|
item.mChangeListener.onCheckedChanged(matSwitch, check);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
setOnLongClickListener(item.mLongClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ public class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void zoom(float zoom) {
|
public void zoom(float zoom) {
|
||||||
this.zoom = MathUtils.clamp(this.zoom + zoom / 25f, 1f, 5f);
|
this.zoom = MathUtils.clamp(this.zoom + zoom / 25f, 1f, 10f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZoom(float zoom) {
|
public void setZoom(float zoom) {
|
||||||
this.zoom = zoom;
|
this.zoom = zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(float x, float y) {
|
public Vec3d calcScreenMovement(float x, float y) {
|
||||||
x /= zoom;
|
x /= zoom;
|
||||||
y /= zoom;
|
y /= zoom;
|
||||||
|
|
||||||
@@ -61,8 +61,10 @@ public class Camera {
|
|||||||
screenX.multiply(x);
|
screenX.multiply(x);
|
||||||
screenY.multiply(y);
|
screenY.multiply(y);
|
||||||
|
|
||||||
Vec3d move = new Vec3d(screenX).add(screenY);
|
return new Vec3d(screenX).add(screenY);
|
||||||
|
}
|
||||||
|
public void move(float x, float y) {
|
||||||
|
Vec3d move = calcScreenMovement(x, y);
|
||||||
position.add(move);
|
position.add(move);
|
||||||
origin.add(move);
|
origin.add(move);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ public class CoordAxes {
|
|||||||
arrow.render();
|
arrow.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(double[] viewMatrix, double[] projectionMatrix, float emissionFactor, float invZoom) {
|
public void render(GLShadersManager shadersManager, double[] viewMatrix, double[] projectionMatrix, float emissionFactor, float invZoom) {
|
||||||
if (!arrow.isInitialized()) {
|
if (!arrow.isInitialized()) {
|
||||||
arrow.stilizedArrow(tipRadius, tipLength, stemRadius, stemLength);
|
arrow.stilizedArrow(tipRadius, tipLength, stemRadius, stemLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLShaderProgram currentShader = GLShadersManager.getCurrentShader();
|
GLShaderProgram currentShader = shadersManager.getCurrentShader();
|
||||||
GLShaderProgram shader = GLShadersManager.get(GLShadersManager.SHADER_GOURAUD_LIGHT);
|
GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_GOURAUD_LIGHT);
|
||||||
if (currentShader != null) {
|
if (currentShader != null) {
|
||||||
currentShader.stopUsing();
|
currentShader.stopUsing();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
// Instance values, should be released
|
// Instance values, should be released
|
||||||
private Bed3D bed;
|
private Bed3D bed;
|
||||||
private int lastConfigUid;
|
private int lastConfigUid;
|
||||||
|
private GLShadersManager shadersManager;
|
||||||
private GLModel backgroundModel;
|
private GLModel backgroundModel;
|
||||||
private GLModel selectionModel;
|
private GLModel selectionModel;
|
||||||
private List<GLModel> glModels = new ArrayList<>();
|
private List<GLModel> glModels = new ArrayList<>();
|
||||||
@@ -212,7 +213,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
GLShaderProgram shader = GLShadersManager.get(GLShadersManager.SHADER_BACKGROUND);
|
GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_BACKGROUND);
|
||||||
shader.startUsing();
|
shader.startUsing();
|
||||||
shader.setUniformColor("top_color", ThemesRepo.getColor(R.attr.backgroundColorTop));
|
shader.setUniformColor("top_color", ThemesRepo.getColor(R.attr.backgroundColorTop));
|
||||||
shader.setUniformColor("bottom_color", ThemesRepo.getColor(R.attr.backgroundColorBottom));
|
shader.setUniformColor("bottom_color", ThemesRepo.getColor(R.attr.backgroundColorBottom));
|
||||||
@@ -225,7 +226,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
configureBed();
|
configureBed();
|
||||||
}
|
}
|
||||||
if (bed.isValid()) {
|
if (bed.isValid()) {
|
||||||
bed.render(bottom, camera.getViewModelMatrix(), projectionMatrix, 1f / camera.getZoom());
|
bed.render(shadersManager, bottom, camera.getViewModelMatrix(), projectionMatrix, 1f / camera.getZoom());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isViewerEnabled) {
|
if (isViewerEnabled) {
|
||||||
@@ -239,7 +240,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
viewer.render(camera.getViewModelMatrix(), projectionMatrix);
|
viewer.render(camera.getViewModelMatrix(), projectionMatrix);
|
||||||
}
|
}
|
||||||
if (viewer == null && model != null) {
|
if (viewer == null && model != null) {
|
||||||
shader = GLShadersManager.get(GLShadersManager.SHADER_GOURAUD_LIGHT);
|
shader = shadersManager.get(GLShadersManager.SHADER_GOURAUD_LIGHT);
|
||||||
shader.startUsing();
|
shader.startUsing();
|
||||||
int color = ThemesRepo.getColor(android.R.attr.colorAccent);
|
int color = ThemesRepo.getColor(android.R.attr.colorAccent);
|
||||||
int hoverColor = ThemesRepo.getColor(R.attr.modelHoverColor);
|
int hoverColor = ThemesRepo.getColor(R.attr.modelHoverColor);
|
||||||
@@ -301,7 +302,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
if (selected) {
|
if (selected) {
|
||||||
shader.stopUsing();
|
shader.stopUsing();
|
||||||
|
|
||||||
GLShaderProgram flat = GLShadersManager.get(GLShadersManager.SHADER_FLAT);
|
GLShaderProgram flat = shadersManager.get(GLShadersManager.SHADER_FLAT);
|
||||||
glLineWidth(ViewUtils.dp(1.5f));
|
glLineWidth(ViewUtils.dp(1.5f));
|
||||||
|
|
||||||
flat.startUsing();
|
flat.startUsing();
|
||||||
@@ -323,7 +324,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
if (isInFlattenMode) {
|
if (isInFlattenMode) {
|
||||||
shader.stopUsing();
|
shader.stopUsing();
|
||||||
|
|
||||||
GLShaderProgram flat = GLShadersManager.get(GLShadersManager.SHADER_FLAT);
|
GLShaderProgram flat = shadersManager.get(GLShadersManager.SHADER_FLAT);
|
||||||
|
|
||||||
flat.startUsing();
|
flat.startUsing();
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
@@ -374,9 +375,8 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean onClick(float x, float y) {
|
public int raycastObjectIndex(float x, float y) {
|
||||||
if (model == null || isViewerEnabled) return false;
|
if (model == null) return -1;
|
||||||
|
|
||||||
double minDistance = Double.MAX_VALUE;
|
double minDistance = Double.MAX_VALUE;
|
||||||
int j = -1;
|
int j = -1;
|
||||||
for (int i = 0, c = model.getObjectsCount(); i < c; i++) {
|
for (int i = 0, c = model.getObjectsCount(); i < c; i++) {
|
||||||
@@ -394,6 +394,13 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onClick(float x, float y) {
|
||||||
|
if (model == null || isViewerEnabled) return false;
|
||||||
|
|
||||||
|
int j = raycastObjectIndex(x, y);
|
||||||
|
|
||||||
if (isInFlattenMode && (j == selectedObject || j == -1)) {
|
if (isInFlattenMode && (j == selectedObject || j == -1)) {
|
||||||
int minPlane = -1;
|
int minPlane = -1;
|
||||||
@@ -597,6 +604,7 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
|
|
||||||
backgroundModel = new GLModel();
|
backgroundModel = new GLModel();
|
||||||
backgroundModel.initBackgroundTriangles();
|
backgroundModel.initBackgroundTriangles();
|
||||||
|
shadersManager = new GLShadersManager();
|
||||||
if (!bed.isValid()) return;
|
if (!bed.isValid()) return;
|
||||||
|
|
||||||
if (cameraIsDirty) {
|
if (cameraIsDirty) {
|
||||||
@@ -619,7 +627,10 @@ public class GLRenderer implements GLSurfaceView.Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
GLShadersManager.clearShaders();
|
if (shadersManager != null) {
|
||||||
|
shadersManager.clearShaders();
|
||||||
|
shadersManager = null;
|
||||||
|
}
|
||||||
if (backgroundModel != null) {
|
if (backgroundModel != null) {
|
||||||
backgroundModel.release();
|
backgroundModel.release();
|
||||||
backgroundModel = null;
|
backgroundModel = null;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ public class Bed3D {
|
|||||||
|
|
||||||
private void configure(String path) {
|
private void configure(String path) {
|
||||||
Native.bed_configure(pointer, path);
|
Native.bed_configure(pointer, path);
|
||||||
|
Native.bed_init_triangles_mesh(pointer, triangles.pointer);
|
||||||
boundingVolume = Native.bed_get_bounding_volume(pointer);
|
boundingVolume = Native.bed_get_bounding_volume(pointer);
|
||||||
|
|
||||||
min = max = null;
|
min = max = null;
|
||||||
@@ -78,7 +79,11 @@ public class Bed3D {
|
|||||||
return boundingVolume != null;
|
return boundingVolume != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(boolean bottom, double[] viewModelMatrix, double[] projectionMatrix, float invZoom) {
|
public GLModel.MeshRaycaster getRaycaster() {
|
||||||
|
return triangles.getRaycaster();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(GLShadersManager shadersManager, boolean bottom, double[] viewModelMatrix, double[] projectionMatrix, float invZoom) {
|
||||||
assertTrue(viewModelMatrix.length == 16);
|
assertTrue(viewModelMatrix.length == 16);
|
||||||
assertTrue(projectionMatrix.length == 16);
|
assertTrue(projectionMatrix.length == 16);
|
||||||
|
|
||||||
@@ -87,12 +92,12 @@ public class Bed3D {
|
|||||||
DoubleMatrix.translateM(modelMatrix, 0, -getVolumeMin().x * 2, -getVolumeMin().y * 2, -getVolumeMin().z);
|
DoubleMatrix.translateM(modelMatrix, 0, -getVolumeMin().x * 2, -getVolumeMin().y * 2, -getVolumeMin().z);
|
||||||
}
|
}
|
||||||
DoubleMatrix.multiplyMM(outModelMatrix, 0, viewModelMatrix, 0, modelMatrix, 0);
|
DoubleMatrix.multiplyMM(outModelMatrix, 0, viewModelMatrix, 0, modelMatrix, 0);
|
||||||
renderDefaultBed(bottom, outModelMatrix, projectionMatrix);
|
renderDefaultBed(shadersManager, bottom, outModelMatrix, projectionMatrix);
|
||||||
axes.render(viewModelMatrix, projectionMatrix, 0.25f, invZoom);
|
axes.render(shadersManager, viewModelMatrix, projectionMatrix, 0.25f, invZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderDefaultBed(boolean bottom, double[] viewModelMatrix, double[] projectionMatrix) {
|
private void renderDefaultBed(GLShadersManager shadersManager, boolean bottom, double[] viewModelMatrix, double[] projectionMatrix) {
|
||||||
GLShaderProgram shader = GLShadersManager.get(GLShadersManager.SHADER_FLAT);
|
GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_FLAT);
|
||||||
shader.startUsing();
|
shader.startUsing();
|
||||||
|
|
||||||
shader.setUniformMatrix4fv("view_model_matrix", viewModelMatrix);
|
shader.setUniformMatrix4fv("view_model_matrix", viewModelMatrix);
|
||||||
@@ -121,8 +126,8 @@ public class Bed3D {
|
|||||||
shader.stopUsing();
|
shader.stopUsing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderTexturedBed(boolean bottom, float[] viewModelMatrix, float[] projectionMatrix) {
|
private void renderTexturedBed(GLShadersManager shadersManager, boolean bottom, float[] viewModelMatrix, float[] projectionMatrix) {
|
||||||
GLShaderProgram shader = GLShadersManager.get(GLShadersManager.SHADER_PRINTBED);
|
GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_PRINTBED);
|
||||||
shader.startUsing();
|
shader.startUsing();
|
||||||
|
|
||||||
shader.setUniform3f("view_model_matrix", viewModelMatrix);
|
shader.setUniform3f("view_model_matrix", viewModelMatrix);
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import androidx.annotation.StringDef;
|
|||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class GLShadersManager {
|
public class GLShadersManager {
|
||||||
@@ -52,7 +54,9 @@ public class GLShadersManager {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
public @interface ShaderType {}
|
public @interface ShaderType {}
|
||||||
|
|
||||||
private final static Map<String, GLShaderProgram> shaders = new HashMap<String, GLShaderProgram>() {
|
private final static List<GLShadersManager> managers = new ArrayList<>();
|
||||||
|
|
||||||
|
private final Map<String, GLShaderProgram> shaders = new HashMap<String, GLShaderProgram>() {
|
||||||
@Override
|
@Override
|
||||||
public GLShaderProgram get(@Nullable Object key) {
|
public GLShaderProgram get(@Nullable Object key) {
|
||||||
GLShaderProgram shader = super.get(key);
|
GLShaderProgram shader = super.get(key);
|
||||||
@@ -61,24 +65,35 @@ public class GLShadersManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void clearShaders() {
|
public GLShadersManager() {
|
||||||
|
managers.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearShaders() {
|
||||||
for (GLShaderProgram program : shaders.values()) {
|
for (GLShaderProgram program : shaders.values()) {
|
||||||
program.release();
|
program.release();
|
||||||
}
|
}
|
||||||
shaders.clear();
|
shaders.clear();
|
||||||
|
managers.remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GLShaderProgram get(@ShaderType String key) {
|
public GLShaderProgram get(@ShaderType String key) {
|
||||||
return shaders.get(key);
|
return shaders.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
private static long getCurrentShaderPointer() {
|
private static long getCurrentShaderPointer() {
|
||||||
GLShaderProgram prog = getCurrentShader();
|
GLShaderProgram prog = null;
|
||||||
|
for (GLShadersManager manager : managers) {
|
||||||
|
prog = manager.getCurrentShader();
|
||||||
|
if (prog != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return prog != null ? prog.pointer : 0;
|
return prog != null ? prog.pointer : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GLShaderProgram getCurrentShader() {
|
public GLShaderProgram getCurrentShader() {
|
||||||
int[] idRef = {0};
|
int[] idRef = {0};
|
||||||
GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, idRef, 0);
|
GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, idRef, 0);
|
||||||
int id = idRef[0];
|
int id = idRef[0];
|
||||||
|
|||||||
@@ -147,14 +147,30 @@ public class Model {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getExtruder(int i) {
|
||||||
|
return Native.model_get_extruder(pointer, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtruder(int i, int extruder) {
|
||||||
|
Native.model_set_extruder(pointer, i, extruder);
|
||||||
|
}
|
||||||
|
|
||||||
public void autoOrient(int i) {
|
public void autoOrient(int i) {
|
||||||
Native.model_auto_orient(pointer, i);
|
Native.model_auto_orient(pointer, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBigObject(int i) {
|
||||||
|
return Native.model_is_big_object(pointer, i);
|
||||||
|
}
|
||||||
|
|
||||||
public GCodeProcessorResult slice(String configPath, String gcodePath, SliceListener listener) throws Slic3rRuntimeError {
|
public GCodeProcessorResult slice(String configPath, String gcodePath, SliceListener listener) throws Slic3rRuntimeError {
|
||||||
return new GCodeProcessorResult(Native.model_slice(pointer, configPath, gcodePath, listener));
|
return new GCodeProcessorResult(Native.model_slice(pointer, configPath, gcodePath, listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void export3mf(String configPath, String _3mfPath) throws Slic3rRuntimeError {
|
||||||
|
Native.model_export_3mf(pointer, configPath, _3mfPath);
|
||||||
|
}
|
||||||
|
|
||||||
public void release() {
|
public void release() {
|
||||||
if (pointer != 0) {
|
if (pointer != 0) {
|
||||||
Native.model_release(pointer);
|
Native.model_release(pointer);
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class Native {
|
|||||||
static native int bed_get_bounding_volume_max_size(long ptr);
|
static native int bed_get_bounding_volume_max_size(long ptr);
|
||||||
static native double[] bed_get_bounding_volume(long ptr);
|
static native double[] bed_get_bounding_volume(long ptr);
|
||||||
static native void bed_configure(long ptr, String configPath);
|
static native void bed_configure(long ptr, String configPath);
|
||||||
|
static native void bed_init_triangles_mesh(long ptr, long triangles);
|
||||||
static native boolean bed_arrange(long ptr, long modelPtr);
|
static native boolean bed_arrange(long ptr, long modelPtr);
|
||||||
static native void bed_release(long ptr);
|
static native void bed_release(long ptr);
|
||||||
|
|
||||||
@@ -72,7 +73,11 @@ class Native {
|
|||||||
static native void model_flatten_rotate(long ptr, int i, long surfacePtr);
|
static native void model_flatten_rotate(long ptr, int i, long surfacePtr);
|
||||||
static native long[] model_create_flatten_planes(long ptr, int i);
|
static native long[] model_create_flatten_planes(long ptr, int i);
|
||||||
static native void model_auto_orient(long ptr, int i);
|
static native void model_auto_orient(long ptr, int i);
|
||||||
|
static native boolean model_is_big_object(long ptr, int i);
|
||||||
|
static native int model_get_extruder(long ptr, int i);
|
||||||
|
static native void model_set_extruder(long ptr, int i, int extruder);
|
||||||
static native long model_slice(long ptr, String configPath, String path, SliceListener listener) throws Slic3rRuntimeError;
|
static native long model_slice(long ptr, String configPath, String path, SliceListener listener) throws Slic3rRuntimeError;
|
||||||
|
static native void model_export_3mf(long ptr, String configPath, String path) throws Slic3rRuntimeError;
|
||||||
static native void model_release(long ptr);
|
static native void model_release(long ptr);
|
||||||
|
|
||||||
static native long gcoderesult_load_file(String path, String name);
|
static native long gcoderesult_load_file(String path, String name);
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -47,6 +47,12 @@ public class BeamTheme {
|
|||||||
colors.put(R.attr.yTrackColor, 0xff00bf00);
|
colors.put(R.attr.yTrackColor, 0xff00bf00);
|
||||||
colors.put(R.attr.zTrackColor, 0xff0000bf);
|
colors.put(R.attr.zTrackColor, 0xff0000bf);
|
||||||
|
|
||||||
|
colors.put(R.attr.snackbarBase, 0xFFEEEEEE);
|
||||||
|
colors.put(R.attr.snackbarDone, 0xFF56AB2F);
|
||||||
|
colors.put(R.attr.snackbarWarning, 0xFFAE660C);
|
||||||
|
colors.put(R.attr.snackbarInfo, 0xFF009DC6);
|
||||||
|
colors.put(R.attr.snackbarError, 0xFFDC100E);
|
||||||
|
|
||||||
colors.put(android.R.attr.textColorPrimary, 0xff000000);
|
colors.put(android.R.attr.textColorPrimary, 0xff000000);
|
||||||
colors.put(android.R.attr.textColorSecondary, 0x99000000);
|
colors.put(android.R.attr.textColorSecondary, 0x99000000);
|
||||||
colors.put(android.R.attr.windowBackground, 0xffffffff);
|
colors.put(android.R.attr.windowBackground, 0xffffffff);
|
||||||
@@ -73,6 +79,8 @@ public class BeamTheme {
|
|||||||
colors.put(R.attr.yTrackColor, 0xff00ee00);
|
colors.put(R.attr.yTrackColor, 0xff00ee00);
|
||||||
colors.put(R.attr.zTrackColor, 0xff0000ee);
|
colors.put(R.attr.zTrackColor, 0xff0000ee);
|
||||||
|
|
||||||
|
colors.put(R.attr.snackbarBase, 0xFF212121);
|
||||||
|
|
||||||
colors.put(android.R.attr.textColorPrimary, 0xffffffff);
|
colors.put(android.R.attr.textColorPrimary, 0xffffffff);
|
||||||
colors.put(android.R.attr.textColorSecondary, 0x99ffffff);
|
colors.put(android.R.attr.textColorSecondary, 0x99ffffff);
|
||||||
colors.put(android.R.attr.windowBackground, 0xff121212);
|
colors.put(android.R.attr.windowBackground, 0xff121212);
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ru.ytkab0bp.slicebeam.utils;
|
package ru.ytkab0bp.slicebeam.utils;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
@@ -9,16 +13,39 @@ import org.json.JSONObject;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
import ru.ytkab0bp.slicebeam.config.ConfigObject;
|
||||||
|
|
||||||
public class IOUtils {
|
public class IOUtils {
|
||||||
|
public static ExecutorService IO_POOL = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
public static String getDisplayName(Uri uri) {
|
||||||
|
ContentResolver resolver = SliceBeam.INSTANCE.getContentResolver();
|
||||||
|
|
||||||
|
String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
|
||||||
|
Cursor metaCursor = resolver.query(uri, projection, null, null, null);
|
||||||
|
String fileName = null;
|
||||||
|
if (metaCursor != null) {
|
||||||
|
try {
|
||||||
|
if (metaCursor.moveToFirst()) {
|
||||||
|
fileName = metaCursor.getString(0);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
metaCursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
public static String readString(InputStream in) throws IOException {
|
public static String readString(InputStream in) throws IOException {
|
||||||
return readString(in, false);
|
return readString(in, false);
|
||||||
}
|
}
|
||||||
@@ -49,10 +76,13 @@ public class IOUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ConfigObject downloadProfilesRecursively(String vendor, String type, String profile, List<String> supportedKeys) throws IOException, JSONException {
|
private static ConfigObject downloadProfilesRecursively(String vendor, String type, String profile, List<String> supportedKeys) throws IOException, JSONException, MissingProfileException {
|
||||||
ConfigObject cfg = new ConfigObject();
|
ConfigObject cfg = new ConfigObject();
|
||||||
|
|
||||||
URLConnection con = new URL(String.format("https://raw.githubusercontent.com/SoftFever/OrcaSlicer/main/resources/profiles/%s/%s/%s.json", vendor, type, profile)).openConnection();
|
HttpURLConnection con = (HttpURLConnection) new URL(String.format("https://raw.githubusercontent.com/SoftFever/OrcaSlicer/main/resources/profiles/%s/%s/%s.json", vendor, type, profile)).openConnection();
|
||||||
|
if (con.getResponseCode() == 404) {
|
||||||
|
throw new MissingProfileException(profile);
|
||||||
|
}
|
||||||
JSONObject obj = new JSONObject(readString(con.getInputStream()));
|
JSONObject obj = new JSONObject(readString(con.getInputStream()));
|
||||||
if (!TextUtils.isEmpty(obj.optString("inherits", null))) {
|
if (!TextUtils.isEmpty(obj.optString("inherits", null))) {
|
||||||
ConfigObject o = downloadProfilesRecursively(vendor, type, obj.getString("inherits"), supportedKeys);
|
ConfigObject o = downloadProfilesRecursively(vendor, type, obj.getString("inherits"), supportedKeys);
|
||||||
@@ -111,19 +141,103 @@ public class IOUtils {
|
|||||||
|
|
||||||
ConfigObject _obj = downloadProfilesRecursively(vendor, type, inherit, supportedKeys);
|
ConfigObject _obj = downloadProfilesRecursively(vendor, type, inherit, supportedKeys);
|
||||||
for (Map.Entry<String, String> en : _obj.values.entrySet()) {
|
for (Map.Entry<String, String> en : _obj.values.entrySet()) {
|
||||||
if (supportedKeys.contains(en.getKey())) {
|
String key = en.getKey();
|
||||||
if (en.getKey().equals("printable_area")) {
|
switch (key) {
|
||||||
cfg.values.put("bed_shape", en.getValue());
|
case "machine_start_gcode":
|
||||||
} else if (en.getKey().equals("ironing_type") && en.getValue().equals("no ironing")) {
|
key = "start_gcode";
|
||||||
|
break;
|
||||||
|
case "machine_end_gcode":
|
||||||
|
key = "end_gcode";
|
||||||
|
break;
|
||||||
|
case "printable_area":
|
||||||
|
key = "bed_shape";
|
||||||
|
break;
|
||||||
|
case "printable_height":
|
||||||
|
key = "max_print_height";
|
||||||
|
break;
|
||||||
|
case "layer_change_gcode":
|
||||||
|
key = "layer_gcode";
|
||||||
|
break;
|
||||||
|
case "before_layer_change_gcode":
|
||||||
|
key = "before_layer_gcode";
|
||||||
|
break;
|
||||||
|
case "filament_start_gcode":
|
||||||
|
key = "start_filament_gcode";
|
||||||
|
break;
|
||||||
|
case "filament_end_gcode":
|
||||||
|
key = "end_filament_gcode";
|
||||||
|
break;
|
||||||
|
case "retraction_minimum_level":
|
||||||
|
key = "retract_before_travel";
|
||||||
|
break;
|
||||||
|
case "retraction_length":
|
||||||
|
key = "retract_length";
|
||||||
|
break;
|
||||||
|
case "retraction_speed":
|
||||||
|
key = "retract_speed";
|
||||||
|
break;
|
||||||
|
case "deretraction_speed":
|
||||||
|
key = "deretract_speed";
|
||||||
|
break;
|
||||||
|
case "change_filament_gcode":
|
||||||
|
key = "pause_print_gcode";
|
||||||
|
break;
|
||||||
|
case "nozzle_temperature":
|
||||||
|
key = "temperature";
|
||||||
|
break;
|
||||||
|
case "nozzle_temperature_initial_layer":
|
||||||
|
key = "first_layer_temperature";
|
||||||
|
break;
|
||||||
|
case "filament_flow_ratio":
|
||||||
|
key = "extrusion_multiplier";
|
||||||
|
break;
|
||||||
|
case "chamber_temperatures":
|
||||||
|
key = "chamber_temperature";
|
||||||
|
break;
|
||||||
|
case "fan_max_speed":
|
||||||
|
key = "max_fan_speed";
|
||||||
|
break;
|
||||||
|
case "fan_min_speed":
|
||||||
|
key = "min_fan_speed";
|
||||||
|
break;
|
||||||
|
case "overhang_fan_speed":
|
||||||
|
key = "bridge_fan_speed";
|
||||||
|
break;
|
||||||
|
case "slow_down_layer_time":
|
||||||
|
key = "slowdown_below_layer_time";
|
||||||
|
break;
|
||||||
|
case "slow_down_min_speed":
|
||||||
|
key = "min_print_speed";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.equals("pressure_advance")) {
|
||||||
|
StringBuilder sb = new StringBuilder("SET_PRESSURE_ADVANCE ADVANCE=").append(en.getValue());
|
||||||
|
if (cfg.values.containsKey("start_filament_gcode")) {
|
||||||
|
sb.append("\n").append(cfg.get("start_filament_gcode"));
|
||||||
|
}
|
||||||
|
cfg.values.put("start_filament_gcode", sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportedKeys.contains(key)) {
|
||||||
|
if (key.equals("ironing_type") && en.getValue().equals("no ironing")) {
|
||||||
cfg.values.put("ironing", "0");
|
cfg.values.put("ironing", "0");
|
||||||
cfg.values.put("ironing_type", "top");
|
cfg.values.put("ironing_type", "top");
|
||||||
} if (en.getKey().equals("start_filament_gcode") || en.getKey().equals("end_filament_gcode") ||
|
}
|
||||||
en.getKey().equals("start_gcode") || en.getKey().equals("end_gcode")) {
|
if (key.equals("start_filament_gcode") || key.equals("end_filament_gcode") ||
|
||||||
|
key.equals("start_gcode") || key.equals("end_gcode")) {
|
||||||
|
|
||||||
cfg.values.put(en.getKey(), en.getValue().replaceAll("(\\{|\\[)nozzle_temperature_initial_layer(\\[\\d+]|)(}|])", "$1first_layer_temperature$2$3")
|
String val = en.getValue();
|
||||||
|
if (key.equals("start_filament_gcode")) {
|
||||||
|
if (cfg.values.containsKey("start_filament_gcode")) {
|
||||||
|
val = cfg.get("start_filament_gcode") + "\n" + val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.values.put(key, val.replaceAll("(\\{|\\[)nozzle_temperature_initial_layer(\\[\\d+]|)(}|])", "$1first_layer_temperature$2$3")
|
||||||
.replaceAll("(\\{|\\[)bed_temperature_initial_layer_single(\\[\\d+]|)(}|])", "$1first_layer_bed_temperature$2$3"));
|
.replaceAll("(\\{|\\[)bed_temperature_initial_layer_single(\\[\\d+]|)(}|])", "$1first_layer_bed_temperature$2$3"));
|
||||||
} else if (!en.getKey().equals("thumbnails")) {
|
} else if (!key.equals("thumbnails")) {
|
||||||
cfg.values.put(en.getKey(), en.getValue());
|
cfg.values.put(key, en.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import ru.ytkab0bp.slicebeam.R;
|
|||||||
import ru.ytkab0bp.slicebeam.SetupActivity;
|
import ru.ytkab0bp.slicebeam.SetupActivity;
|
||||||
|
|
||||||
public class Prefs {
|
public class Prefs {
|
||||||
|
public final static int CAMERA_CONTROL_MODE_ROTATE_MOVE = 0,
|
||||||
|
CAMERA_CONTROL_MODE_MOVE_ROTATE = 1,
|
||||||
|
CAMERA_CONTROL_MODE_MOVE_ONLY = 2;
|
||||||
|
|
||||||
private static SharedPreferences mPrefs;
|
private static SharedPreferences mPrefs;
|
||||||
|
|
||||||
public static void init(Application ctx) {
|
public static void init(Application ctx) {
|
||||||
@@ -27,6 +31,14 @@ public class Prefs {
|
|||||||
mPrefs.edit().putString("last_commit", BuildConfig.COMMIT).apply();
|
mPrefs.edit().putString("last_commit", BuildConfig.COMMIT).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isScaleInputInMM() {
|
||||||
|
return mPrefs.getBoolean("scale_input_mm", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setScaleInputInMM(boolean v) {
|
||||||
|
mPrefs.edit().putBoolean("scale_input_mm", v).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isScaleLinked() {
|
public static boolean isScaleLinked() {
|
||||||
return mPrefs.getBoolean("scale_linked", true);
|
return mPrefs.getBoolean("scale_linked", true);
|
||||||
}
|
}
|
||||||
@@ -60,12 +72,12 @@ public class Prefs {
|
|||||||
return mPrefs.getString("beam_server_data", "{}");
|
return mPrefs.getString("beam_server_data", "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isRotationEnabled() {
|
public static int getCameraControlMode() {
|
||||||
return mPrefs.getBoolean("rotation_enabled", true);
|
return mPrefs.getInt("camera_control_mode", mPrefs.getBoolean("rotation_enabled", true) ? CAMERA_CONTROL_MODE_ROTATE_MOVE : CAMERA_CONTROL_MODE_MOVE_ONLY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setRotationEnabled(boolean e) {
|
public static void setCameraControlMode(int mode) {
|
||||||
mPrefs.edit().putBoolean("rotation_enabled", e).apply();
|
mPrefs.edit().putInt("camera_control_mode", mode).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isOrthoProjectionEnabled() {
|
public static boolean isOrthoProjectionEnabled() {
|
||||||
@@ -113,6 +125,108 @@ 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 getCloudLocalLastSentModified() {
|
||||||
|
return mPrefs.getLong("cloud_local_last_sent_modified", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudLocalLastSentModified(long lm) {
|
||||||
|
mPrefs.edit().putLong("cloud_local_last_sent_modified", lm).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getCloudLocalLastModified() {
|
||||||
|
return mPrefs.getLong("cloud_local_last_modified", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudLocalLastModified(long lm) {
|
||||||
|
mPrefs.edit().putLong("cloud_local_last_modified", lm).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long getCloudRemoteLastModified() {
|
||||||
|
return mPrefs.getLong("cloud_remote_last_modified", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCloudRemoteLastModified(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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package ru.ytkab0bp.slicebeam.view;
|
package ru.ytkab0bp.slicebeam.view;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.graphics.LinearGradient;
|
import android.graphics.LinearGradient;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.Shader;
|
import android.graphics.Shader;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@@ -25,6 +29,10 @@ public class FadeRecyclerView extends RecyclerView implements IThemeView {
|
|||||||
private float topProgress, bottomProgress;
|
private float topProgress, bottomProgress;
|
||||||
private float overlayAlpha = 1f;
|
private float overlayAlpha = 1f;
|
||||||
|
|
||||||
|
private Bitmap bitmap;
|
||||||
|
private Canvas bitmapCanvas;
|
||||||
|
private boolean bitmapMode;
|
||||||
|
|
||||||
public FadeRecyclerView(@NonNull Context context) {
|
public FadeRecyclerView(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
@@ -52,6 +60,16 @@ public class FadeRecyclerView extends RecyclerView implements IThemeView {
|
|||||||
onApplyTheme();
|
onApplyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Very heavy, should be used only if transparent background is really needed
|
||||||
|
*/
|
||||||
|
public void setBitmapMode() {
|
||||||
|
this.bitmapMode = true;
|
||||||
|
topPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
|
||||||
|
bottomPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
|
||||||
|
invalidateShaders();
|
||||||
|
}
|
||||||
|
|
||||||
public void setOverlayAlpha(float overlayAlpha) {
|
public void setOverlayAlpha(float overlayAlpha) {
|
||||||
this.overlayAlpha = overlayAlpha;
|
this.overlayAlpha = overlayAlpha;
|
||||||
invalidate();
|
invalidate();
|
||||||
@@ -59,15 +77,56 @@ public class FadeRecyclerView extends RecyclerView implements IThemeView {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void draw(Canvas c) {
|
public void draw(Canvas c) {
|
||||||
super.draw(c);
|
Canvas cv;
|
||||||
|
if (bitmapMode) {
|
||||||
|
if (bitmap == null || bitmap.getWidth() != getWidth() || bitmap.getHeight() != getHeight()) {
|
||||||
|
if (bitmap != null) {
|
||||||
|
bitmap.recycle();
|
||||||
|
}
|
||||||
|
bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
bitmapCanvas = new Canvas(bitmap);
|
||||||
|
}
|
||||||
|
bitmap.eraseColor(Color.TRANSPARENT);
|
||||||
|
cv = bitmapCanvas;
|
||||||
|
super.draw(cv);
|
||||||
|
} else {
|
||||||
|
super.draw(cv = c);
|
||||||
|
}
|
||||||
|
|
||||||
if (topProgress > 0) {
|
if (topProgress > 0) {
|
||||||
topPaint.setAlpha((int) (topProgress * overlayAlpha * 0xFF));
|
cv.save();
|
||||||
c.drawRect(0, 0, getWidth(), ViewUtils.dp(HEIGHT_DP), topPaint);
|
if (bitmapMode) {
|
||||||
|
cv.translate(0, -ViewUtils.dp(HEIGHT_DP) * (1f - topProgress * overlayAlpha));
|
||||||
|
} else {
|
||||||
|
topPaint.setAlpha((int) (topProgress * overlayAlpha * 0xFF));
|
||||||
|
}
|
||||||
|
cv.drawRect(0, 0, getWidth(), ViewUtils.dp(HEIGHT_DP), topPaint);
|
||||||
|
cv.restore();
|
||||||
}
|
}
|
||||||
if (bottomProgress > 0) {
|
if (bottomProgress > 0) {
|
||||||
bottomPaint.setAlpha((int) (bottomProgress * overlayAlpha * 0xFF));
|
cv.save();
|
||||||
c.drawRect(0, getHeight() - ViewUtils.dp(HEIGHT_DP), getWidth(), getHeight(), bottomPaint);
|
if (bitmapMode) {
|
||||||
|
cv.translate(0, ViewUtils.dp(HEIGHT_DP) * (1f - bottomProgress * overlayAlpha));
|
||||||
|
} else {
|
||||||
|
bottomPaint.setAlpha((int) (bottomProgress * overlayAlpha * 0xFF));
|
||||||
|
}
|
||||||
|
cv.drawRect(0, getHeight() - ViewUtils.dp(HEIGHT_DP), getWidth(), getHeight(), bottomPaint);
|
||||||
|
cv.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmapMode) {
|
||||||
|
c.drawBitmap(bitmap, 0, 0, null);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (bitmap != null) {
|
||||||
|
bitmap.recycle();
|
||||||
|
bitmap = null;
|
||||||
|
bitmapCanvas = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,8 +139,9 @@ public class FadeRecyclerView extends RecyclerView implements IThemeView {
|
|||||||
private void invalidateShaders() {
|
private void invalidateShaders() {
|
||||||
if (getWidth() == 0 || getHeight() == 0) return;
|
if (getWidth() == 0 || getHeight() == 0) return;
|
||||||
|
|
||||||
topPaint.setShader(new LinearGradient(getWidth() / 2f, 0, getWidth() / 2f, ViewUtils.dp(HEIGHT_DP), ThemesRepo.getColor(android.R.attr.windowBackground), 0, Shader.TileMode.CLAMP));
|
int clr = bitmapMode ? Color.BLACK : ThemesRepo.getColor(android.R.attr.windowBackground);
|
||||||
bottomPaint.setShader(new LinearGradient(getWidth() / 2f, getHeight() - ViewUtils.dp(HEIGHT_DP), getWidth() / 2f, getHeight(), 0, ThemesRepo.getColor(android.R.attr.windowBackground), Shader.TileMode.CLAMP));
|
topPaint.setShader(new LinearGradient(getWidth() / 2f, 0, getWidth() / 2f, ViewUtils.dp(HEIGHT_DP), bitmapMode ? 0 : clr, bitmapMode ? clr : 0, Shader.TileMode.CLAMP));
|
||||||
|
bottomPaint.setShader(new LinearGradient(getWidth() / 2f, getHeight() - ViewUtils.dp(HEIGHT_DP), getWidth() / 2f, getHeight(), bitmapMode ? clr : 0, bitmapMode ? 0 : clr, Shader.TileMode.CLAMP));
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,23 @@ import android.opengl.GLSurfaceView;
|
|||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.StaticLayout;
|
import android.text.StaticLayout;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import ru.ytkab0bp.slicebeam.R;
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.SliceBeam;
|
||||||
|
import ru.ytkab0bp.slicebeam.events.LongClickTranslationEvent;
|
||||||
import ru.ytkab0bp.slicebeam.render.GLRenderer;
|
import ru.ytkab0bp.slicebeam.render.GLRenderer;
|
||||||
|
import ru.ytkab0bp.slicebeam.slic3r.GLModel;
|
||||||
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.Prefs;
|
import ru.ytkab0bp.slicebeam.utils.Prefs;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.Vec3d;
|
||||||
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
|
||||||
public class GLView extends GLSurfaceView implements IThemeView {
|
public class GLView extends GLSurfaceView implements IThemeView {
|
||||||
@@ -38,11 +44,28 @@ public class GLView extends GLSurfaceView implements IThemeView {
|
|||||||
private int touchSlop;
|
private int touchSlop;
|
||||||
|
|
||||||
private boolean fromTwoPointers;
|
private boolean fromTwoPointers;
|
||||||
private boolean isRotating;
|
private boolean onePointerGesture;
|
||||||
private boolean isMoving;
|
private boolean twoPointerGesture;
|
||||||
|
private boolean longClickGesture;
|
||||||
private boolean isScaling;
|
private boolean isScaling;
|
||||||
|
|
||||||
|
private Vec3d tempVec = new Vec3d();
|
||||||
|
private Vec3d longClickOffset = new Vec3d();
|
||||||
|
private Vec3d longClickTranslation = new Vec3d();
|
||||||
|
private ArrayList<GLModel.HitResult> longClickHitResults = new ArrayList<>();
|
||||||
private long lastActionTime = System.currentTimeMillis();
|
private long lastActionTime = System.currentTimeMillis();
|
||||||
|
private Runnable longClick = () -> {
|
||||||
|
getRenderer().getBed().getRaycaster().raycast(getRenderer(), longClickHitResults, lastX, lastY);
|
||||||
|
if (!longClickHitResults.isEmpty()) {
|
||||||
|
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||||
|
longClickGesture = true;
|
||||||
|
|
||||||
|
GLModel.HitResult result = longClickHitResults.get(0);
|
||||||
|
getRenderer().getModel().getTranslation(getRenderer().getSelectedObject(), tempVec);
|
||||||
|
longClickOffset.x = result.position.x - tempVec.x;
|
||||||
|
longClickOffset.y = result.position.y - tempVec.y;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private Path path = new Path();
|
private Path path = new Path();
|
||||||
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
@@ -249,33 +272,55 @@ public class GLView extends GLSurfaceView implements IThemeView {
|
|||||||
int action = e.getActionMasked();
|
int action = e.getActionMasked();
|
||||||
|
|
||||||
if (e.getPointerCount() > 2) {
|
if (e.getPointerCount() > 2) {
|
||||||
|
removeCallbacks(longClick);
|
||||||
|
longClickGesture = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
|
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||||
if (e.getPointerCount() == 2) {
|
if (e.getPointerCount() == 2) {
|
||||||
|
removeCallbacks(longClick);
|
||||||
|
longClickGesture = false;
|
||||||
calcStartFocus(e);
|
calcStartFocus(e);
|
||||||
fromTwoPointers = true;
|
fromTwoPointers = true;
|
||||||
} else {
|
} else {
|
||||||
lastX = e.getX();
|
lastX = e.getX();
|
||||||
lastY = e.getY();
|
lastY = e.getY();
|
||||||
|
|
||||||
|
int j = renderer.raycastObjectIndex(lastX, lastY);
|
||||||
|
if (renderer.getGcodeResult() == null && j == renderer.getSelectedObject() && j != -1) {
|
||||||
|
postDelayed(longClick, 300);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL) {
|
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||||
|
removeCallbacks(longClick);
|
||||||
|
if (longClickGesture) {
|
||||||
|
queueEvent(()->{
|
||||||
|
int j = getRenderer().getSelectedObject();
|
||||||
|
getRenderer().getModel().getTranslation(j, tempVec);
|
||||||
|
getRenderer().setSelectionTranslation(0, 0, 0);
|
||||||
|
getRenderer().getModel().translate(j, longClickTranslation.x, longClickTranslation.y, 0);
|
||||||
|
getRenderer().invalidateGlModel(j);
|
||||||
|
requestRender();
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new LongClickTranslationEvent(longClickTranslation.x, longClickTranslation.y, false));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
longClickGesture = false;
|
||||||
if (fromTwoPointers) {
|
if (fromTwoPointers) {
|
||||||
if (e.getPointerCount() == 1) {
|
if (e.getPointerCount() == 1) {
|
||||||
fromTwoPointers = false;
|
fromTwoPointers = false;
|
||||||
isScaling = false;
|
isScaling = false;
|
||||||
isMoving = false;
|
twoPointerGesture = false;
|
||||||
lastActionTime = 0;
|
lastActionTime = 0;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.getPointerCount() == 1) {
|
if (e.getPointerCount() == 1) {
|
||||||
if (!isRotating && action != MotionEvent.ACTION_CANCEL) {
|
if (!onePointerGesture && action != MotionEvent.ACTION_CANCEL) {
|
||||||
if (renderer.onClick(e.getX() * Prefs.getRenderScale(), e.getY() * Prefs.getRenderScale())) {
|
if (renderer.onClick(e.getX() * Prefs.getRenderScale(), e.getY() * Prefs.getRenderScale())) {
|
||||||
requestRender();
|
requestRender();
|
||||||
}
|
}
|
||||||
@@ -283,10 +328,10 @@ public class GLView extends GLSurfaceView implements IThemeView {
|
|||||||
|
|
||||||
lastX = e.getX(0);
|
lastX = e.getX(0);
|
||||||
lastY = e.getY(0);
|
lastY = e.getY(0);
|
||||||
isRotating = false;
|
onePointerGesture = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Rotate with inertia
|
// TODO: Rotate with inertia?
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (action == MotionEvent.ACTION_MOVE) {
|
if (action == MotionEvent.ACTION_MOVE) {
|
||||||
@@ -300,16 +345,16 @@ public class GLView extends GLSurfaceView implements IThemeView {
|
|||||||
|
|
||||||
if (deltaMs > 128) {
|
if (deltaMs > 128) {
|
||||||
isScaling = false;
|
isScaling = false;
|
||||||
isMoving = false;
|
twoPointerGesture = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean startingGesture = false;
|
boolean startingGesture = false;
|
||||||
if (!isScaling && !isMoving) {
|
if (!isScaling && !twoPointerGesture) {
|
||||||
if (Math.abs(distanceX) < touchSlop && Math.abs(distanceY) < touchSlop && Math.abs(len - lastLength) > touchSlop * 1.5f) {
|
if (Math.abs(distanceX) < touchSlop && Math.abs(distanceY) < touchSlop && Math.abs(len - lastLength) > touchSlop * 1.5f) {
|
||||||
isScaling = true;
|
isScaling = true;
|
||||||
startingGesture = true;
|
startingGesture = true;
|
||||||
} else if (Math.sqrt(distanceX * distanceX + distanceY * distanceY) >= touchSlop) {
|
} else if (Math.sqrt(distanceX * distanceX + distanceY * distanceY) >= touchSlop) {
|
||||||
isMoving = true;
|
twoPointerGesture = true;
|
||||||
startingGesture = true;
|
startingGesture = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,9 +370,14 @@ public class GLView extends GLSurfaceView implements IThemeView {
|
|||||||
|
|
||||||
lastX = x;
|
lastX = x;
|
||||||
lastY = y;
|
lastY = y;
|
||||||
} else if (isMoving) {
|
} else if (twoPointerGesture) {
|
||||||
if (!startingGesture) {
|
if (!startingGesture) {
|
||||||
renderer.getCamera().move(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
int mode = Prefs.getCameraControlMode();
|
||||||
|
if (mode == Prefs.CAMERA_CONTROL_MODE_ROTATE_MOVE || mode == Prefs.CAMERA_CONTROL_MODE_MOVE_ONLY) {
|
||||||
|
renderer.getCamera().move(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
||||||
|
} else {
|
||||||
|
renderer.getCamera().rotateAround(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
||||||
|
}
|
||||||
requestRender();
|
requestRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,21 +388,37 @@ public class GLView extends GLSurfaceView implements IThemeView {
|
|||||||
float distanceX = lastX - e.getX(), distanceY = lastY - e.getY();
|
float distanceX = lastX - e.getX(), distanceY = lastY - e.getY();
|
||||||
boolean startingGesture = false;
|
boolean startingGesture = false;
|
||||||
|
|
||||||
if (!isRotating) {
|
if (!onePointerGesture) {
|
||||||
if (Math.sqrt(distanceX * distanceX + distanceY * distanceY) >= touchSlop) {
|
if (Math.sqrt(distanceX * distanceX + distanceY * distanceY) >= touchSlop) {
|
||||||
isRotating = true;
|
onePointerGesture = true;
|
||||||
startingGesture = true;
|
startingGesture = true;
|
||||||
|
removeCallbacks(longClick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRotating) {
|
if (onePointerGesture) {
|
||||||
if (!startingGesture) {
|
if (!startingGesture) {
|
||||||
if (Prefs.isRotationEnabled()) {
|
if (longClickGesture) {
|
||||||
renderer.getCamera().rotateAround(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
getRenderer().getModel().getTranslation(getRenderer().getSelectedObject(), tempVec);
|
||||||
|
getRenderer().getBed().getRaycaster().raycast(getRenderer(), longClickHitResults, e.getX(), e.getY());
|
||||||
|
if (!longClickHitResults.isEmpty()) {
|
||||||
|
GLModel.HitResult result = longClickHitResults.get(0);
|
||||||
|
longClickTranslation.x = result.position.x - tempVec.x - longClickOffset.x;
|
||||||
|
longClickTranslation.y = result.position.y - tempVec.y - longClickOffset.y;
|
||||||
|
getRenderer().setSelectionTranslation(longClickTranslation.x, longClickTranslation.y, 0);
|
||||||
|
SliceBeam.EVENT_BUS.fireEvent(new LongClickTranslationEvent(longClickTranslation.x, longClickTranslation.y, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
requestRender();
|
||||||
} else {
|
} else {
|
||||||
renderer.getCamera().move(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
int mode = Prefs.getCameraControlMode();
|
||||||
|
if (mode == Prefs.CAMERA_CONTROL_MODE_ROTATE_MOVE) {
|
||||||
|
renderer.getCamera().rotateAround(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
||||||
|
} else {
|
||||||
|
renderer.getCamera().move(distanceX / touchSlop * Prefs.getCameraSensitivity(), distanceY / touchSlop * Prefs.getCameraSensitivity());
|
||||||
|
}
|
||||||
|
requestRender();
|
||||||
}
|
}
|
||||||
requestRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastX = e.getX();
|
lastX = e.getX();
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,341 @@
|
|||||||
|
package ru.ytkab0bp.slicebeam.view;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
|
import android.graphics.Outline;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.Space;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.graphics.ColorUtils;
|
||||||
|
import androidx.dynamicanimation.animation.FloatValueHolder;
|
||||||
|
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||||
|
import androidx.dynamicanimation.animation.SpringForce;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ru.ytkab0bp.slicebeam.R;
|
||||||
|
import ru.ytkab0bp.slicebeam.theme.ThemesRepo;
|
||||||
|
import ru.ytkab0bp.slicebeam.utils.ViewUtils;
|
||||||
|
|
||||||
|
public class SnackbarsLayout extends FrameLayout {
|
||||||
|
public SnackbarsLayout(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(Snackbar snackbar) {
|
||||||
|
if (snackbar.tag != null) {
|
||||||
|
dismiss(snackbar.tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
SnackbarView v = new SnackbarView(getContext()).bind(snackbar);
|
||||||
|
addView(v);
|
||||||
|
applyTransforms();
|
||||||
|
new SpringAnimation(new FloatValueHolder(0))
|
||||||
|
.setMinimumVisibleChange(1 / 500f)
|
||||||
|
.setSpring(new SpringForce(1f)
|
||||||
|
.setStiffness(1000f)
|
||||||
|
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY))
|
||||||
|
.addUpdateListener((animation, value, velocity) -> {
|
||||||
|
v.progress = value;
|
||||||
|
applyTransforms();
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
|
||||||
|
if (snackbar.lifetime > 0) {
|
||||||
|
ViewUtils.postOnMainThread(v::dismiss, snackbar.lifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dismiss(String tag) {
|
||||||
|
for (int i = 0, s = getChildCount(); i < s; i++) {
|
||||||
|
SnackbarView snackbar = (SnackbarView) getChildAt(i);
|
||||||
|
if (Objects.equals(snackbar.snackbar.tag, tag)) {
|
||||||
|
snackbar.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
super.onLayout(changed, left, top, right, bottom);
|
||||||
|
applyTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
applyTransforms();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTransforms() {
|
||||||
|
float y = getHeight() - ViewUtils.dp(8);
|
||||||
|
|
||||||
|
for (int i = getChildCount() - 1; i >= 0; i--) {
|
||||||
|
SnackbarView snackbar = (SnackbarView) getChildAt(i);
|
||||||
|
if (snackbar.getTag() == null) {
|
||||||
|
snackbar.setAlpha(snackbar.progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
float yOff = snackbar.getAlpha() * snackbar.progress * (snackbar.getHeight() + ViewUtils.dp(8));
|
||||||
|
y -= yOff;
|
||||||
|
snackbar.setTranslationY(y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SnackbarView extends LinearLayout {
|
||||||
|
private final static int MARGIN_DP = 8;
|
||||||
|
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private ImageView icon;
|
||||||
|
private TextView title;
|
||||||
|
private Snackbar snackbar;
|
||||||
|
|
||||||
|
private TextView button;
|
||||||
|
|
||||||
|
private float progress;
|
||||||
|
|
||||||
|
private GestureDetector gestureDetector;
|
||||||
|
|
||||||
|
SnackbarView(Context context) {
|
||||||
|
super(context);
|
||||||
|
|
||||||
|
setElevation(ViewUtils.dp(4));
|
||||||
|
setClipToOutline(true);
|
||||||
|
setOutlineProvider(new ViewOutlineProvider() {
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline) {
|
||||||
|
outline.setRoundRect(0, 0, getWidth(), getHeight(), ViewUtils.dp(16));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setAlpha(0f);
|
||||||
|
setPadding(ViewUtils.dp(10), ViewUtils.dp(10), ViewUtils.dp(10), ViewUtils.dp(10));
|
||||||
|
setMinimumHeight(ViewUtils.dp(48));
|
||||||
|
|
||||||
|
setOrientation(HORIZONTAL);
|
||||||
|
setGravity(Gravity.CENTER_VERTICAL);
|
||||||
|
|
||||||
|
FrameLayout fl = new FrameLayout(context);
|
||||||
|
icon = new ImageView(context);
|
||||||
|
fl.addView(icon, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
progressBar = new ProgressBar(context);
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
fl.addView(progressBar, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||||
|
addView(fl, new LinearLayout.LayoutParams(ViewUtils.dp(28), ViewUtils.dp(28)) {{
|
||||||
|
setMarginStart(ViewUtils.dp(4));
|
||||||
|
setMarginEnd(ViewUtils.dp(14));
|
||||||
|
}});
|
||||||
|
title = new TextView(context);
|
||||||
|
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||||
|
title.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
title.setMaxLines(2);
|
||||||
|
title.setEllipsize(TextUtils.TruncateAt.END);
|
||||||
|
addView(title);
|
||||||
|
|
||||||
|
addView(new Space(context), new LinearLayout.LayoutParams(0, 0, 1f));
|
||||||
|
|
||||||
|
button = new TextView(context);
|
||||||
|
button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
|
||||||
|
button.setTypeface(ViewUtils.getTypeface(ViewUtils.ROBOTO_MEDIUM));
|
||||||
|
button.setMaxLines(1);
|
||||||
|
button.setEllipsize(TextUtils.TruncateAt.END);
|
||||||
|
button.setPadding(ViewUtils.dp(8), ViewUtils.dp(8), ViewUtils.dp(8),ViewUtils.dp(8));
|
||||||
|
addView(button);
|
||||||
|
|
||||||
|
setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {{
|
||||||
|
leftMargin = topMargin = rightMargin = bottomMargin = ViewUtils.dp(MARGIN_DP);
|
||||||
|
}});
|
||||||
|
|
||||||
|
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDown(@NonNull MotionEvent e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
|
||||||
|
getParent().requestDisallowInterceptTouchEvent(true);
|
||||||
|
|
||||||
|
float off = e2.getX() - e1.getX();
|
||||||
|
setTranslationX(off);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
|
||||||
|
if (snackbar.lifetime == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(velocityX) >= 1500) {
|
||||||
|
if (velocityX > 0) {
|
||||||
|
animateTo(getWidth() + ViewUtils.dp(MARGIN_DP), true);
|
||||||
|
} else {
|
||||||
|
animateTo(-getWidth() - ViewUtils.dp(MARGIN_DP), true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismiss() {
|
||||||
|
setTag(1);
|
||||||
|
|
||||||
|
new SpringAnimation(new FloatValueHolder(0))
|
||||||
|
.setMinimumVisibleChange(1 / 500f)
|
||||||
|
.setSpring(new SpringForce(1f)
|
||||||
|
.setStiffness(1000f)
|
||||||
|
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY))
|
||||||
|
.addUpdateListener((animation, value, velocity) -> {
|
||||||
|
setAlpha(1f - value);
|
||||||
|
applyTransforms();
|
||||||
|
})
|
||||||
|
.addEndListener((animation, canceled, value, velocity) -> {
|
||||||
|
if (getParent() == null) return;
|
||||||
|
((ViewGroup) getParent()).removeView(this);
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateTo(float x, boolean remove) {
|
||||||
|
if (remove) {
|
||||||
|
setTag(1);
|
||||||
|
}
|
||||||
|
float start = getTranslationX();
|
||||||
|
new SpringAnimation(new FloatValueHolder(0))
|
||||||
|
.setMinimumVisibleChange(1 / 500f)
|
||||||
|
.setSpring(new SpringForce(1f)
|
||||||
|
.setStiffness(1000f)
|
||||||
|
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY))
|
||||||
|
.addUpdateListener((animation, value, velocity) -> {
|
||||||
|
setTranslationX(ViewUtils.lerp(start, x, value));
|
||||||
|
if (remove) {
|
||||||
|
progress = 1f - value;
|
||||||
|
applyTransforms();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addEndListener((animation, canceled, value, velocity) -> {
|
||||||
|
if (remove && getParent() != null) {
|
||||||
|
((ViewGroup) getParent()).removeView(SnackbarView.this);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
if ((event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL) && getTag() == null) {
|
||||||
|
animateTo(0, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent ev = MotionEvent.obtain(event);
|
||||||
|
ev.offsetLocation(getTranslationX(), 0);
|
||||||
|
boolean ret = gestureDetector.onTouchEvent(ev);
|
||||||
|
ev.recycle();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnackbarView bind(Snackbar snackbar) {
|
||||||
|
this.snackbar = snackbar;
|
||||||
|
|
||||||
|
progressBar.setVisibility(snackbar.type == Type.LOADING ? VISIBLE : GONE);
|
||||||
|
icon.setVisibility(snackbar.type == Type.LOADING ? GONE : VISIBLE);
|
||||||
|
|
||||||
|
title.setText(snackbar.title);
|
||||||
|
button.setText(snackbar.buttonTitle);
|
||||||
|
button.setOnClickListener(snackbar.buttonClick);
|
||||||
|
button.setVisibility(snackbar.buttonTitle != null ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
switch (snackbar.type) {
|
||||||
|
case DONE:
|
||||||
|
icon.setImageResource(R.drawable.done_outline_28);
|
||||||
|
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarDone)));
|
||||||
|
title.setTextColor(ThemesRepo.getColor(R.attr.snackbarDone));
|
||||||
|
button.setTextColor(ThemesRepo.getColor(R.attr.snackbarDone));
|
||||||
|
button.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ColorUtils.setAlphaComponent(ThemesRepo.getColor(R.attr.snackbarDone), 0x21), 8));
|
||||||
|
setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarDone), 0.15f));
|
||||||
|
break;
|
||||||
|
case WARNING:
|
||||||
|
icon.setImageResource(R.drawable.warning_triangle_outline_28);
|
||||||
|
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarWarning)));
|
||||||
|
title.setTextColor(ThemesRepo.getColor(R.attr.snackbarWarning));
|
||||||
|
button.setTextColor(ThemesRepo.getColor(R.attr.snackbarWarning));
|
||||||
|
button.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ColorUtils.setAlphaComponent(ThemesRepo.getColor(R.attr.snackbarWarning), 0x21), 8));
|
||||||
|
setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarWarning), 0.15f));
|
||||||
|
break;
|
||||||
|
case LOADING:
|
||||||
|
progressBar.setIndeterminateTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarInfo)));
|
||||||
|
title.setTextColor(ThemesRepo.getColor(R.attr.snackbarInfo));
|
||||||
|
button.setTextColor(ThemesRepo.getColor(R.attr.snackbarInfo));
|
||||||
|
button.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ColorUtils.setAlphaComponent(ThemesRepo.getColor(R.attr.snackbarInfo), 0x21), 8));
|
||||||
|
setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarInfo), 0.15f));
|
||||||
|
break;
|
||||||
|
case INFO:
|
||||||
|
icon.setImageResource(R.drawable.info_outline_28);
|
||||||
|
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarInfo)));
|
||||||
|
title.setTextColor(ThemesRepo.getColor(R.attr.snackbarInfo));
|
||||||
|
button.setTextColor(ThemesRepo.getColor(R.attr.snackbarInfo));
|
||||||
|
button.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ColorUtils.setAlphaComponent(ThemesRepo.getColor(R.attr.snackbarInfo), 0x21), 8));
|
||||||
|
setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarInfo), 0.15f));
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
icon.setImageResource(R.drawable.error_outline_28);
|
||||||
|
icon.setImageTintList(ColorStateList.valueOf(ThemesRepo.getColor(R.attr.snackbarError)));
|
||||||
|
title.setTextColor(ThemesRepo.getColor(R.attr.snackbarError));
|
||||||
|
button.setTextColor(ThemesRepo.getColor(R.attr.snackbarError));
|
||||||
|
button.setBackground(ViewUtils.createRipple(ThemesRepo.getColor(android.R.attr.colorControlHighlight), ColorUtils.setAlphaComponent(ThemesRepo.getColor(R.attr.snackbarError), 0x10), 8));
|
||||||
|
setBackgroundColor(ColorUtils.blendARGB(ThemesRepo.getColor(R.attr.snackbarBase), ThemesRepo.getColor(R.attr.snackbarError), 0.15f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Snackbar {
|
||||||
|
public CharSequence title;
|
||||||
|
public Type type;
|
||||||
|
public int lifetime = 2500;
|
||||||
|
public String tag;
|
||||||
|
|
||||||
|
public CharSequence buttonTitle;
|
||||||
|
public View.OnClickListener buttonClick;
|
||||||
|
|
||||||
|
public Snackbar(Type type, CharSequence title) {
|
||||||
|
this.type = type;
|
||||||
|
this.title = title;
|
||||||
|
|
||||||
|
if (type == Type.WARNING || type == Type.ERROR) {
|
||||||
|
lifetime = 5000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Snackbar tag(String tag) {
|
||||||
|
this.lifetime = 0;
|
||||||
|
this.tag = tag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
DONE, WARNING, INFO, ERROR,
|
||||||
|
LOADING // Must use tag
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,13 @@
|
|||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
|
||||||
#include "libslic3r/ModelArrange.hpp"
|
#include "libslic3r/ModelArrange.hpp"
|
||||||
#include "libslic3r/SVG.hpp"
|
#include "libslic3r/SVG.hpp"
|
||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
#include "libslic3r/Arrange.hpp"
|
#include "libslic3r/Arrange.hpp"
|
||||||
#include "libslic3r/AABBMesh.hpp"
|
#include "libslic3r/AABBMesh.hpp"
|
||||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||||
|
#include "libslic3r/Format/3mf.hpp"
|
||||||
#include "bbl/Orient.hpp"
|
#include "bbl/Orient.hpp"
|
||||||
#include "Viewer.hpp"
|
#include "Viewer.hpp"
|
||||||
|
|
||||||
@@ -817,16 +817,33 @@ extern "C" {
|
|||||||
orientation::orient(obj);
|
orientation::orient(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1is_1big_1object(JNIEnv* env, jclass, jlong ptr, jint i) {
|
||||||
|
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
||||||
|
ModelObject* obj = model->model.objects[i];
|
||||||
|
return obj->volumes.size() == 1 && obj->volumes.front()->mesh().its.indices.size() >= 500000;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1get_1extruder(JNIEnv* env, jclass, jlong ptr, jint i) {
|
||||||
|
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
||||||
|
ModelObject* obj = model->model.objects[i];
|
||||||
|
return obj->config.has("extruder") ? obj->config.opt_int("extruder") : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1set_1extruder(JNIEnv* env, jclass, jlong ptr, jint i, jint extruder) {
|
||||||
|
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
||||||
|
ModelObject* obj = model->model.objects[i];
|
||||||
|
obj->config.set("extruder", extruder);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1slice(JNIEnv* env, jclass, jlong ptr, jstring configPath, jstring path, jobject listener) {
|
JNIEXPORT jlong JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1slice(JNIEnv* env, jclass, jlong ptr, jstring configPath, jstring path, jobject listener) {
|
||||||
try {
|
try {
|
||||||
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
||||||
|
|
||||||
Print print;
|
Print print;
|
||||||
PresetBundle bundle;
|
|
||||||
DynamicPrintConfig config;
|
DynamicPrintConfig config;
|
||||||
const char *chars = env->GetStringUTFChars(configPath, JNI_FALSE);
|
const char *chars = env->GetStringUTFChars(configPath, JNI_FALSE);
|
||||||
config.load(std::string(chars), ForwardCompatibilitySubstitutionRule::Disable);
|
config.load(std::string(chars), ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
env->ReleaseStringUTFChars(path, chars);
|
env->ReleaseStringUTFChars(configPath, chars);
|
||||||
config.normalize_fdm();
|
config.normalize_fdm();
|
||||||
|
|
||||||
for (auto* mo : model->model.objects) {
|
for (auto* mo : model->model.objects) {
|
||||||
@@ -883,6 +900,24 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1export_13mf(JNIEnv* env, jclass, jlong ptr, jstring configPath, jstring path) {
|
||||||
|
auto model = reinterpret_cast<ModelRef*>(ptr);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
const char *chars = env->GetStringUTFChars(configPath, JNI_FALSE);
|
||||||
|
config.load(std::string(chars), ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
|
env->ReleaseStringUTFChars(configPath, chars);
|
||||||
|
config.normalize_fdm();
|
||||||
|
|
||||||
|
const char *pathChars = env->GetStringUTFChars(path, JNI_FALSE);
|
||||||
|
Slic3r::store_3mf(pathChars, &model->model, &config, false, nullptr, false);
|
||||||
|
env->ReleaseStringUTFChars(path, pathChars);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
env->ThrowNew(env->FindClass("ru/ytkab0bp/slicebeam/slic3r/Slic3rRuntimeError"), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1release(JNIEnv* env, jclass, jlong ptr) {
|
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1release(JNIEnv* env, jclass, jlong ptr) {
|
||||||
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
ModelRef* model = (ModelRef*) (intptr_t) ptr;
|
||||||
delete model;
|
delete model;
|
||||||
@@ -1208,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) {
|
||||||
@@ -1366,6 +1407,19 @@ extern "C" {
|
|||||||
bed_util_init_contourlines(ref->contour, ref->contourlines);
|
bed_util_init_contourlines(ref->contour, ref->contourlines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_bed_1init_1triangles_1mesh(JNIEnv* env, jclass, jlong ptr, jlong triangles_ptr) {
|
||||||
|
auto ref = reinterpret_cast<BedRef*>(ptr);
|
||||||
|
auto tRef = reinterpret_cast<GLModelRef*>(triangles_ptr);
|
||||||
|
|
||||||
|
auto contour = ref->contour;
|
||||||
|
BoundingBox bb = get_extents(contour);
|
||||||
|
Point center = bb.center();
|
||||||
|
float scaleFactor = 4;
|
||||||
|
contour.scale(scaleFactor);
|
||||||
|
contour.translate(-center.x() * scaleFactor * 0.5f, -center.y() * scaleFactor * 0.5f);
|
||||||
|
bed_util_init_triangles_its(contour, &tRef->mesh.its);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_bed_1arrange(JNIEnv* env, jclass, jlong ptr, jlong model) {
|
JNIEXPORT jboolean JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_bed_1arrange(JNIEnv* env, jclass, jlong ptr, jlong model) {
|
||||||
BedRef* ref = (BedRef*) (intptr_t) ptr;
|
BedRef* ref = (BedRef*) (intptr_t) ptr;
|
||||||
ModelRef* mRef = (ModelRef*) (intptr_t) model;
|
ModelRef* mRef = (ModelRef*) (intptr_t) model;
|
||||||
@@ -1453,6 +1507,7 @@ extern "C" {
|
|||||||
|
|
||||||
ref->data = libvgcode_convert_input_data(resultRef->result, resultRef->result.extruder_colors, resultRef->result.extruder_colors, ref->viewer);
|
ref->data = libvgcode_convert_input_data(resultRef->result, resultRef->result.extruder_colors, resultRef->result.extruder_colors, ref->viewer);
|
||||||
ref->viewer.load(std::move(ref->data));
|
ref->viewer.load(std::move(ref->data));
|
||||||
|
ref->viewer.set_time_mode(libvgcode::ETimeMode::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1reset(JNIEnv* env, jclass, jlong ptr) {
|
JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_vgcode_1reset(JNIEnv* env, jclass, jlong ptr) {
|
||||||
|
|||||||
@@ -56,6 +56,22 @@ void bed_util_init_gridlines(ExPolygon& contour, GLModel* glGridlines) {
|
|||||||
glGridlines->init_from(std::move(init_data));
|
glGridlines->init_from(std::move(init_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bed_util_init_triangles_its(ExPolygon& contour, indexed_triangle_set* its) {
|
||||||
|
if (contour.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto triangles = triangulate_expolygon_3d(contour, 0);
|
||||||
|
its->vertices.reserve(triangles.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < triangles.size(); i += 3) {
|
||||||
|
its->vertices.emplace_back(triangles[i].cast<float>());
|
||||||
|
its->vertices.emplace_back(triangles[i + 1].cast<float>());
|
||||||
|
its->vertices.emplace_back(triangles[i + 2].cast<float>());
|
||||||
|
|
||||||
|
its->indices.emplace_back(i, i + 1, i + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void bed_util_init_triangles(ExPolygon& contour, GLModel* glTriangles) {
|
void bed_util_init_triangles(ExPolygon& contour, GLModel* glTriangles) {
|
||||||
if (glTriangles->is_initialized())
|
if (glTriangles->is_initialized())
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -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="M15,3a1,1 0,1 0,-2 0v13.586l-2.293,-2.293a1,1 0,0 0,-1.414 1.414l4,4 0.006,0.007a1,1 0,0 0,0.698 0.286h0.006a0.996,0.996 0,0 0,0.697 -0.286l0.008,-0.008 4,-3.999a1,1 0,0 0,-1.415 -1.414L15,16.586V3ZM8.98,6h-0.02c-1.525,0.062 -2.332,0.299 -3.145,0.733a5.02,5.02 0,0 0,-2.082 2.082C3.194,9.823 3,10.814 3,13.023v5.954c0,2.21 0.194,3.2 0.733,4.208a5.02,5.02 0,0 0,2.082 2.082c1.008,0.539 1.999,0.733 4.208,0.733h7.954c2.21,0 3.2,-0.194 4.208,-0.733a5.019,5.019 0,0 0,2.082 -2.082c0.539,-1.008 0.733,-1.999 0.733,-4.207v-5.956c0,-2.208 -0.194,-3.2 -0.733,-4.207a5.02,5.02 0,0 0,-2.082 -2.082c-0.813,-0.434 -1.62,-0.67 -3.145,-0.732L19.02,6H18a1,1 0,1 0,0 2h0.98c1.28,0.053 1.776,0.237 2.261,0.497 0.547,0.292 0.97,0.715 1.262,1.262 0.307,0.572 0.497,1.168 0.497,3.264v5.954c0,2.096 -0.19,2.692 -0.497,3.264a3.02,3.02 0,0 1,-1.262 1.262c-0.572,0.307 -1.168,0.497 -3.264,0.497h-7.954c-2.096,0 -2.692,-0.19 -3.264,-0.497a3.02,3.02 0,0 1,-1.262 -1.262C5.19,21.67 5,21.073 5,18.977v-5.954c0,-2.096 0.19,-2.691 0.497,-3.264a3.02,3.02 0,0 1,1.262 -1.262c0.485,-0.26 0.98,-0.444 2.262,-0.497H10a1,1 0,1 0,0 -2H8.98Z"
|
||||||
|
android:fillColor="#000"/>
|
||||||
|
</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="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,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="M11,18.586L5.707,13.293C5.317,12.902 4.683,12.902 4.293,13.293C3.902,13.683 3.902,14.317 4.293,14.707L10.293,20.707C10.683,21.098 11.317,21.098 11.707,20.707L23.707,8.707C24.098,8.317 24.098,7.683 23.707,7.293C23.317,6.902 22.683,6.902 22.293,7.293L11,18.586Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</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="M3,14C3,7.928 7.928,3 14,3C20.072,3 25,7.928 25,14C25,20.072 20.072,25 14,25C7.928,25 3,20.072 3,14ZM14,4.8C8.922,4.8 4.8,8.922 4.8,14C4.8,19.078 8.922,23.2 14,23.2C19.078,23.2 23.2,19.078 23.2,14C23.2,8.922 19.078,4.8 14,4.8ZM13,14.5L13,9.5C13,8.948 13.448,8.5 14,8.5C14.552,8.5 15,8.948 15,9.5L15,14.5C15,15.052 14.552,15.5 14,15.5C13.448,15.5 13,15.052 13,14.5ZM12.75,18.25C12.75,17.56 13.31,17 14,17L14,17C14.69,17 15.25,17.56 15.25,18.25C15.25,18.94 14.69,19.5 14,19.5L14,19.5C13.31,19.5 12.75,18.94 12.75,18.25Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeColor="#00000000"/>
|
||||||
|
</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>
|
||||||
@@ -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.119,4.173a10.017,10.017 0,0 0,-6.835 4.92h4.58a20.109,20.109 0,0 1,2.255 -4.92ZM11.418,11.093h6.004a1,1 0,0 0,0.933 -1.36c-0.576,-1.922 -1.474,-3.927 -2.416,-5.55a10.024,10.024 0,0 1,7.41 6.231l0.012,0.035a1,1 0,1 0,1.87 -0.71l-0.016,-0.042c-1.73,-4.503 -6.095,-7.701 -11.21,-7.701C7.375,1.996 2,7.37 2,14c0,5.126 3.212,9.499 7.73,11.22a1,1 0,0 0,0.712 -1.868,10.04 10.04,0 0,1 -5.158,-4.445L9.5,18.907a1,1 0,1 0,0 -2L4.429,16.907A10.005,10.005 0,0 1,4 14c0,-1.011 0.15,-1.987 0.429,-2.907h4.95a17.64,17.64 0,0 0,-0.271 2.373l-0.004,0.083a1.192,1.192 0,0 0,0.003 0.145,1 1,0 1,0 1.997,-0.107l0.002,-0.029c0.039,-0.852 0.148,-1.675 0.312,-2.465ZM14.036,4.893a25.223,25.223 0,0 1,1.999 4.2h-4.071a18.207,18.207 0,0 1,2.072 -4.2ZM19.5,26.5a7,7 0,1 0,0 -14,7 7,0 0,0 0,14ZM22.957,17.457a1,1 0,0 0,-1.414 -1.414L19.5,18.086l-2.043,-2.043a1,1 0,0 0,-1.414 1.414l2.043,2.043 -2.043,2.043a1,1 0,0 0,1.414 1.414l2.043,-2.043 2.043,2.043a1,1 0,0 0,1.414 -1.414L20.914,19.5l2.043,-2.043Z"
|
||||||
|
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,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="M5.07,4.801C6.164,4.216 7.243,4 9.691,4h8.616c2.448,0 3.527,0.216 4.622,0.801A5.465,5.465 0,0 1,25.2 7.07c0.585,1.096 0.801,2.175 0.801,4.623v1.848c0,0.666 0,1.226 -0.037,1.683 -0.04,0.48 -0.124,0.934 -0.345,1.366a3.5,3.5 0,0 1,-1.529 1.53c-0.478,0.243 -0.988,0.322 -1.528,0.355a1,1 0,0 1,-0.122 -1.996c0.44,-0.027 0.626,-0.083 0.742,-0.142a1.5,1.5 0,0 0,0.655 -0.655c0.053,-0.103 0.104,-0.265 0.133,-0.62 0.03,-0.367 0.031,-0.844 0.031,-1.561v-1.808c0,-2.335 -0.212,-3.019 -0.565,-3.68a3.466,3.466 0,0 0,-1.448 -1.447C21.327,6.212 20.643,6 18.308,6L9.692,6c-2.335,0 -3.019,0.212 -3.68,0.565a3.466,3.466 0,0 0,-1.447 1.448C4.212,8.673 4,9.357 4,11.692L4,13.5c0,0.717 0,1.194 0.03,1.56 0.03,0.356 0.081,0.518 0.133,0.621a1.5,1.5 0,0 0,0.656 0.656c0.116,0.058 0.302,0.114 0.742,0.14a1,1 0,1 1,-0.122 1.997c-0.54,-0.033 -1.05,-0.112 -1.528,-0.355a3.5,3.5 0,0 1,-1.53 -1.53c-0.22,-0.432 -0.304,-0.887 -0.344,-1.366C2,14.766 2,14.206 2,13.54v-1.848c0,-2.448 0.216,-3.527 0.801,-4.623A5.466,5.466 0,0 1,5.07 4.801ZM18.059,23.263 L18.083,22.203a0.974,0.974 0,0 0,-0.025 0.219v0.84Z"
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="m16.942,18.55 l-0.03,-0.541a0.994,0.994 0,0 0,-1.987 0.049l0.004,-0.43a0.97,0.97 0,0 0,-1.916 -0.227l-0.024,1.06v-0.84c0,-0.076 0.008,-0.15 0.024,-0.22l0.077,-3.385a1.001,1.001 0,0 0,-2.002 -0.049l-0.155,6.095 -0.914,-1.596a0.935,0.935 0,0 0,-1.67 0.83l1.614,3.787s1.179,2.369 4.544,2.573c2.928,0.178 4.536,-1.946 4.45,-4.533l0.078,-2.6a1.048,1.048 0,1 0,-2.093 0.028Z"
|
||||||
|
android:fillColor="#000"/>
|
||||||
|
</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,15 @@
|
|||||||
|
<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.702,7.212C11.523,5.79 11.934,5.079 12.401,4.739C13.354,4.047 14.646,4.047 15.599,4.739C16.066,5.079 16.477,5.79 17.298,7.212L23.693,18.288C24.513,19.71 24.924,20.421 24.984,20.996C25.108,22.168 24.462,23.286 23.385,23.765C22.857,24 22.037,24 20.395,24H7.605C5.964,24 5.143,24 4.614,23.765C3.538,23.286 2.893,22.168 3.016,20.996C3.076,20.421 3.487,19.71 4.307,18.288L10.702,7.212Z"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#000"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M14,9.5C13.448,9.5 13,9.948 13,10.5L13,15.5C13,16.052 13.448,16.5 14,16.5C14.552,16.5 15,16.052 15,15.5V10.5C15,9.948 14.552,9.5 14,9.5ZM14,18.3C13.337,18.3 12.8,18.837 12.8,19.5C12.8,20.163 13.337,20.7 14,20.7C14.663,20.7 15.2,20.163 15.2,19.5C15.2,18.837 14.663,18.3 14,18.3Z"
|
||||||
|
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="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>
|
||||||
@@ -10,9 +10,25 @@
|
|||||||
<string name="SlotAppSettingsTooltip">Настройки приложения</string>
|
<string name="SlotAppSettingsTooltip">Настройки приложения</string>
|
||||||
<string name="MenuFile">Файл</string>
|
<string name="MenuFile">Файл</string>
|
||||||
<string name="MenuFileOpen">Открыть модель</string>
|
<string name="MenuFileOpen">Открыть модель</string>
|
||||||
|
<string name="MenuFileOpenFileLoaded">Файл загружен.</string>
|
||||||
<string name="MenuFileOpenFileFailed">Не удалось открыть модель</string>
|
<string name="MenuFileOpenFileFailed">Не удалось открыть модель</string>
|
||||||
<string name="MenuFileOpenFileFailedNullName">Не удалось определить имя файла.</string>
|
<string name="MenuFileOpenFileFailedNullName">Не удалось определить имя файла.</string>
|
||||||
|
<string name="MenuFileOpenFileBigObject">Файл содержит более 500к треугольников. Нарезка может быть медленной.</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="MenuFileAIGeneratorAlreadyGenerating">Уже генерируется другая модель.</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>
|
||||||
@@ -28,15 +44,19 @@
|
|||||||
<string name="MenuFileCalibrationsModelsCylinder">Цилиндр</string>
|
<string name="MenuFileCalibrationsModelsCylinder">Цилиндр</string>
|
||||||
<string name="MenuFileCalibrationsModelsPyramid">Пирамида</string>
|
<string name="MenuFileCalibrationsModelsPyramid">Пирамида</string>
|
||||||
<string name="MenuFileCalibrationsModelsSphere">Сфера</string>
|
<string name="MenuFileCalibrationsModelsSphere">Сфера</string>
|
||||||
<string name="MenuFileOpenFileLoaded">Файл загружен.</string>
|
|
||||||
<string name="MenuFileImportProfiles">Импорт. профилей</string>
|
<string name="MenuFileImportProfiles">Импорт. профилей</string>
|
||||||
<string name="MenuFileImportProfilesFailed">Не удалось импортировать</string>
|
<string name="MenuFileImportProfilesFailed">Не удалось импортировать</string>
|
||||||
|
<string name="MenuFileImportProfilesFailedBaseProfileNotFound">Родительский профиль не найден: %s</string>
|
||||||
<string name="MenuFileImportProfilesFailedNotIni">Не файл .ini</string>
|
<string name="MenuFileImportProfilesFailedNotIni">Не файл .ini</string>
|
||||||
|
<string name="MenuFileImportProfilesFailedEmpty">Файл не содержит профилей</string>
|
||||||
<string name="MenuFileExportProfiles">Экспорт. профилей</string>
|
<string name="MenuFileExportProfiles">Экспорт. профилей</string>
|
||||||
<string name="MenuFileExportProfilesPrints">Профили печати</string>
|
<string name="MenuFileExportProfilesPrints">Профили печати</string>
|
||||||
<string name="MenuFileExportProfilesFilaments">Филаменты</string>
|
<string name="MenuFileExportProfilesFilaments">Филаменты</string>
|
||||||
<string name="MenuFileExportProfilesPrinters">Принтеры</string>
|
<string name="MenuFileExportProfilesPrinters">Принтеры</string>
|
||||||
<string name="MenuFileExportProfilesNoProfiles">Не выбрано ни одного профиля.</string>
|
<string name="MenuFileExportProfilesNoProfiles">Не выбрано ни одного профиля.</string>
|
||||||
|
<string name="MenuFileExportProfilesSuccess">Профили успешно экспортированы.</string>
|
||||||
|
<string name="MenuFileExport3mf">Экспорт. 3mf</string>
|
||||||
|
<string name="MenuFileExport3mfSuccess">Успешно экспортировали проект.</string>
|
||||||
<string name="MenuCamera">Камера</string>
|
<string name="MenuCamera">Камера</string>
|
||||||
<string name="MenuCameraIsometric">Изомет.\nвид</string>
|
<string name="MenuCameraIsometric">Изомет.\nвид</string>
|
||||||
<string name="MenuCameraTop">Вид\nсверху</string>
|
<string name="MenuCameraTop">Вид\nсверху</string>
|
||||||
@@ -46,7 +66,11 @@
|
|||||||
<string name="MenuCameraLeft">Вид\nслева</string>
|
<string name="MenuCameraLeft">Вид\nслева</string>
|
||||||
<string name="MenuCameraRight">Вид\nсправа</string>
|
<string name="MenuCameraRight">Вид\nсправа</string>
|
||||||
<string name="MenuCameraOrtho">Ортог. проекц.</string>
|
<string name="MenuCameraOrtho">Ортог. проекц.</string>
|
||||||
<string name="MenuCameraEnableRotation">Вкл. поворот</string>
|
<string name="MenuCameraControlMode">Режим управл.</string>
|
||||||
|
<string name="MenuCameraControlModeFull">Режим управления</string>
|
||||||
|
<string name="MenuCameraControlModeOne">Один палец - поворот, два пальца - перемещение</string>
|
||||||
|
<string name="MenuCameraControlModeTwo">Один палец - перемещение, два пальца - поворот</string>
|
||||||
|
<string name="MenuCameraControlModeThree">Только перемещение</string>
|
||||||
<string name="MenuOrientation">Ориентация</string>
|
<string name="MenuOrientation">Ориентация</string>
|
||||||
<string name="MenuOrientationArrange">Расст. модели</string>
|
<string name="MenuOrientationArrange">Расст. модели</string>
|
||||||
<string name="MenuOrientationArrangeFinished">Модели расставлены.</string>
|
<string name="MenuOrientationArrangeFinished">Модели расставлены.</string>
|
||||||
@@ -72,9 +96,10 @@
|
|||||||
<string name="MenuTransform">Изменение</string>
|
<string name="MenuTransform">Изменение</string>
|
||||||
<string name="MenuTransformScale">Размер</string>
|
<string name="MenuTransformScale">Размер</string>
|
||||||
<string name="MenuTransformScaleLink">Сохранять соотношение сторон</string>
|
<string name="MenuTransformScaleLink">Сохранять соотношение сторон</string>
|
||||||
<string name="MenuTransformScaleXValue">X = %.2f %%</string>
|
<string name="MenuTransformScaleXValue">X = %.2f %% | %.2f мм</string>
|
||||||
<string name="MenuTransformScaleYValue">Y = %.2f %%</string>
|
<string name="MenuTransformScaleYValue">Y = %.2f %% | %.2f мм</string>
|
||||||
<string name="MenuTransformScaleZValue">Z = %.2f %%</string>
|
<string name="MenuTransformScaleZValue">Z = %.2f %% | %.2f мм</string>
|
||||||
|
<string name="MenuTransformScaleMM">Ввод в мм</string>
|
||||||
<string name="MenuTransformMirror">Отразить</string>
|
<string name="MenuTransformMirror">Отразить</string>
|
||||||
<string name="MenuTransformMirrorX">Отразить по X</string>
|
<string name="MenuTransformMirrorX">Отразить по X</string>
|
||||||
<string name="MenuTransformMirrorY">Отразить по Y</string>
|
<string name="MenuTransformMirrorY">Отразить по Y</string>
|
||||||
@@ -93,7 +118,7 @@
|
|||||||
<string name="MenuSliceShare">Отправка</string>
|
<string name="MenuSliceShare">Отправка</string>
|
||||||
<string name="MenuSliceSendToPrinter">Отправ. на принтер</string>
|
<string name="MenuSliceSendToPrinter">Отправ. на принтер</string>
|
||||||
<string name="MenuSliceSendToPrinterAndPrint">Отправ. на печать</string>
|
<string name="MenuSliceSendToPrinterAndPrint">Отправ. на печать</string>
|
||||||
<string name="MenuSliceSendToPrinterStarted">Загрузка началась…</string>
|
<string name="MenuSliceSendToPrinterLoading">Отправка…</string>
|
||||||
<string name="MenuSliceSendToPrinterOK">Файл отправлен на принтер.</string>
|
<string name="MenuSliceSendToPrinterOK">Файл отправлен на принтер.</string>
|
||||||
<string name="MenuSliceSendToPrinterPrintStarted">Файл отправлен, начало печати…</string>
|
<string name="MenuSliceSendToPrinterPrintStarted">Файл отправлен, начало печати…</string>
|
||||||
<string name="MenuSliceSendToPrinterFailed">Не удалось отправить файл</string>
|
<string name="MenuSliceSendToPrinterFailed">Не удалось отправить файл</string>
|
||||||
@@ -104,6 +129,8 @@
|
|||||||
<string name="IntroLetStartWithColor">Давайте начнём с вашего любимого цвета.</string>
|
<string name="IntroLetStartWithColor">Давайте начнём с вашего любимого цвета.</string>
|
||||||
<string name="IntroFailedToLoadRepos">Не удалось загрузить репозитории!</string>
|
<string name="IntroFailedToLoadRepos">Не удалось загрузить репозитории!</string>
|
||||||
<string name="IntroSelectRepos">Выберите репозитории для загрузки профилей.</string>
|
<string name="IntroSelectRepos">Выберите репозитории для загрузки профилей.</string>
|
||||||
|
<string name="IntroImportFromCloud">Импортировать из облака Beam</string>
|
||||||
|
<string name="IntroImportOr">Или</string>
|
||||||
<string name="IntroCustomProfile">Использовать пользовательский профиль</string>
|
<string name="IntroCustomProfile">Использовать пользовательский профиль</string>
|
||||||
<string name="IntroCustomProfileHeader">Пользовательские</string>
|
<string name="IntroCustomProfileHeader">Пользовательские</string>
|
||||||
<string name="IntroCustomProfileName">Пользовательский профиль</string>
|
<string name="IntroCustomProfileName">Пользовательский профиль</string>
|
||||||
@@ -143,6 +170,33 @@
|
|||||||
<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="SettingsCloudManageFeatureEarlyAccess">Ранний доступ</string>
|
||||||
|
<string name="SettingsCloudManageFeatureEarlyAccessDescription">Вы можете скачать ранние сборки из чата Telegram для подписчиков</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">Ваш ник будет написан в списке поддержавших.\nСпасибо за вашу поддержку!</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>
|
||||||
@@ -150,9 +204,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>
|
||||||
@@ -34,4 +34,9 @@
|
|||||||
<attr name="yTrackColor" format="reference|color"/>
|
<attr name="yTrackColor" format="reference|color"/>
|
||||||
<attr name="zTrackColor" format="reference|color"/>
|
<attr name="zTrackColor" format="reference|color"/>
|
||||||
<attr name="textColorNegative" format="reference|color"/>
|
<attr name="textColorNegative" format="reference|color"/>
|
||||||
|
<attr name="snackbarBase" format="reference|color"/>
|
||||||
|
<attr name="snackbarDone" format="reference|color"/>
|
||||||
|
<attr name="snackbarWarning" format="reference|color"/>
|
||||||
|
<attr name="snackbarInfo" format="reference|color"/>
|
||||||
|
<attr name="snackbarError" format="reference|color"/>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -14,7 +14,22 @@
|
|||||||
<string name="MenuFileOpenFileLoaded">File loaded.</string>
|
<string name="MenuFileOpenFileLoaded">File loaded.</string>
|
||||||
<string name="MenuFileOpenFileFailed">Failed to open model</string>
|
<string name="MenuFileOpenFileFailed">Failed to open model</string>
|
||||||
<string name="MenuFileOpenFileFailedNullName">Failed to resolve file name.</string>
|
<string name="MenuFileOpenFileFailedNullName">Failed to resolve file name.</string>
|
||||||
|
<string name="MenuFileOpenFileBigObject">File has more than 500k triangles. Processing could be slow.</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="MenuFileAIGeneratorAlreadyGenerating">Already generating another model.</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>
|
||||||
@@ -33,12 +48,17 @@
|
|||||||
<string name="MenuFileCalibrationsModelsSphere">Sphere</string>
|
<string name="MenuFileCalibrationsModelsSphere">Sphere</string>
|
||||||
<string name="MenuFileImportProfiles">Import profiles</string>
|
<string name="MenuFileImportProfiles">Import profiles</string>
|
||||||
<string name="MenuFileImportProfilesFailed">Import failed</string>
|
<string name="MenuFileImportProfilesFailed">Import failed</string>
|
||||||
|
<string name="MenuFileImportProfilesFailedBaseProfileNotFound">Parent profile not found: %s</string>
|
||||||
<string name="MenuFileImportProfilesFailedNotIni">Not an .ini file</string>
|
<string name="MenuFileImportProfilesFailedNotIni">Not an .ini file</string>
|
||||||
|
<string name="MenuFileImportProfilesFailedEmpty">File does not contain profiles</string>
|
||||||
<string name="MenuFileExportProfiles">Export profiles</string>
|
<string name="MenuFileExportProfiles">Export profiles</string>
|
||||||
<string name="MenuFileExportProfilesPrints">Print configs</string>
|
<string name="MenuFileExportProfilesPrints">Print configs</string>
|
||||||
<string name="MenuFileExportProfilesFilaments">Filaments</string>
|
<string name="MenuFileExportProfilesFilaments">Filaments</string>
|
||||||
<string name="MenuFileExportProfilesPrinters">Printers</string>
|
<string name="MenuFileExportProfilesPrinters">Printers</string>
|
||||||
<string name="MenuFileExportProfilesNoProfiles">No profiles selected.</string>
|
<string name="MenuFileExportProfilesNoProfiles">No profiles selected.</string>
|
||||||
|
<string name="MenuFileExportProfilesSuccess">Exported profiles successfully.</string>
|
||||||
|
<string name="MenuFileExport3mf">Export 3mf</string>
|
||||||
|
<string name="MenuFileExport3mfSuccess">Successfully exported project.</string>
|
||||||
<string name="MenuCamera">Camera</string>
|
<string name="MenuCamera">Camera</string>
|
||||||
<string name="MenuCameraIsometric">Isom. view</string>
|
<string name="MenuCameraIsometric">Isom. view</string>
|
||||||
<string name="MenuCameraTop">Top view</string>
|
<string name="MenuCameraTop">Top view</string>
|
||||||
@@ -48,7 +68,11 @@
|
|||||||
<string name="MenuCameraLeft">Left view</string>
|
<string name="MenuCameraLeft">Left view</string>
|
||||||
<string name="MenuCameraRight">Right view</string>
|
<string name="MenuCameraRight">Right view</string>
|
||||||
<string name="MenuCameraOrtho">Orth. project.</string>
|
<string name="MenuCameraOrtho">Orth. project.</string>
|
||||||
<string name="MenuCameraEnableRotation">Allow rotation</string>
|
<string name="MenuCameraControlMode">Control mode</string>
|
||||||
|
<string name="MenuCameraControlModeFull">Control mode</string>
|
||||||
|
<string name="MenuCameraControlModeOne">One finger - rotation, two fingers - movement</string>
|
||||||
|
<string name="MenuCameraControlModeTwo">One finger - movement, two fingers - rotation</string>
|
||||||
|
<string name="MenuCameraControlModeThree">Only movement</string>
|
||||||
<string name="MenuOrientation">Orientation</string>
|
<string name="MenuOrientation">Orientation</string>
|
||||||
<string name="MenuOrientationArrange">Arrange models</string>
|
<string name="MenuOrientationArrange">Arrange models</string>
|
||||||
<string name="MenuOrientationArrangeFinished">Models arranged.</string>
|
<string name="MenuOrientationArrangeFinished">Models arranged.</string>
|
||||||
@@ -74,9 +98,10 @@
|
|||||||
<string name="MenuTransform">Transform</string>
|
<string name="MenuTransform">Transform</string>
|
||||||
<string name="MenuTransformScale">Scale</string>
|
<string name="MenuTransformScale">Scale</string>
|
||||||
<string name="MenuTransformScaleLink">Save aspect ratio</string>
|
<string name="MenuTransformScaleLink">Save aspect ratio</string>
|
||||||
<string name="MenuTransformScaleXValue">X = %.2f %%</string>
|
<string name="MenuTransformScaleXValue">X = %.2f %% | %.2f mm</string>
|
||||||
<string name="MenuTransformScaleYValue">Y = %.2f %%</string>
|
<string name="MenuTransformScaleYValue">Y = %.2f %% | %.2f mm</string>
|
||||||
<string name="MenuTransformScaleZValue">Z = %.2f %%</string>
|
<string name="MenuTransformScaleZValue">Z = %.2f %% | %.2f mm</string>
|
||||||
|
<string name="MenuTransformScaleMM">Input in mm</string>
|
||||||
<string name="MenuTransformMirror">Mirror</string>
|
<string name="MenuTransformMirror">Mirror</string>
|
||||||
<string name="MenuTransformMirrorX">Mirror X</string>
|
<string name="MenuTransformMirrorX">Mirror X</string>
|
||||||
<string name="MenuTransformMirrorY">Mirror Y</string>
|
<string name="MenuTransformMirrorY">Mirror Y</string>
|
||||||
@@ -95,7 +120,7 @@
|
|||||||
<string name="MenuSliceShare">Share</string>
|
<string name="MenuSliceShare">Share</string>
|
||||||
<string name="MenuSliceSendToPrinter">Send to printer</string>
|
<string name="MenuSliceSendToPrinter">Send to printer</string>
|
||||||
<string name="MenuSliceSendToPrinterAndPrint">Send and print</string>
|
<string name="MenuSliceSendToPrinterAndPrint">Send and print</string>
|
||||||
<string name="MenuSliceSendToPrinterStarted">Upload started…</string>
|
<string name="MenuSliceSendToPrinterLoading">Uploading…</string>
|
||||||
<string name="MenuSliceSendToPrinterOK">File sent to printer.</string>
|
<string name="MenuSliceSendToPrinterOK">File sent to printer.</string>
|
||||||
<string name="MenuSliceSendToPrinterPrintStarted">File sent, starting print…</string>
|
<string name="MenuSliceSendToPrinterPrintStarted">File sent, starting print…</string>
|
||||||
<string name="MenuSliceSendToPrinterFailed">Failed to send file</string>
|
<string name="MenuSliceSendToPrinterFailed">Failed to send file</string>
|
||||||
@@ -106,6 +131,8 @@
|
|||||||
<string name="IntroLetStartWithColor">Let\'s start with your favorite color.</string>
|
<string name="IntroLetStartWithColor">Let\'s start with your favorite color.</string>
|
||||||
<string name="IntroFailedToLoadRepos">Failed to load repositories from network!</string>
|
<string name="IntroFailedToLoadRepos">Failed to load repositories from network!</string>
|
||||||
<string name="IntroSelectRepos">Select repositories to fetch profiles.</string>
|
<string name="IntroSelectRepos">Select repositories to fetch profiles.</string>
|
||||||
|
<string name="IntroImportFromCloud">Import from Beam Cloud</string>
|
||||||
|
<string name="IntroImportOr">Or</string>
|
||||||
<string name="IntroCustomProfile">Use custom profile</string>
|
<string name="IntroCustomProfile">Use custom profile</string>
|
||||||
<string name="IntroCustomProfileHeader">Custom</string>
|
<string name="IntroCustomProfileHeader">Custom</string>
|
||||||
<string name="IntroCustomProfileName">Custom profile</string>
|
<string name="IntroCustomProfileName">Custom profile</string>
|
||||||
@@ -145,6 +172,33 @@
|
|||||||
<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="SettingsCloudManageFeatureEarlyAccess">Early access</string>
|
||||||
|
<string name="SettingsCloudManageFeatureEarlyAccessDescription">You can download early builds from Telegram chat for subscribers</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">Your nickname will be written in the list of supporters.\nThanks 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>
|
||||||
@@ -153,9 +207,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>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Base.Theme.SliceBeam" parent="Theme.Material3.Light.NoActionBar">
|
<style name="Base.Theme.SliceBeam" parent="Theme.Material3.Light.NoActionBar">
|
||||||
<item name="android:windowBackground">#FFF</item>
|
<item name="android:windowBackground">#FFF</item>
|
||||||
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/icon_adaptive_foreground</item>
|
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/icon_adaptive_foreground</item>
|
||||||
|
|||||||
+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