init
|
|
@ -0,0 +1,18 @@
|
||||||
|
# https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{java,scala,groovy,kt,kts}]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.gradle]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
*.bat text=auto eol=crlf
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/core/
|
||||||
|
/build/
|
||||||
|
/gradle/
|
||||||
|
/gradlew
|
||||||
|
/local.properties
|
||||||
|
/gradlew.bat
|
||||||
|
|
||||||
|
|
||||||
|
## Java
|
||||||
|
|
||||||
|
*.class
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
## Robovm
|
||||||
|
/ios/robovm-build/
|
||||||
|
|
||||||
|
## GWT
|
||||||
|
/html/war/
|
||||||
|
/html/gwt-unitCache/
|
||||||
|
.apt_generated/
|
||||||
|
.gwt/
|
||||||
|
gwt-unitCache/
|
||||||
|
www-test/
|
||||||
|
.gwt-tmp/
|
||||||
|
|
||||||
|
## Android Studio and Intellij and Android in general
|
||||||
|
/android/libs/armeabi-v7a/
|
||||||
|
/android/libs/arm64-v8a/
|
||||||
|
/android/libs/x86/
|
||||||
|
/android/libs/x86_64/
|
||||||
|
/android/gen/
|
||||||
|
.idea/
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
/android/out/
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
|
||||||
|
## Eclipse
|
||||||
|
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.metadata/
|
||||||
|
/android/bin/
|
||||||
|
/core/bin/
|
||||||
|
/desktop/bin/
|
||||||
|
/html/bin/
|
||||||
|
/ios/bin/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.externalToolBuilders/
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
## NetBeans
|
||||||
|
|
||||||
|
/nbproject/private/
|
||||||
|
/android/nbproject/private/
|
||||||
|
/core/nbproject/private/
|
||||||
|
/desktop/nbproject/private/
|
||||||
|
/html/nbproject/private/
|
||||||
|
/ios/nbproject/private/
|
||||||
|
|
||||||
|
/build/
|
||||||
|
/android/build/
|
||||||
|
/core/build/
|
||||||
|
/desktop/build/
|
||||||
|
/html/build/
|
||||||
|
/ios/build/
|
||||||
|
|
||||||
|
/nbbuild/
|
||||||
|
/android/nbbuild/
|
||||||
|
/core/nbbuild/
|
||||||
|
/desktop/nbbuild/
|
||||||
|
/html/nbbuild/
|
||||||
|
/ios/nbbuild/
|
||||||
|
|
||||||
|
/dist/
|
||||||
|
/android/dist/
|
||||||
|
/core/dist/
|
||||||
|
/desktop/dist/
|
||||||
|
/html/dist/
|
||||||
|
/ios/dist/
|
||||||
|
|
||||||
|
/nbdist/
|
||||||
|
/android/nbdist/
|
||||||
|
/core/nbdist/
|
||||||
|
/desktop/nbdist/
|
||||||
|
/html/nbdist/
|
||||||
|
/ios/nbdist/
|
||||||
|
|
||||||
|
nbactions.xml
|
||||||
|
nb-configuration.xml
|
||||||
|
|
||||||
|
## Gradle
|
||||||
|
|
||||||
|
/local.properties
|
||||||
|
.gradle/
|
||||||
|
gradle-app.setting
|
||||||
|
/build/
|
||||||
|
/android/build/
|
||||||
|
/core/build/
|
||||||
|
/desktop/build/
|
||||||
|
/html/build/
|
||||||
|
/ios/build/
|
||||||
|
|
||||||
|
## OS Specific
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
## iOS
|
||||||
|
/ios/xcode/*.xcodeproj/*
|
||||||
|
!/ios/xcode/*.xcodeproj/xcshareddata
|
||||||
|
!/ios/xcode/*.xcodeproj/project.pbxproj
|
||||||
|
/ios/xcode/native/
|
||||||
|
/ios/IOSLauncher.app
|
||||||
|
/ios/IOSLauncher.app.dSYM
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-feature android:name="android.hardware.camera"/>
|
||||||
|
|
||||||
|
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||||
|
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Dermy_app"
|
||||||
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
android {
|
||||||
|
namespace "xyz.r0r5chach.dermy_app"
|
||||||
|
compileSdk 35
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
manifest.srcFile 'AndroidManifest.xml'
|
||||||
|
java.srcDirs = ['src']
|
||||||
|
aidl.srcDirs = ['src']
|
||||||
|
renderscript.srcDirs = ['src']
|
||||||
|
res.srcDirs = ['res']
|
||||||
|
assets.srcDirs = ['../assets']
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
exclude 'META-INF/robovm/ios/robovm.xml'
|
||||||
|
exclude 'META-INF/native-image/org.mongodb/bson/native-image.properties'
|
||||||
|
}
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "xyz.r0r5chach.dermy_app"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 35
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
renderScript true
|
||||||
|
aidl true
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
coreLibraryDesugaringEnabled false
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "androidx.room:room-ktx:$roomVersion"
|
||||||
|
implementation "androidx.room:room-runtime:$roomVersion"
|
||||||
|
ksp "androidx.room:room-compiler:$roomVersion"
|
||||||
|
}
|
||||||
|
|
||||||
|
// called every time gradle gets executed, takes the native dependencies of
|
||||||
|
// the natives configuration, and extracts them to the proper libs/ folders
|
||||||
|
// so they get packed with the APK.
|
||||||
|
tasks.register('copyAndroidNatives') {
|
||||||
|
doFirst {
|
||||||
|
file("libs/armeabi-v7a/").mkdirs()
|
||||||
|
file("libs/arm64-v8a/").mkdirs()
|
||||||
|
file("libs/x86_64/").mkdirs()
|
||||||
|
file("libs/x86/").mkdirs()
|
||||||
|
|
||||||
|
configurations.natives.copy().files.each { jar ->
|
||||||
|
def outputDir = null
|
||||||
|
if (jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a")
|
||||||
|
if (jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a")
|
||||||
|
if (jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64")
|
||||||
|
if (jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86")
|
||||||
|
if (outputDir != null) {
|
||||||
|
copy {
|
||||||
|
from zipTree(jar)
|
||||||
|
into outputDir
|
||||||
|
include "*.so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.matching { it.name.contains("merge") && it.name.contains("JniLibFolders") }.configureEach { packageTask ->
|
||||||
|
packageTask.dependsOn 'copyAndroidNatives'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register('run', Exec) {
|
||||||
|
def path
|
||||||
|
def localProperties = project.file("../local.properties")
|
||||||
|
if (localProperties.exists()) {
|
||||||
|
Properties properties = new Properties()
|
||||||
|
localProperties.withInputStream { instr ->
|
||||||
|
properties.load(instr)
|
||||||
|
}
|
||||||
|
def sdkDir = properties.getProperty('sdk.dir')
|
||||||
|
if (sdkDir) {
|
||||||
|
path = sdkDir
|
||||||
|
} else {
|
||||||
|
path = "$System.env.ANDROID_HOME"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path = "$System.env.ANDROID_HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
def adb = path + "/platform-tools/adb"
|
||||||
|
commandLine "$adb", 'shell', 'am', 'start', '-n', 'xyz.r0r5chach.dermy_app/xyz.r0r5chach.dermy_app.MainActivity'
|
||||||
|
}
|
||||||
|
|
||||||
|
eclipse.project.name = appName + "-android"
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# To enable ProGuard in your project, edit project.properties
|
||||||
|
# to define the proguard.config property as described in that file.
|
||||||
|
#
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in ${sdk.dir}/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the ProGuard
|
||||||
|
# include property in project.properties.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
-verbose
|
||||||
|
|
||||||
|
-dontwarn com.badlogic.gdx.backends.android.AndroidFragmentApplication
|
||||||
|
|
||||||
|
# Required if using Gdx-Controllers extension
|
||||||
|
|
||||||
|
# Required if using Box2D extension
|
||||||
|
-keep class androidx.room.* { *; }
|
||||||
|
-dontwarn androidx.room.**
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# This file is used by the Eclipse ADT plugin. It is unnecessary for IDEA and Android Studio projects, which
|
||||||
|
# configure Proguard and the Android target via the build.gradle file.
|
||||||
|
|
||||||
|
# To enable ProGuard to work with Eclipse ADT, uncomment this (available properties: sdk.dir, user.home)
|
||||||
|
# and ensure proguard.jar in the Android SDK is up to date (or alternately reduce the android target to 23 or lower):
|
||||||
|
# proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-rules.pro
|
||||||
|
|
||||||
|
# Project target.
|
||||||
|
target=android-19
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M13.0001 10.9999L22.0002 10.9997L22.0002 12.9997L13.0001 12.9999L13.0001 21.9998L11.0001 21.9998L11.0001 12.9999L2.00004 13.0001L2 11.0001L11.0001 10.9999L11 2.00025L13 2.00024L13.0001 10.9999Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="50dp"
|
||||||
|
android:height="50dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M16,2L21,7V21.008C21,21.556 20.555,22 20.007,22H3.993C3.445,22 3,21.545 3,21.008V2.992C3,2.444 3.445,2 3.993,2H16ZM11,11H8V13H11V16H13V13H16V11H13V8H11V11Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="#000000"/>
|
||||||
|
<size
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"/>
|
||||||
|
</shape>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12 1L21.5 6.5V17.5L12 23L2.5 17.5V6.5L12 1ZM12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M3 3H12.382C12.7607 3 13.107 3.214 13.2764 3.55279L14 5H20C20.5523 5 21 5.44772 21 6V17C21 17.5523 20.5523 18 20 18H13.618C13.2393 18 12.893 17.786 12.7236 17.4472L12 16H5V22H3V3Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="50dp"
|
||||||
|
android:height="50dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M21,20C21,20.552 20.552,21 20,21H4C3.448,21 3,20.552 3,20V9.489C3,9.18 3.142,8.889 3.386,8.7L11.386,2.477C11.747,2.197 12.253,2.197 12.614,2.477L20.614,8.7C20.858,8.889 21,9.18 21,9.489V20Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M13.0607 8.11097L14.4749 9.52518C17.2086 12.2589 17.2086 16.691 14.4749 19.4247L14.1214 19.7782C11.3877 22.5119 6.95555 22.5119 4.22188 19.7782C1.48821 17.0446 1.48821 12.6124 4.22188 9.87874L5.6361 11.293C3.68348 13.2456 3.68348 16.4114 5.6361 18.364C7.58872 20.3166 10.7545 20.3166 12.7072 18.364L13.0607 18.0105C15.0133 16.0578 15.0133 12.892 13.0607 10.9394L11.6465 9.52518L13.0607 8.11097ZM19.7782 14.1214L18.364 12.7072C20.3166 10.7545 20.3166 7.58872 18.364 5.6361C16.4114 3.68348 13.2456 3.68348 11.293 5.6361L10.9394 5.98965C8.98678 7.94227 8.98678 11.1081 10.9394 13.0607L12.3536 14.4749L10.9394 15.8891L9.52518 14.4749C6.79151 11.7413 6.79151 7.30911 9.52518 4.57544L9.87874 4.22188C12.6124 1.48821 17.0446 1.48821 19.7782 4.22188C22.5119 6.95555 22.5119 11.3877 19.7782 14.1214Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M2 5L9 2L15 5L21.303 2.2987C21.5569 2.18992 21.8508 2.30749 21.9596 2.56131C21.9862 2.62355 22 2.69056 22 2.75827V19L15 22L9 19L2.69696 21.7013C2.44314 21.8101 2.14921 21.6925 2.04043 21.4387C2.01375 21.3765 2 21.3094 2 21.2417V5ZM16 19.3955L20 17.6812V5.03308L16 6.74736V19.3955ZM14 19.2639V6.73607L10 4.73607V17.2639L14 19.2639ZM8 17.2526V4.60451L4 6.31879V18.9669L8 17.2526Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="50dp"
|
||||||
|
android:height="50dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M15.224,15.508L13.011,20.158C12.869,20.457 12.511,20.584 12.212,20.442C12.145,20.41 12.085,20.367 12.034,20.313L8.492,16.574C8.397,16.474 8.271,16.41 8.134,16.392L3.028,15.724C2.7,15.681 2.468,15.38 2.511,15.052C2.521,14.978 2.544,14.908 2.579,14.843L5.04,10.319C5.106,10.198 5.128,10.058 5.103,9.923L4.16,4.86C4.099,4.534 4.314,4.221 4.64,4.16C4.713,4.147 4.787,4.147 4.86,4.16L9.922,5.103C10.058,5.129 10.198,5.106 10.319,5.04L14.842,2.579C15.134,2.421 15.498,2.528 15.656,2.819C15.692,2.884 15.715,2.955 15.724,3.028L16.392,8.135C16.41,8.271 16.474,8.398 16.574,8.492L20.313,12.034C20.553,12.262 20.564,12.642 20.336,12.882C20.285,12.936 20.225,12.979 20.158,13.011L15.508,15.224C15.383,15.283 15.283,15.384 15.224,15.508ZM16.021,17.435L17.435,16.021L21.677,20.263L20.263,21.678L16.021,17.435Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="50dp"
|
||||||
|
android:height="50dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M18,21V13H6V21H4C3.448,21 3,20.552 3,20V4C3,3.448 3.448,3 4,3H17L21,7V20C21,20.552 20.552,21 20,21H18ZM16,21H8V15H16V21Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M4 3H20C20.5523 3 21 3.44772 21 4V11H3V4C3 3.44772 3.44772 3 4 3ZM3 13H21V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V13ZM7 16V18H10V16H7ZM7 6V8H10V6H7Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="200dp"
|
||||||
|
android:height="200dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M4 22C4 17.5817 7.58172 14 12 14C16.4183 14 20 17.5817 20 22H4ZM12 13C8.685 13 6 10.315 6 7C6 3.685 8.685 1 12 1C15.315 1 18 3.685 18 7C18 10.315 15.315 13 12 13Z"
|
||||||
|
android:fillColor="?attr/buttonIcon"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/home_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="25dp"
|
||||||
|
android:background="@drawable/home_icon"
|
||||||
|
android:contentDescription="@string/string_home"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/fragment_container"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/fragment_container" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/username_field"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:autofillHints="username"
|
||||||
|
android:hint="@string/string_username"
|
||||||
|
android:inputType="text"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/password_field"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/password_field"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:background="@null"
|
||||||
|
android:autofillHints="password"
|
||||||
|
android:hint="@string/string_password"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/login_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/username_field"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/login_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="@string/string_login"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/register_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/password_field"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/register_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="@string/string_register"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/backup_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/login_button"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/backup_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="@string/string_backup"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/restore_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/login_button"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/restore_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="@string/string_restore"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/logout_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/backup_button"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/logout_button"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:text="@string/string_logout"
|
||||||
|
app:layout_constrainedHeight="true"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_min="48dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/restore_button"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_min="250dp"
|
||||||
|
app:layout_constraintWidth_percent="0.25"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<androidx.camera.view.PreviewView
|
||||||
|
android:id="@+id/camera_preview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
|
</androidx.camera.view.PreviewView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/add_mole_button"
|
||||||
|
android:background="@drawable/add_icon"
|
||||||
|
android:contentDescription="@string/string_add_mole"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/links_button"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/map_button"
|
||||||
|
app:layout_constraintHeight_percent="0.15"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_percent="0.28" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/map_button"
|
||||||
|
android:background="@drawable/map_icon"
|
||||||
|
android:contentDescription="@string/string_open_body_map"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/settings_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_percent="0.15"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/add_mole_button"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_percent="0.28" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/links_button"
|
||||||
|
android:background="@drawable/links_icon"
|
||||||
|
android:contentDescription="@string/string_open_useful_links"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/settings_button"
|
||||||
|
app:layout_constraintHeight_percent="0.15"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/add_mole_button"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_percent="0.28" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/settings_button"
|
||||||
|
android:background="@drawable/cog_icon"
|
||||||
|
android:contentDescription="@string/string_open_settings"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_percent="0.15"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/links_button"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/map_button"
|
||||||
|
app:layout_constraintWidth_max="320dp"
|
||||||
|
app:layout_constraintWidth_percent="0.28" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/links_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_name_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/location_empty_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_you_have_no_moles_here"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/location_name_text" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/moles_list"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/location_name_text">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,292 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:minHeight="48dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/mole_image"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="100dp"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:layout_marginEnd="100dp"
|
||||||
|
android:contentDescription="@string/string_mole_image"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/mole_details"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/circle_button" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/mole_details"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/mole_logs"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mole_image">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mole_location"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:layout_marginBottom="25dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/mole_predictions"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mole_location_header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_location"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
tools:layout_editor_absoluteY="27dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mole_side"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:layout_editor_absoluteY="37dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/side_header"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_side" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/side_value"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints=""
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="text"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
tools:layout_editor_absoluteY="19dp"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mole_body_part"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:layout_editor_absoluteY="101dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/body_part_header"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_body_part" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/body_part_value"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints=""
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="text"
|
||||||
|
tools:ignore="LabelFor"
|
||||||
|
tools:layout_editor_absoluteY="20dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mole_predictions"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:layout_marginBottom="25dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/mole_location"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mole_predictions_header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_prediction"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
tools:layout_editor_absoluteY="240dp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mole_benign_prediction"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:layout_editor_absoluteY="250dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/benign_prediction_header"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_benign_probability" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/benign_prediction_value"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints=""
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="text"
|
||||||
|
tools:ignore="LabelFor"
|
||||||
|
tools:layout_editor_absoluteY="100dp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/mole_malignant_prediction"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:layout_editor_absoluteY="350dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/malignant_prediction_header"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_malignant_probability" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/malignant_prediction_value"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints=""
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="text"
|
||||||
|
tools:ignore="LabelFor" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/mole_logs"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/mole_buttons"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mole_details">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/log_empty_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/string_this_mole_has_no_logs"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/mole_logs_header"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:text="@string/string_logs"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/log_list"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/log_list"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mole_logs_header">
|
||||||
|
|
||||||
|
</androidx.recyclerview.widget.RecyclerView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/mole_buttons"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginTop="25dp"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:layout_marginBottom="25dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mole_logs">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/mole_add_log_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="25dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:background="@drawable/add_log_icon"
|
||||||
|
android:contentDescription="@string/string_add_log"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/mole_predict_button"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mole_save_button" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/mole_predict_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="25dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:background="@drawable/predict_icon"
|
||||||
|
android:contentDescription="@string/string_new_prediction"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/mole_add_log_button"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/mole_save_button" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/mole_save_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/save_icon"
|
||||||
|
android:contentDescription="@string/save_mole"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/link_item_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:layout_marginBottom="50dp"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/mole_list_image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/circle_button" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
<resources>
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.Dermy_app" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="buttonIcon">@color/white</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Dermy</string>
|
||||||
|
<string name="string_add_mole">Add Mole</string>
|
||||||
|
<string name="string_open_body_map">Open Body Map</string>
|
||||||
|
<string name="string_open_useful_links">Open Useful Links</string>
|
||||||
|
<string name="string_open_settings">Open Settings</string>
|
||||||
|
<string name="string_login">Login</string>
|
||||||
|
<string name="string_username">username</string>
|
||||||
|
<string name="string_password">password</string>
|
||||||
|
<string name="string_backup">Backup</string>
|
||||||
|
<string name="string_restore">Restore</string>
|
||||||
|
<string name="string_logout">Logout</string>
|
||||||
|
<string name="string_register">Register</string>
|
||||||
|
<string name="disclaimer"><!-- TODO: Remove or change this placeholder text -->
|
||||||
|
|
||||||
|
1. General Information
|
||||||
|
\nThe information provided by Dermy (“we,” “us” or “our”) on The Dermy App (the “App”) is for general informational purposes only and is based on a deep learning algorithm. While our model, based on MobileNetV3, attempts to predict the likelihood of a mole being malignant or benign, it has an accuracy rating of approximately 80%. All information on the App is provided in good faith; however, we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability, or completeness of any information on the App.
|
||||||
|
\n\n2. Not Medical Advice
|
||||||
|
\nThe App cannot and does not provide medical advice. The health information provided is for general informational and educational purposes only and is not a substitute for professional medical advice, diagnosis, or treatment. Always seek the advice of your physician or other qualified health provider with any questions you may have regarding a medical condition. Never disregard professional medical advice or delay in seeking it because of something you have read on this App.
|
||||||
|
\n\n3. Limitation of Accuracy
|
||||||
|
\nOur model\'s predictions should not be taken as definitive or conclusive. While our algorithm uses advanced techniques, it is not infallible and its accuracy is limited to approximately 80%. The results generated by the App should not be used as a sole basis for making health decisions. Users should follow up with a qualified healthcare professional for any concerns regarding their health.
|
||||||
|
\n\n4. Personal Responsibility
|
||||||
|
\nYou acknowledge you are using our App voluntarily and that any choices, actions, and results now and in the future are solely your responsibility. We strongly encourage you to consult with a healthcare professional before making any health-related decisions based on the information provided by the App. We will not be liable to you or any other party for any decision made or action taken in reliance on the information given in the App.
|
||||||
|
\n\n5. External Links Disclaimer
|
||||||
|
\nThe App may contain (or you may be sent through the App) links to other websites or content belonging to or originating from third parties or links to websites and features in banners or other advertising. Such external links are not investigated, monitored, or checked for accuracy, adequacy, validity, reliability, availability, or completeness by us. We do not warrant, endorse, guarantee, or assume responsibility for the accuracy or reliability of any information offered by third-party websites linked through the App or any website or feature linked in any banner or other advertising. We will not be a party to or in any way be responsible for monitoring any transaction between you and third-party providers of products or services.</string>
|
||||||
|
<string name="string_home">Home</string>
|
||||||
|
<string name="string_you_have_no_moles_here">You have No Moles Here!</string>
|
||||||
|
<string name="string_mole_image">Mole Image</string>
|
||||||
|
<string name="string_location">Location</string>
|
||||||
|
<string name="string_side">Side</string>
|
||||||
|
<string name="string_name">Name</string>
|
||||||
|
<string name="string_body_part">Body Part</string>
|
||||||
|
<string name="string_prediction">Prediction</string>
|
||||||
|
<string name="string_benign_probability">Benign Probability</string>
|
||||||
|
<string name="string_malignant_probability">Malignant Probability</string>
|
||||||
|
<string name="string_logs">Logs</string>
|
||||||
|
<string name="string_add_log">Add Log</string>
|
||||||
|
<string name="string_new_prediction">New Prediction</string>
|
||||||
|
<string name="save_mole">Save Mole</string>
|
||||||
|
<string name="string_this_mole_has_no_logs">This Mole Has No Logs!</string>
|
||||||
|
|
||||||
|
<string-array name="links">
|
||||||
|
<item>https://www.nhs.uk/service-search/other-health-services/dermatology</item>
|
||||||
|
<item>https://www.britishskinfoundation.org.uk/news/find-your-nearest-dermatology-clinic</item>
|
||||||
|
<item>https://www.privatehealth.co.uk/dermatology/specialists/</item>
|
||||||
|
<item>https://www.sknclinics.co.uk/treatments-and-pricing/dermatology</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<resources>
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.Dermy_app" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="buttonIcon">@color/black</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample backup rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/guide/topics/data/autobackup
|
||||||
|
for details.
|
||||||
|
Note: This file is ignored for devices older that API 31
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore
|
||||||
|
-->
|
||||||
|
<full-backup-content>
|
||||||
|
<!--
|
||||||
|
<include domain="sharedpref" path="."/>
|
||||||
|
<exclude domain="sharedpref" path="device.xml"/>
|
||||||
|
-->
|
||||||
|
</full-backup-content>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample data extraction rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||||
|
for details.
|
||||||
|
-->
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup>
|
||||||
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
-->
|
||||||
|
</cloud-backup>
|
||||||
|
<!--
|
||||||
|
<device-transfer>
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
</device-transfer>
|
||||||
|
-->
|
||||||
|
</data-extraction-rules>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="Account"
|
||||||
|
app:allowDividerAbove="false"
|
||||||
|
app:allowDividerBelow="false"
|
||||||
|
app:icon="@drawable/user_icon">
|
||||||
|
<xyz.r0r5chach.dermy_app.AccountPreference
|
||||||
|
app:key="api_key"
|
||||||
|
app:layout="@layout/fragment_account" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="Server"
|
||||||
|
app:icon="@drawable/server_icon"
|
||||||
|
app:allowDividerAbove="false"
|
||||||
|
app:allowDividerBelow="false">
|
||||||
|
<EditTextPreference
|
||||||
|
app:defaultValue="https://dermy.r0r-5chach.xyz/"
|
||||||
|
app:key="instance_url"
|
||||||
|
app:title="Instance URL" />
|
||||||
|
<Preference
|
||||||
|
app:key="disclaimer"
|
||||||
|
app:title="Disclaimer"
|
||||||
|
app:icon="@drawable/flag_icon"/>
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("xyz.r0r5chach.dermy_app", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
fun addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.findFragment
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.preference.PreferenceViewHolder
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import org.json.JSONObject
|
||||||
|
import xyz.r0r5chach.dermy_app.api.DermyApi
|
||||||
|
import xyz.r0r5chach.dermy_app.fragments.SettingsFragment
|
||||||
|
|
||||||
|
|
||||||
|
@DelicateCoroutinesApi
|
||||||
|
class AccountPreference @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
): Preference(context, attrs, defStyleAttr) {
|
||||||
|
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
private lateinit var usernameField: EditText
|
||||||
|
private lateinit var passwordField: EditText
|
||||||
|
private lateinit var loggedIn: List<View>
|
||||||
|
private lateinit var loggedOut: List<View>
|
||||||
|
|
||||||
|
init {
|
||||||
|
widgetLayoutResource = R.layout.fragment_account
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||||
|
super.onBindViewHolder(holder)
|
||||||
|
usernameField = holder.findViewById(R.id.username_field) as EditText
|
||||||
|
passwordField = holder.findViewById(R.id.password_field) as EditText
|
||||||
|
|
||||||
|
val loginButton = holder.findViewById(R.id.login_button)
|
||||||
|
val registerButton = holder.findViewById(R.id.register_button)
|
||||||
|
val logOutButton = holder.findViewById(R.id.logout_button)
|
||||||
|
val backupButton = holder.findViewById(R.id.backup_button)
|
||||||
|
val restoreButton = holder.findViewById(R.id.restore_button)
|
||||||
|
|
||||||
|
loggedIn = listOf(backupButton, restoreButton, logOutButton)
|
||||||
|
loggedOut = listOf(usernameField, passwordField, loginButton, registerButton)
|
||||||
|
|
||||||
|
checkSet()
|
||||||
|
|
||||||
|
loginButton.setOnClickListener {
|
||||||
|
val api = DermyApi(preferences.getString("instance_url", null)!!).getInstance()
|
||||||
|
val toast = Toast.makeText(context, "User does not exist", Toast.LENGTH_SHORT)
|
||||||
|
GlobalScope.launch {
|
||||||
|
try {
|
||||||
|
val saltRes = api.getSalt(usernameField.text.toString())
|
||||||
|
|
||||||
|
if (saltRes.containsKey("_salt")) {
|
||||||
|
val salt = saltRes["_salt"]
|
||||||
|
val hashedPassword = "abc" //TODO: Hash password
|
||||||
|
val body =
|
||||||
|
RequestBody.create(
|
||||||
|
MediaType.get("application/json"),JSONObject()
|
||||||
|
.put("username", usernameField.text.toString())
|
||||||
|
.put("_auth", JSONObject().put("_hash", hashedPassword)).toString())
|
||||||
|
|
||||||
|
|
||||||
|
val loginRes = api.login(body)
|
||||||
|
|
||||||
|
if (loginRes.containsKey("_api_key")) {
|
||||||
|
preferences.edit()
|
||||||
|
.putString("api_key", loginRes["_api_key"])
|
||||||
|
.apply()
|
||||||
|
toast.setText("Login successful")
|
||||||
|
toast.show()
|
||||||
|
back()
|
||||||
|
} else {
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(e: Exception) {
|
||||||
|
Log.d("$e", "$e")
|
||||||
|
toast.setText("Login request has failed")
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerButton.setOnClickListener {
|
||||||
|
val api = DermyApi(preferences.getString("instance_url", null)!!).getInstance()
|
||||||
|
val toast = Toast.makeText(context, "User already exists", Toast.LENGTH_SHORT)
|
||||||
|
GlobalScope.launch {
|
||||||
|
try {
|
||||||
|
val salt = "1234" //TODO: Make salt
|
||||||
|
val hashedPassword = "abc" //TODO: hash password
|
||||||
|
val body =
|
||||||
|
RequestBody.create(MediaType.get("application/json"),
|
||||||
|
JSONObject()
|
||||||
|
.put("username", usernameField.text.toString())
|
||||||
|
.put("_auth", JSONObject().put("_salt", salt).put("_hash", hashedPassword))
|
||||||
|
.toString())
|
||||||
|
val registerRes = api.register(body)
|
||||||
|
|
||||||
|
if (registerRes.containsKey("_success")) {
|
||||||
|
toast.setText("Registration successful")
|
||||||
|
toast.show()
|
||||||
|
} else {
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
Log.d("$e", "$e")
|
||||||
|
toast.setText("Registration request failed")
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logOutButton.setOnClickListener {
|
||||||
|
val api = DermyApi(preferences.getString("instance_url", null)!!).getInstance()
|
||||||
|
val toast = Toast.makeText(context, "Your API key does not belong to any user", Toast.LENGTH_SHORT)
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
try {
|
||||||
|
val key = preferences.getString("api_key", null)!!
|
||||||
|
preferences.edit().remove("api_key").apply()
|
||||||
|
|
||||||
|
|
||||||
|
val logoutRes = api.logout(key)
|
||||||
|
if (logoutRes.containsKey("_success")) {
|
||||||
|
toast.setText("Logout successful")
|
||||||
|
toast.show()
|
||||||
|
back()
|
||||||
|
} else {
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
Log.d("$e", "$e")
|
||||||
|
toast.setText("Logout request failed")
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backupButton.setOnClickListener {
|
||||||
|
//TODO: Handle Backup
|
||||||
|
|
||||||
|
Toast.makeText(context, "Backup Button Pressed", Toast.LENGTH_SHORT).show() //TODO: Change text to relevant message
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreButton.setOnClickListener {
|
||||||
|
//TODO: Handle Restore
|
||||||
|
Toast.makeText(context, "Restore Button Pressed", Toast.LENGTH_SHORT).show() //TODO: Change text to relevant message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun checkSet() {
|
||||||
|
if(preferences.getString("api_key", null) != null) {
|
||||||
|
changeView(loggedIn, loggedOut)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
changeView(loggedOut, loggedIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changeView(show: List<View>, hide: List<View>) {
|
||||||
|
show.forEach { it.visibility = View.VISIBLE }
|
||||||
|
hide.forEach { it.visibility = View.GONE }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun back() {
|
||||||
|
usernameField.findFragment<SettingsFragment>().parentFragmentManager.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
|
class LinksAdapter(private val dataSet: Array<String>): RecyclerView.Adapter<LinksAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
|
||||||
|
private val title: TextView = view.findViewById(R.id.link_item_title)
|
||||||
|
|
||||||
|
fun bind(url: String) {
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
try {
|
||||||
|
updateItem(getInfo(url))
|
||||||
|
}
|
||||||
|
catch (_: Exception) {
|
||||||
|
updateItem(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getInfo(url: String): Document {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
Jsoup.connect(url).get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateItem(result: Document) {
|
||||||
|
title.text = result.title()
|
||||||
|
title.setOnClickListener {
|
||||||
|
title.context.startActivity(
|
||||||
|
Intent(Intent.ACTION_VIEW, Uri.parse(result.location()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun updateItem(result: String) {
|
||||||
|
title.text = result
|
||||||
|
title.setOnClickListener {
|
||||||
|
title.context.startActivity(
|
||||||
|
Intent(Intent.ACTION_VIEW, Uri.parse(result))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.links_list_item, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = dataSet.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(dataSet[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Entities
|
||||||
|
import xyz.r0r5chach.dermy_app.fragments.MoleFragment
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class LogsAdapter(private val dataSet: List<Entities.Entity?>?, private val filesDir: File): RecyclerView.Adapter<LogsAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
|
||||||
|
val textView: TextView = view.findViewById(R.id.mole_list_image)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.mole_list_item, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = dataSet!!.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val log = (dataSet!![position]!! as Entities.LogEntry)
|
||||||
|
|
||||||
|
holder.textView.setOnClickListener {
|
||||||
|
//TODO: item displays date created
|
||||||
|
//TODO: On log item pressed: Popup -> show contents
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
holder.textView.text = log.dateCreated.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.add
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.fragment.app.replace
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.badlogic.gdx.backends.android.AndroidFragmentApplication
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.fragments.HomeFragment
|
||||||
|
|
||||||
|
class MainActivity: AppCompatActivity(R.layout.activity_main), AndroidFragmentApplication.Callbacks {
|
||||||
|
val db = DermyDatabase(this)
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val homeButton = findViewById<ImageButton>(R.id.home_button)
|
||||||
|
|
||||||
|
homeButton.visibility = View.GONE
|
||||||
|
|
||||||
|
homeButton.setOnClickListener {
|
||||||
|
goHome()
|
||||||
|
homeButton.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
add(R.id.fragment_container, HomeFragment(db!!), null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun exit() {}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun goHome() {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace(R.id.fragment_container, HomeFragment(db!!), null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Entities
|
||||||
|
import xyz.r0r5chach.dermy_app.fragments.MoleFragment
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MolesAdapter(private val dataSet: List<Entities.Entity?>?, private val filesDir: File): RecyclerView.Adapter<MolesAdapter.ViewHolder>() {
|
||||||
|
class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
|
||||||
|
val imageView: ImageView = view.findViewById(R.id.mole_list_image)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.mole_list_item, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = dataSet!!.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val mole = (dataSet!![position]!! as Entities.Mole)
|
||||||
|
val db = DermyDatabase(holder.imageView.context)
|
||||||
|
|
||||||
|
holder.imageView.setOnClickListener {
|
||||||
|
(holder.imageView.context as AppCompatActivity).supportFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace(R.id.fragment_container, MoleFragment(mole, PredictionMode.NONE, db), null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
holder.imageView.setImageBitmap(BitmapFactory.decodeFile(
|
||||||
|
File(filesDir, "${mole.id}.jpg").absolutePath
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package xyz.r0r5chach.dermy_app
|
||||||
|
|
||||||
|
enum class PredictionMode {
|
||||||
|
NEW,
|
||||||
|
UPDATE,
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.api
|
||||||
|
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.jackson.JacksonConverterFactory
|
||||||
|
|
||||||
|
class DermyApi(private val url: String) {
|
||||||
|
fun getInstance(): DermyApiService {
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.baseUrl(url)
|
||||||
|
.addConverterFactory(JacksonConverterFactory.create())
|
||||||
|
.build().create(DermyApiService::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.api
|
||||||
|
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import org.json.JSONObject
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
interface DermyApiService {
|
||||||
|
@GET("/account/sign-in/{username}")
|
||||||
|
suspend fun getSalt(@Path("username") username: String): Map<String, String>
|
||||||
|
|
||||||
|
@POST("/account/sign-in")
|
||||||
|
suspend fun login(@Body loginForm: RequestBody): Map<String, String>
|
||||||
|
|
||||||
|
@POST("/account/sign-up")
|
||||||
|
suspend fun register(@Body loginForm: RequestBody): Map<String,String>
|
||||||
|
|
||||||
|
@GET("/account/sign-out/{api}")
|
||||||
|
suspend fun logout(@Path("api") apiKey: String): Map<String,String>
|
||||||
|
|
||||||
|
@GET("/account/restore/{api}")
|
||||||
|
suspend fun restore(@Path("api") apiKey: String): Map<String,String>
|
||||||
|
|
||||||
|
@POST("/account/backup/{api}")
|
||||||
|
suspend fun backup(@Path("api") apiKey: String, @Body backup: RequestBody): Map<String, String>
|
||||||
|
|
||||||
|
@GET("/predict/{api}")
|
||||||
|
suspend fun predict(@Path("api") apiKey: String, @Body image: RequestBody): Map<String, String>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.api
|
||||||
|
|
||||||
|
class Prediction(var benign: Float, val malignant: Float) {
|
||||||
|
constructor(string: String): this(
|
||||||
|
string.split("/")[0].toFloat(),
|
||||||
|
string.split("/")[1].toFloat()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.db
|
||||||
|
|
||||||
|
enum class DBProcess {
|
||||||
|
CREATE,
|
||||||
|
READ,
|
||||||
|
READ_ALL,
|
||||||
|
UPDATE,
|
||||||
|
DELETE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.db
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
|
|
||||||
|
class DermyDatabase(context: Context?) :
|
||||||
|
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||||
|
|
||||||
|
override fun onCreate(sqLiteDatabase: SQLiteDatabase) {
|
||||||
|
sqLiteDatabase.execSQL("CREATE TABLE ${Tables.MOLES} (" +
|
||||||
|
"id TEXT PRIMARY KEY," +
|
||||||
|
"userId TEXT," +
|
||||||
|
"dateCreated TEXT," +
|
||||||
|
"location TEXT," +
|
||||||
|
"benign TEXT," +
|
||||||
|
"malignant TEXT)")
|
||||||
|
|
||||||
|
sqLiteDatabase.execSQL("CREATE TABLE ${Tables.LOGS} (" +
|
||||||
|
"id TEXT PRIMARY KEY," +
|
||||||
|
"moleId TEXT," +
|
||||||
|
"dateCreated TEXT," +
|
||||||
|
"contents TEXT)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
|
||||||
|
for(table in Tables.values()) {
|
||||||
|
db!!.execSQL("DROP TABLE IF EXISTS $table")
|
||||||
|
}
|
||||||
|
onCreate(db!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DB_VERSION = 1
|
||||||
|
private const val DB_NAME = "dermy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.db
|
||||||
|
|
||||||
|
import xyz.r0r5chach.dermy_app.api.Prediction
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Location
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class Entities {
|
||||||
|
interface Entity
|
||||||
|
|
||||||
|
data class LogEntry(
|
||||||
|
val id: String,
|
||||||
|
val moleId: String,
|
||||||
|
val dateCreated: Instant,
|
||||||
|
val contents: String
|
||||||
|
): Entity
|
||||||
|
data class Mole(
|
||||||
|
val id: String?,
|
||||||
|
val userId: String?,
|
||||||
|
val dateCreated: Instant?,
|
||||||
|
val location: Location?,
|
||||||
|
var prediction: Prediction?
|
||||||
|
): Entity
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.db
|
||||||
|
|
||||||
|
enum class Tables {
|
||||||
|
MOLES,
|
||||||
|
LOGS
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.fragments
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.camera.core.CameraSelector
|
||||||
|
import androidx.camera.core.ImageCapture
|
||||||
|
import androidx.camera.core.ImageCapture.OutputFileOptions
|
||||||
|
import androidx.camera.core.ImageCaptureException
|
||||||
|
import androidx.camera.core.Preview
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import org.bson.types.ObjectId
|
||||||
|
import xyz.r0r5chach.dermy_app.PredictionMode
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Entities
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Location
|
||||||
|
import java.io.File
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class CameraFragment(val location: Location, val db: DermyDatabase, val mode: PredictionMode, var mole: Entities.Mole?) : Fragment(R.layout.fragment_camera) {
|
||||||
|
private lateinit var previewView: PreviewView
|
||||||
|
private lateinit var imageCapture: ImageCapture
|
||||||
|
private lateinit var cameraExecutor: ExecutorService
|
||||||
|
|
||||||
|
private val requestPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) {isGranted: Boolean ->
|
||||||
|
if (isGranted) {
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Toast.makeText(context, "Camera permission is required", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
previewView = requireView().findViewById(R.id.camera_preview)!!
|
||||||
|
|
||||||
|
if (context?.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
previewView.setOnClickListener { takePhoto() }
|
||||||
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCamera() {
|
||||||
|
val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(requireContext())
|
||||||
|
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||||
|
|
||||||
|
val preview = Preview.Builder().build().also {
|
||||||
|
it.setSurfaceProvider(previewView.surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCapture = ImageCapture.Builder().build()
|
||||||
|
|
||||||
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraProvider.unbindAll()
|
||||||
|
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
|
||||||
|
} catch (exc: Exception) {
|
||||||
|
exc.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
}, ContextCompat.getMainExecutor(requireContext()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takePhoto() {
|
||||||
|
val id = when(mole) {
|
||||||
|
null -> ObjectId().toString()
|
||||||
|
else -> mole!!.id
|
||||||
|
}
|
||||||
|
val photoFile = File(context?.filesDir, "$id.jpg")
|
||||||
|
val outputFileOptions: OutputFileOptions = OutputFileOptions.Builder(photoFile).build()
|
||||||
|
imageCapture.takePicture(
|
||||||
|
outputFileOptions,
|
||||||
|
ContextCompat.getMainExecutor(requireContext()),
|
||||||
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
|
if (mole == null) {
|
||||||
|
mole = Entities.Mole(id, null, Instant.now(), location, null)
|
||||||
|
}
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace(R.id.fragment_container, MoleFragment(mole!!, mode, db), null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: ImageCaptureException) {
|
||||||
|
Toast.makeText(
|
||||||
|
requireContext(),
|
||||||
|
"Error saving photo: ${exception.message}",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
cameraExecutor.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.fragment.app.replace
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.map.MapFragment
|
||||||
|
import xyz.r0r5chach.dermy_app.map.Mode
|
||||||
|
|
||||||
|
@DelicateCoroutinesApi
|
||||||
|
class HomeFragment(val db: DermyDatabase) : Fragment(R.layout.fragment_home) {
|
||||||
|
private var buttons: IntArray = intArrayOf(R.id.add_mole_button, R.id.map_button, R.id.links_button, R.id.settings_button)
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
for (button in buttons) {
|
||||||
|
view.findViewById<ImageButton>(button).setOnClickListener {
|
||||||
|
initButton(button)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initButton(button: Int) {
|
||||||
|
requireActivity().findViewById<ImageButton>(R.id.home_button).visibility = View.VISIBLE
|
||||||
|
|
||||||
|
when (button) {
|
||||||
|
R.id.add_mole_button -> {
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace(R.id.fragment_container, MapFragment(Mode.ADD, db), null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.map_button -> {
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace(R.id.fragment_container, MapFragment(Mode.VIEW, db), null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.links_button -> {
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace<LinksFragment>(R.id.fragment_container, null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.settings_button -> {
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
replace<SettingsFragment>(R.id.fragment_container, null)
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.r0r5chach.dermy_app.LinksAdapter
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
|
||||||
|
class LinksFragment : Fragment(R.layout.fragment_links) {
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
val list = view.findViewById<RecyclerView>(R.id.links_list)
|
||||||
|
val links = resources.getStringArray(R.array.links)
|
||||||
|
|
||||||
|
val adapter = LinksAdapter(links)
|
||||||
|
list.layoutManager = LinearLayoutManager(context)
|
||||||
|
list.adapter = adapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.fragments
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.r0r5chach.dermy_app.MolesAdapter
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
import xyz.r0r5chach.dermy_app.api.Prediction
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Tables
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Entities
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Location
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class LocationFragment(private val location: Location, val db: DermyDatabase): Fragment(R.layout.fragment_location) {
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val list = view.findViewById<RecyclerView>(R.id.moles_list)
|
||||||
|
val emptyList = view.findViewById<TextView>(R.id.location_empty_text)
|
||||||
|
val moles = mutableListOf<Entities.Mole>()
|
||||||
|
with(db.readableDatabase.query(Tables.MOLES.toString(), null, null, null, null, null, null)) {
|
||||||
|
while (moveToNext()) {
|
||||||
|
moles.add(
|
||||||
|
Entities.Mole(
|
||||||
|
getString(getColumnIndexOrThrow("id")),
|
||||||
|
getString(getColumnIndexOrThrow("userId")),
|
||||||
|
Instant.parse(getString(getColumnIndexOrThrow("dateCreated"))),
|
||||||
|
Location(getString(getColumnIndexOrThrow("location"))),
|
||||||
|
Prediction(getString(getColumnIndexOrThrow("prediction")))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
val adapter = MolesAdapter(moles, requireContext().filesDir)
|
||||||
|
list.layoutManager = GridLayoutManager(context, 3)
|
||||||
|
list.adapter = adapter
|
||||||
|
|
||||||
|
if (adapter.itemCount == 0) {
|
||||||
|
list.visibility = View.GONE
|
||||||
|
emptyList.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list.visibility = View.VISIBLE
|
||||||
|
emptyList.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = "${location.side.toString()} ${location.bodyPart.toString()}"
|
||||||
|
view.findViewById<TextView>(R.id.location_name_text).text = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.fragments
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.database.sqlite.SQLiteDatabase
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.findViewTreeViewModelStoreOwner
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import xyz.r0r5chach.dermy_app.LogsAdapter
|
||||||
|
import xyz.r0r5chach.dermy_app.MolesAdapter
|
||||||
|
import xyz.r0r5chach.dermy_app.PredictionMode
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
import xyz.r0r5chach.dermy_app.api.DermyApi
|
||||||
|
import xyz.r0r5chach.dermy_app.api.DermyApiService
|
||||||
|
import xyz.r0r5chach.dermy_app.api.Prediction
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DBProcess
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Entities
|
||||||
|
import xyz.r0r5chach.dermy_app.db.Tables
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Location
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class MoleFragment(private val mole: Entities.Mole, private val mode: PredictionMode, val db: DermyDatabase) : Fragment(R.layout.fragment_mole) {
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
val api = DermyApi(preferences.getString("instance_url", null)!!).getInstance()
|
||||||
|
val apiKey = preferences.getString("api_key", null)
|
||||||
|
|
||||||
|
val moleImage = view.findViewById<ImageView>(R.id.mole_image)
|
||||||
|
val image = File(requireContext().filesDir, "${mole.id}.jpg").absolutePath
|
||||||
|
val moleSide = view.findViewById<EditText>(R.id.side_value)
|
||||||
|
val moleBodyPart = view.findViewById<EditText>(R.id.body_part_value)
|
||||||
|
val benignPrediction = view.findViewById<EditText>(R.id.benign_prediction_value)
|
||||||
|
val malignantPrediction = view.findViewById<EditText>(R.id.malignant_prediction_value)
|
||||||
|
|
||||||
|
val list = view.findViewById<RecyclerView>(R.id.log_list)
|
||||||
|
val emptyList = view.findViewById<TextView>(R.id.log_empty_text)
|
||||||
|
val logs = mutableListOf<Entities.LogEntry>()
|
||||||
|
|
||||||
|
with(db.readableDatabase.query(Tables.MOLES.toString(), null, null, null, null, null, null)) {
|
||||||
|
while (moveToNext()) {
|
||||||
|
logs.add(
|
||||||
|
Entities.LogEntry(
|
||||||
|
getString(getColumnIndexOrThrow("id")),
|
||||||
|
getString(getColumnIndexOrThrow("moleId")),
|
||||||
|
Instant.parse(getString(getColumnIndexOrThrow("dateCreated"))),
|
||||||
|
getString(getColumnIndexOrThrow("contents"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
val adapter = LogsAdapter(logs, requireContext().filesDir)
|
||||||
|
list.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
list.adapter = adapter
|
||||||
|
|
||||||
|
if (adapter.itemCount == 0) {
|
||||||
|
list.visibility = View.GONE
|
||||||
|
emptyList.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
list.visibility = View.VISIBLE
|
||||||
|
emptyList.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
val saveButton = view.findViewById<ImageButton>(R.id.mole_save_button)
|
||||||
|
|
||||||
|
if (mode == PredictionMode.NEW || mode == PredictionMode.UPDATE) {
|
||||||
|
if (mode == PredictionMode.NEW) {
|
||||||
|
saveButton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
saveButton.visibility = View.GONE
|
||||||
|
|
||||||
|
}
|
||||||
|
val toast = Toast.makeText(context, "Error making prediction", Toast.LENGTH_SHORT)
|
||||||
|
|
||||||
|
GlobalScope.launch {
|
||||||
|
try {
|
||||||
|
val result = api.predict(
|
||||||
|
apiKey!!, RequestBody.create(
|
||||||
|
MediaType.get("application/octet-stream"),
|
||||||
|
image.toByteArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.containsKey("benign_probability") && result.containsKey("malignant_probability")) {
|
||||||
|
mole.prediction = Prediction(
|
||||||
|
result["benign_probability"]!!.toFloat(),
|
||||||
|
result["malignant_probability"]!!.toFloat()
|
||||||
|
)
|
||||||
|
benignPrediction.setText((mole.prediction!!.benign * 100).toString())
|
||||||
|
malignantPrediction.setText((mole.prediction!!.malignant * 100).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
saveButton.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
moleImage.setImageBitmap(BitmapFactory.decodeFile(image))
|
||||||
|
moleSide.setText(mole.location?.side.toString())
|
||||||
|
moleBodyPart.setText(mole.location?.bodyPart.toString())
|
||||||
|
benignPrediction.setText((mole.prediction?.benign?.times(100)).toString())
|
||||||
|
malignantPrediction.setText((mole.prediction?.malignant?.times(100))toString())
|
||||||
|
|
||||||
|
//TODO: On new prediction pressed: CameraFragment -> take photo -> MoleFragment -> predict -> update db
|
||||||
|
//TODO: On save pressed: insert mole to db
|
||||||
|
//TODO: On new log pressed: Popup -> save input from popup as contents -> insert new Log
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
|
||||||
|
|
||||||
|
@DelicateCoroutinesApi
|
||||||
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||||
|
|
||||||
|
findPreference<Preference>("disclaimer")
|
||||||
|
?.setOnPreferenceClickListener {
|
||||||
|
val dialog = AlertDialog.Builder(requireContext())
|
||||||
|
|
||||||
|
dialog
|
||||||
|
.setMessage(R.string.disclaimer)
|
||||||
|
.setTitle("Disclaimer")
|
||||||
|
.setNeutralButton("Ok") { window, _ -> window.dismiss() }
|
||||||
|
.create().show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//TODO: Backup data
|
||||||
|
//TODO: Recover Account
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map
|
||||||
|
|
||||||
|
import com.badlogic.gdx.ApplicationListener
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.assets.AssetManager
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.GL20
|
||||||
|
import com.badlogic.gdx.graphics.PerspectiveCamera
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Model
|
||||||
|
import com.badlogic.gdx.graphics.g3d.ModelBatch
|
||||||
|
import com.badlogic.gdx.graphics.g3d.ModelInstance
|
||||||
|
import com.badlogic.gdx.math.Vector3
|
||||||
|
import com.badlogic.gdx.math.collision.BoundingBox
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Location
|
||||||
|
|
||||||
|
class BodyMap(val observer: (Location?) -> Unit, private val backgroundColor: Int): ApplicationListener {
|
||||||
|
private val environment: MapEnvironment = MapEnvironment()
|
||||||
|
private var bodyModel: Model? = null
|
||||||
|
private var modelBatch: ModelBatch? = null
|
||||||
|
private var spriteBatch: SpriteBatch? = null
|
||||||
|
private var mapInstance: ModelInstance? = null
|
||||||
|
private var cam: PerspectiveCamera? = null
|
||||||
|
private var camController: MapCameraController? = null
|
||||||
|
private var assetManager: AssetManager = AssetManager()
|
||||||
|
private var font: BitmapFont? = null
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun initModel() {
|
||||||
|
bodyModel = assetManager.get(Gdx.files.internal("human.obj").path())
|
||||||
|
mapInstance = ModelInstance(bodyModel)
|
||||||
|
val modelCenter = Vector3()
|
||||||
|
|
||||||
|
mapInstance!!.calculateBoundingBox(BoundingBox()).getCenter(modelCenter)
|
||||||
|
mapInstance!!.transform.translate(-modelCenter.x, -modelCenter.y, -modelCenter.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initCam() {
|
||||||
|
cam = PerspectiveCamera(67f, Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat())
|
||||||
|
camController = MapCameraController(cam, null)
|
||||||
|
|
||||||
|
cam!!.position[0f, 5f] = 25f
|
||||||
|
cam!!.lookAt(0f, 0f, 0f)
|
||||||
|
cam!!.near = 1f
|
||||||
|
cam!!.far = 300f
|
||||||
|
cam!!.update()
|
||||||
|
|
||||||
|
Gdx.input.inputProcessor = camController
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun create() {
|
||||||
|
assetManager.load(Gdx.files.internal("human.obj").path(), Model::class.java)
|
||||||
|
initCam()
|
||||||
|
modelBatch = ModelBatch()
|
||||||
|
spriteBatch = SpriteBatch()
|
||||||
|
|
||||||
|
font = BitmapFont()
|
||||||
|
font!!.color = Color.WHITE
|
||||||
|
font!!.data.scale(5f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun render() {
|
||||||
|
camController!!.update()
|
||||||
|
assetManager.update()
|
||||||
|
|
||||||
|
Gdx.gl.glClearColor(
|
||||||
|
((backgroundColor shr 0) and 0xFF) / 255f,
|
||||||
|
((backgroundColor shr 8) and 0xFF) / 255f,
|
||||||
|
((backgroundColor shr 16) and 0xFF) / 255f,
|
||||||
|
((backgroundColor shr 24) and 0xFF) / 255f)
|
||||||
|
|
||||||
|
Gdx.gl.glViewport(0, 0, Gdx.graphics.width, Gdx.graphics.height)
|
||||||
|
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT)
|
||||||
|
|
||||||
|
if (assetManager.isFinished) {
|
||||||
|
initModel()
|
||||||
|
camController!!.modelInstance = mapInstance
|
||||||
|
camController!!.addObserver { newValue -> observer(newValue) }
|
||||||
|
|
||||||
|
|
||||||
|
modelBatch!!.begin(cam)
|
||||||
|
modelBatch!!.render(mapInstance!!, environment)
|
||||||
|
modelBatch!!.end()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
spriteBatch!!.begin()
|
||||||
|
font!!.draw(spriteBatch, "Map Loading...", (Gdx.graphics.width.toFloat() - font!!.region.regionWidth) / 3, (Gdx.graphics.height.toFloat() - font!!.region.regionHeight) / 1.75f)
|
||||||
|
spriteBatch!!.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resize(width: Int, height: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pause() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resume() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
if (modelBatch != null) {
|
||||||
|
modelBatch!!.dispose()
|
||||||
|
}
|
||||||
|
if (bodyModel != null) {
|
||||||
|
bodyModel!!.dispose()
|
||||||
|
}
|
||||||
|
font!!.dispose()
|
||||||
|
spriteBatch!!.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.PerspectiveCamera
|
||||||
|
import com.badlogic.gdx.graphics.g3d.ModelInstance
|
||||||
|
import com.badlogic.gdx.graphics.g3d.model.MeshPart
|
||||||
|
import com.badlogic.gdx.graphics.g3d.utils.CameraInputController
|
||||||
|
import com.badlogic.gdx.math.Intersector
|
||||||
|
import com.badlogic.gdx.math.Vector3
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.BodyPart
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Location
|
||||||
|
import xyz.r0r5chach.dermy_app.map.locations.Side
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class MapCameraController(
|
||||||
|
camera: PerspectiveCamera?,
|
||||||
|
var modelInstance: ModelInstance?
|
||||||
|
) : CameraInputController(camera) {
|
||||||
|
private var initialX: Int = 0
|
||||||
|
private var initialY: Int = 0
|
||||||
|
private var selectedLocation: Location by Delegates.observable(Location(Side.NONE, BodyPart.NONE)) { _, _, newValue ->
|
||||||
|
notifyObservers(newValue)
|
||||||
|
}
|
||||||
|
private val observers = mutableListOf<(Location) -> Unit>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.pinchZoomFactor = 25f
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addObserver(observer: (Location) -> Unit) {
|
||||||
|
observers.add(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyObservers(newValue: Location) {
|
||||||
|
for (observer in observers) {
|
||||||
|
observer(newValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
|
||||||
|
initialX = screenX
|
||||||
|
initialY = screenY
|
||||||
|
return super.touchDown(screenX, screenY, pointer, button)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean {
|
||||||
|
if (abs((initialX - screenX).toDouble()) < 10 && abs((initialY - screenY).toDouble()) < 10) {
|
||||||
|
handleClick()
|
||||||
|
}
|
||||||
|
return super.touchUp(screenX, screenY, pointer, button)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleClick() {
|
||||||
|
if (modelInstance != null) {
|
||||||
|
val selectionRay = camera.getPickRay(Gdx.input.x.toFloat(), Gdx.input.y.toFloat())
|
||||||
|
var closestDistance = Float.MAX_VALUE
|
||||||
|
var closestPart: MeshPart? = null
|
||||||
|
val meshes = modelInstance!!.model.meshParts
|
||||||
|
|
||||||
|
for (part in meshes) {
|
||||||
|
val bounds = part.mesh.calculateBoundingBox()
|
||||||
|
bounds.mul(modelInstance!!.transform)
|
||||||
|
val intersection = Vector3()
|
||||||
|
|
||||||
|
if (Intersector.intersectRayBounds(selectionRay, bounds, intersection)) {
|
||||||
|
val distance = selectionRay.origin.dst(intersection)
|
||||||
|
if (distance < closestDistance) {
|
||||||
|
closestDistance = distance
|
||||||
|
closestPart = part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val id = closestPart?.id
|
||||||
|
val side = when (id?.get(0)) {
|
||||||
|
'L' -> Side.LEFT
|
||||||
|
'R' -> Side.RIGHT
|
||||||
|
else -> Side.LEFT
|
||||||
|
}
|
||||||
|
val bodyPart = BodyPart.valueOf(id!!.substring(1).uppercase())
|
||||||
|
selectedLocation = Location(side, bodyPart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.g3d.Environment
|
||||||
|
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute
|
||||||
|
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight
|
||||||
|
|
||||||
|
class MapEnvironment internal constructor() : Environment() {
|
||||||
|
init {
|
||||||
|
this.set(ambientLight)
|
||||||
|
this.add(rightLight)
|
||||||
|
this.add(leftLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ambientLight = ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)
|
||||||
|
private val rightLight: DirectionalLight =
|
||||||
|
DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f)
|
||||||
|
private val leftLight: DirectionalLight =
|
||||||
|
DirectionalLight().set(0.8f, 0.8f, 0.8f, 1f, 0.8f, 0.2f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.fragment.app.replace
|
||||||
|
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
|
||||||
|
import com.badlogic.gdx.backends.android.AndroidFragmentApplication
|
||||||
|
import xyz.r0r5chach.dermy_app.PredictionMode
|
||||||
|
import xyz.r0r5chach.dermy_app.R
|
||||||
|
import xyz.r0r5chach.dermy_app.db.DermyDatabase
|
||||||
|
import xyz.r0r5chach.dermy_app.fragments.CameraFragment
|
||||||
|
import xyz.r0r5chach.dermy_app.fragments.LocationFragment
|
||||||
|
|
||||||
|
class MapFragment(private val mapMode: Mode, val db: DermyDatabase) : AndroidFragmentApplication() {
|
||||||
|
private var map: BodyMap? = null
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val backgroundColor = requireContext().getColor(R.color.cardview_dark_background)
|
||||||
|
|
||||||
|
map = BodyMap ({ newValue ->
|
||||||
|
if (newValue != null) {
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setReorderingAllowed(true)
|
||||||
|
when (mapMode) {
|
||||||
|
Mode.ADD -> replace(R.id.fragment_container, CameraFragment(newValue, db, PredictionMode.NEW, null), null)
|
||||||
|
Mode.VIEW -> replace(R.id.fragment_container, LocationFragment(newValue, db), null)
|
||||||
|
}
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, backgroundColor)
|
||||||
|
return initializeForView(map, initConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initConfig(): AndroidApplicationConfiguration {
|
||||||
|
val config = AndroidApplicationConfiguration()
|
||||||
|
config.useAccelerometer = false
|
||||||
|
config.useCompass = false
|
||||||
|
config.useGyroscope = false
|
||||||
|
config.useWakelock = false
|
||||||
|
config.useImmersiveMode = false
|
||||||
|
config.useRotationVectorSensor = false
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
ADD,
|
||||||
|
VIEW
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map.locations
|
||||||
|
|
||||||
|
enum class BodyPart {
|
||||||
|
ABDOMEN,
|
||||||
|
ANKLE,
|
||||||
|
ARMPIT,
|
||||||
|
B_ELBOW,
|
||||||
|
B_FOREARM,
|
||||||
|
B_HEAD,
|
||||||
|
B_KNEE,
|
||||||
|
B_UPPER_ARM,
|
||||||
|
BUTTOCK,
|
||||||
|
CALF,
|
||||||
|
CHEEK,
|
||||||
|
CHEST,
|
||||||
|
EAR,
|
||||||
|
EYE,
|
||||||
|
F_ELBOW,
|
||||||
|
F_FOREARM,
|
||||||
|
F_KNEE,
|
||||||
|
FOOT,
|
||||||
|
FOREHEAD,
|
||||||
|
F_UPPER_ARM,
|
||||||
|
GROIN,
|
||||||
|
HAMSTRING,
|
||||||
|
HAND,
|
||||||
|
HEEL,
|
||||||
|
LOWER_BACK,
|
||||||
|
MOUTH,
|
||||||
|
NECK,
|
||||||
|
SHIN,
|
||||||
|
SHOULDER,
|
||||||
|
THIGH,
|
||||||
|
TOP_HEAD,
|
||||||
|
UPPER_BACK,
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map.locations
|
||||||
|
|
||||||
|
class Location(val side: Side?, val bodyPart: BodyPart?) {
|
||||||
|
constructor(string: String): this(
|
||||||
|
Side.valueOf(string.split("/")[0]),
|
||||||
|
BodyPart.valueOf(string.split("/")[1])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package xyz.r0r5chach.dermy_app.map.locations
|
||||||
|
|
||||||
|
enum class Side {
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Blender 4.1.1 MTL File: 'body.blend'
|
||||||
|
# www.blender.org
|
||||||
|
|
||||||
|
newmtl default
|
||||||
|
Ns 0.000000
|
||||||
|
Ka 1.000000 1.000000 1.000000
|
||||||
|
Kd 0.800000 0.800000 0.800000
|
||||||
|
Ks 0.000000 0.000000 0.000000
|
||||||
|
Ke 0.000000 0.000000 0.000000
|
||||||
|
Ni 1.500000
|
||||||
|
d 1.000000
|
||||||
|
illum 1
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
buildscript {
|
||||||
|
ext.kotlinVersion = '1.8.0'
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.5.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
|
classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:1.5.30-1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply plugin: "eclipse"
|
||||||
|
|
||||||
|
version = '1.0'
|
||||||
|
ext {
|
||||||
|
appName = "dermy-app"
|
||||||
|
gdxVersion = '1.12.0'
|
||||||
|
roboVMVersion = '2.3.20'
|
||||||
|
box2DLightsVersion = '1.5'
|
||||||
|
ashleyVersion = '1.7.4'
|
||||||
|
aiVersion = '1.8.2'
|
||||||
|
gdxControllersVersion = '2.2.1'
|
||||||
|
retrofitVersion = '2.11.0'
|
||||||
|
cameraxVersion = '1.3.4'
|
||||||
|
roomVersion = '2.6.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
gradlePluginPortal()
|
||||||
|
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
|
||||||
|
maven { url "https://oss.sonatype.org/content/repositories/releases/" }
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
project(":android") {
|
||||||
|
apply plugin: "android"
|
||||||
|
apply plugin: "kotlin-android"
|
||||||
|
apply plugin: "com.google.devtools.ksp"
|
||||||
|
|
||||||
|
configurations { natives }
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(":core")
|
||||||
|
|
||||||
|
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx-platform:$gdxVersion"
|
||||||
|
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
|
|
||||||
|
//MongoDB
|
||||||
|
implementation "org.mongodb:bson:5.1.1"
|
||||||
|
|
||||||
|
//Retrofit2 & Jackson
|
||||||
|
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
||||||
|
implementation "com.squareup.retrofit2:converter-jackson:$retrofitVersion"
|
||||||
|
|
||||||
|
//Jsoup
|
||||||
|
implementation "org.jsoup:jsoup:1.17.2"
|
||||||
|
|
||||||
|
implementation 'androidx.core:core-ktx:1.13.1'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
|
implementation 'androidx.preference:preference:1.2.1'
|
||||||
|
|
||||||
|
implementation 'androidx.test.ext:junit:1.2.1'
|
||||||
|
implementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
|
|
||||||
|
//Room
|
||||||
|
implementation "androidx.room:room-ktx:$roomVersion"
|
||||||
|
implementation "androidx.room:room-common:2.6.1"
|
||||||
|
implementation "androidx.room:room-runtime:$roomVersion"
|
||||||
|
ksp "androidx.room:room-compiler:$roomVersion"
|
||||||
|
|
||||||
|
|
||||||
|
//CameraX
|
||||||
|
implementation "androidx.camera:camera-core:$cameraxVersion"
|
||||||
|
implementation "androidx.camera:camera-view:$cameraxVersion"
|
||||||
|
implementation "androidx.camera:camera-lifecycle:$cameraxVersion"
|
||||||
|
implementation "androidx.camera:camera-camera2:$cameraxVersion"
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project(":core") {
|
||||||
|
apply plugin: "kotlin"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
api "com.badlogicgames.gdx:gdx:$gdxVersion"
|
||||||
|
api "com.badlogicgames.gdx:gdx-bullet:$gdxVersion"
|
||||||
|
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
org.gradle.daemon=true
|
||||||
|
org.gradle.jvmargs=-Xms128m -Xmx1500m
|
||||||
|
org.gradle.configureondemand=false
|
||||||
|
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.nonTransitiveRClass=false
|
||||||
|
android.nonFinalResIds=false
|
||||||
|
android.suppressUnsupportedCompileSdk=35
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
include 'android', 'core'
|
||||||