This commit is contained in:
Joshua Perry 2024-06-14 21:36:23 +01:00
parent 8051c2c4ed
commit ccfa694180
48 changed files with 1071 additions and 51 deletions

View File

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-06-12T15:02:16.596717208Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=62f6513d" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -1,3 +1,5 @@
import com.android.build.api.dsl.Packaging
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android) alias(libs.plugins.jetbrains.kotlin.android)
@ -28,7 +30,11 @@ android {
targetCompatibility = JavaVersion.VERSION_16 targetCompatibility = JavaVersion.VERSION_16
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "16"
}
packaging {
resources.excludes.add("META-INF/native-image/org.mongodb/bson/native-image.properties")
} }
} }
@ -53,4 +59,11 @@ dependencies {
//MongoDB //MongoDB
implementation(libs.mongodb.driver.kotlin.sync) implementation(libs.mongodb.driver.kotlin.sync)
//Api Requests
implementation(libs.retrofit.v2110)
implementation(libs.converter.jackson)
//Jsoup
implementation(libs.jsoup.jsoup)
} }

View File

@ -2,7 +2,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> 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"/>
<application <application
android:enableOnBackInvokedCallback="true"
android:usesCleartextTraffic="true"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"

View File

@ -0,0 +1,188 @@
package xyz.r0r5chach.dermy_app
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.fragment.app.findFragment
import androidx.lifecycle.ReportFragment.Companion.reportFragment
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceViewHolder
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import okhttp3.Headers
import okhttp3.MediaType
import okhttp3.RequestBody
import org.json.JSONObject
import xyz.r0r5chach.dermy_app.api.DermyApi
import xyz.r0r5chach.dermy_app.fragments.SettingsFragment
import kotlin.math.log
@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()
}
}

View File

@ -0,0 +1,67 @@
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.lifecycle.viewmodel.viewModelFactory
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_row_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = dataSet.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(dataSet[position])
}
}

View File

@ -9,6 +9,7 @@ import xyz.r0r5chach.dermy_app.fragments.HomeFragment
class MainActivity: AppCompatActivity(R.layout.activity_main) { class MainActivity: AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager.commit { supportFragmentManager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)

View File

@ -0,0 +1,17 @@
package xyz.r0r5chach.dermy_app.api
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.json.JSONObject
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)
}
}

View File

@ -0,0 +1,34 @@
package xyz.r0r5chach.dermy_app.api
import okhttp3.RequestBody
import org.json.JSONObject
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.HeaderMap
import okhttp3.Headers
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): JSONObject
@POST("/account/backup/{api}")
suspend fun backup(@Path("api") apiKey: String, @Body backup: RequestBody): JSONObject
@GET("/predict/{api}")
suspend fun predict(@Path("api") apiKey: String, @Body image: RequestBody): JSONObject
}

View File

@ -0,0 +1,8 @@
package xyz.r0r5chach.dermy_app.db.locations;
public enum LocationCategory {
Front,
Left,
Right,
Back
}

View File

@ -1,5 +0,0 @@
package xyz.r0r5chach.dermy_app.fragments;
public class AccountFragment {
//TODO: Login if no API Key
}

View File

@ -79,7 +79,7 @@ class CameraFragment : Fragment(R.layout.fragment_camera) {
} }
private fun takePhoto() { private fun takePhoto() {
val photoFile = File(context?.getExternalFilesDir(null), "${System.currentTimeMillis()}.jpg") //TODO: Save image as MoleId val photoFile = File(context?.filesDir, "${System.currentTimeMillis()}.jpg") //TODO: Save image as MoleId
val outputFileOptions: OutputFileOptions = OutputFileOptions.Builder(photoFile).build() val outputFileOptions: OutputFileOptions = OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture( imageCapture.takePicture(
outputFileOptions, outputFileOptions,

View File

@ -2,18 +2,23 @@ package xyz.r0r5chach.dermy_app.fragments
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Button import android.widget.ImageButton
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.fragment.app.replace import androidx.fragment.app.replace
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.r0r5chach.dermy_app.R import xyz.r0r5chach.dermy_app.R
@DelicateCoroutinesApi
class HomeFragment : Fragment(R.layout.fragment_home) { class HomeFragment : 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) 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
for (button in buttons) { for (button in buttons) {
view.findViewById<Button>(button).setOnClickListener { view.findViewById<ImageButton>(button).setOnClickListener {
initButton(button) initButton(button)
} }
} }
@ -27,24 +32,30 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
manager.commit { manager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
replace<MoleFragment>(R.id.fragment_container, null) replace<MoleFragment>(R.id.fragment_container, null)
addToBackStack(null)
} }
} }
R.id.map_button -> { R.id.map_button -> {
CoroutineScope(Dispatchers.Main).launch {
manager.commit { manager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
replace<MapFragment>(R.id.fragment_container, null) replace<MapFragment>(R.id.fragment_container, null)
addToBackStack(null)
}
} }
} }
R.id.links_button -> { R.id.links_button -> {
manager.commit { manager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
replace<LinksFragment>(R.id.fragment_container, null) replace<LinksFragment>(R.id.fragment_container, null)
addToBackStack(null)
} }
} }
R.id.settings_button -> { R.id.settings_button -> {
manager.commit { manager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
replace<SettingsFragment>(R.id.fragment_container, null) replace<SettingsFragment>(R.id.fragment_container, null)
addToBackStack(null)
} }
} }

View File

@ -1,7 +0,0 @@
package xyz.r0r5chach.dermy_app.fragments;
import androidx.fragment.app.Fragment;
public class LinksFragment extends Fragment {
//TODO: NHS links etc
}

View File

@ -0,0 +1,26 @@
package xyz.r0r5chach.dermy_app.fragments
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.ListView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import xyz.r0r5chach.dermy_app.LinksAdapter
import xyz.r0r5chach.dermy_app.R
class LinksFragment : Fragment(R.layout.fragment_links) {
//TODO: NHS links etc
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
}
}

View File

@ -1,8 +0,0 @@
package xyz.r0r5chach.dermy_app.fragments;
import androidx.fragment.app.Fragment;
public class MapFragment extends Fragment {
//TODO: Map of all locations
//When pressed route to LocationFragment with specified location
}

View File

@ -0,0 +1,35 @@
package xyz.r0r5chach.dermy_app.fragments
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import xyz.r0r5chach.dermy_app.R
import xyz.r0r5chach.dermy_app.db.locations.LocationCategory
class MapFragment : Fragment(R.layout.fragment_map) {
private lateinit var pager: ViewPager2
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
pager = view.findViewById(R.id.map_pager)
pager.adapter = MapPagerAdapter(requireActivity())
pager.post { pager.setCurrentItem(1, false); pager.visibility = View.VISIBLE }
}
private inner class MapPagerAdapter(fragment: FragmentActivity) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 4
override fun createFragment(position: Int): Fragment {
return when(position) {
0 -> MapPageFragment(LocationCategory.Left)
2 -> MapPageFragment(LocationCategory.Right)
3 -> MapPageFragment(LocationCategory.Back)
else -> MapPageFragment(LocationCategory.Front)
}
}
}
}

View File

@ -0,0 +1,33 @@
package xyz.r0r5chach.dermy_app.fragments
import android.os.Bundle
import android.provider.ContactsContract.CommonDataKinds.Im
import android.view.View
import android.widget.ImageView
import androidx.fragment.app.Fragment
import xyz.r0r5chach.dermy_app.R
import xyz.r0r5chach.dermy_app.db.locations.LocationCategory
class MapPageFragment(private val category: LocationCategory): Fragment(R.layout.fragment_map_page) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val image = view.findViewById<ImageView>(R.id.category_image)
when(category) {
LocationCategory.Front -> {
image.setImageResource(R.drawable.front_body)
}
LocationCategory.Left -> {
image.setImageResource(R.drawable.side_body)
}
LocationCategory.Back -> {
image.setImageResource(R.drawable.back_body)
}
LocationCategory.Right -> {
image.setImageResource(R.drawable.side_body)
image.rotationY = 180.0f
}
}
}
}

View File

@ -1,17 +1,36 @@
package xyz.r0r5chach.dermy_app.fragments package xyz.r0r5chach.dermy_app.fragments
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.commit
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import kotlinx.coroutines.DelicateCoroutinesApi
import xyz.r0r5chach.dermy_app.AccountPreference
import xyz.r0r5chach.dermy_app.R import xyz.r0r5chach.dermy_app.R
@DelicateCoroutinesApi
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
} //TODO: Disclaimers
//TODO: Useful Links 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: Export File //TODO: Export File
//TODO: Backup data //TODO: Backup data
//TODO: Recover Account //TODO: Recover Account
// Login button
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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>

View File

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container" android:id="@+id/fragment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"/>
android:name="xyz.r0r5chach.dermy.fragments.CameraFragment" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,126 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"> 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> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,60 +4,64 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<Button <ImageButton
android:id="@+id/add_mole_button" android:id="@+id/add_mole_button"
android:background="@drawable/add_icon"
android:contentDescription="@string/string_add_mole"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:text="@string/string_add_mole"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/links_button" app:layout_constraintBottom_toTopOf="@+id/links_button"
app:layout_constraintEnd_toStartOf="@+id/map_button" app:layout_constraintEnd_toStartOf="@+id/map_button"
app:layout_constraintHeight_percent="0.1" app:layout_constraintHeight_percent="0.15"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="320dp" app:layout_constraintWidth_max="320dp"
app:layout_constraintWidth_percent="0.28" /> app:layout_constraintWidth_percent="0.28" />
<Button <ImageButton
android:id="@+id/map_button" android:id="@+id/map_button"
android:background="@drawable/map_icon"
android:contentDescription="@string/string_open_body_map"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:text="@string/string_body_map"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/settings_button" app:layout_constraintBottom_toTopOf="@+id/settings_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.1" app:layout_constraintHeight_percent="0.15"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/add_mole_button" app:layout_constraintStart_toEndOf="@+id/add_mole_button"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="320dp" app:layout_constraintWidth_max="320dp"
app:layout_constraintWidth_percent="0.28" /> app:layout_constraintWidth_percent="0.28" />
<Button <ImageButton
android:id="@+id/links_button" android:id="@+id/links_button"
android:background="@drawable/links_icon"
android:contentDescription="@string/string_open_useful_links"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:text="@string/string_links"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/settings_button" app:layout_constraintEnd_toStartOf="@+id/settings_button"
app:layout_constraintHeight_percent="0.1" app:layout_constraintHeight_percent="0.15"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/add_mole_button" app:layout_constraintTop_toBottomOf="@+id/add_mole_button"
app:layout_constraintWidth_max="320dp" app:layout_constraintWidth_max="320dp"
app:layout_constraintWidth_percent="0.28" /> app:layout_constraintWidth_percent="0.28" />
<Button <ImageButton
android:id="@+id/settings_button" android:id="@+id/settings_button"
android:background="@drawable/cog_icon"
android:contentDescription="@string/string_open_settings"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:text="@string/string_settings"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.1" app:layout_constraintHeight_percent="0.15"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/links_button" app:layout_constraintStart_toEndOf="@+id/links_button"
app:layout_constraintTop_toBottomOf="@+id/map_button" app:layout_constraintTop_toBottomOf="@+id/map_button"

View File

@ -1,6 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="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> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <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_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/map_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,254 @@
<?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">
<ImageView
android:id="@+id/category_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="50dp"
android:layout_marginBottom="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/front_body" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/left_wrist_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/genitals_button"
tools:layout_editor_absoluteY="383dp" />
<Button
android:id="@+id/right_wrist_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toStartOf="@+id/genitals_button"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="383dp" />
<Button
android:id="@+id/left_hand_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/left_thigh_button"
tools:layout_editor_absoluteY="437dp" />
<Button
android:id="@+id/right_hand_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toStartOf="@+id/right_thigh_button"
app:layout_constraintStart_toStartOf="parent"
tools:layout_editor_absoluteY="437dp" />
<Button
android:id="@+id/left_thigh_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="240dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toStartOf="@+id/left_hand_button"
app:layout_constraintStart_toEndOf="@+id/right_thigh_button"
app:layout_constraintTop_toBottomOf="@+id/left_breast_button" />
<Button
android:id="@+id/right_thigh_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="240dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toStartOf="@+id/left_thigh_button"
app:layout_constraintStart_toEndOf="@+id/right_hand_button"
app:layout_constraintTop_toBottomOf="@+id/right_breast_button" />
<Button
android:id="@+id/genitals_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginBottom="500dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/left_wrist_button"
app:layout_constraintStart_toEndOf="@+id/right_wrist_button"
app:layout_constraintTop_toBottomOf="@+id/stomach_button" />
<Button
android:id="@+id/stomach_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="100dp"
android:layout_marginBottom="100dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/genitals_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/neck_button" />
<Button
android:id="@+id/left_shoulder_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="189dp"
android:layout_marginEnd="350dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/left_breast_button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/right_shoulder_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginStart="350dp"
android:layout_marginTop="189dp"
android:background="@drawable/circle_button"
app:layout_constraintEnd_toStartOf="@+id/right_breast_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/left_breast_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/left_thigh_button"
app:layout_constraintEnd_toStartOf="@id/left_shoulder_button"
app:layout_constraintStart_toEndOf="@+id/right_breast_button"
app:layout_constraintTop_toBottomOf="@+id/left_collar_button" />
<Button
android:id="@+id/right_breast_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="20dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/right_thigh_button"
app:layout_constraintEnd_toStartOf="@+id/left_breast_button"
app:layout_constraintStart_toEndOf="@+id/right_shoulder_button"
app:layout_constraintTop_toBottomOf="@+id/right_collar_button" />
<Button
android:id="@+id/left_collar_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="45dp"
android:layout_marginEnd="415dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/left_breast_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/neck_button"
app:layout_constraintTop_toBottomOf="@+id/left_eye_button" />
<Button
android:id="@+id/forehead_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="200dp"
android:background="@drawable/circle_button"
android:text="99"
android:textSize="11sp"
app:layout_constraintBottom_toTopOf="@+id/front_nose_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/front_nose_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
android:textSize="11sp"
app:layout_constraintBottom_toTopOf="@+id/lips_button"
app:layout_constraintEnd_toStartOf="@+id/left_eye_button"
app:layout_constraintStart_toEndOf="@+id/right_eye_button"
app:layout_constraintTop_toBottomOf="@+id/forehead_button" />
<Button
android:id="@+id/right_eye_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginStart="400dp"
android:layout_marginTop="60dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/right_collar_button"
app:layout_constraintEnd_toStartOf="@+id/front_nose_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/left_eye_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="400dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/left_collar_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/front_nose_button"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/lips_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/chin_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/front_nose_button" />
<Button
android:id="@+id/chin_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/neck_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/lips_button" />
<Button
android:id="@+id/neck_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/stomach_button"
app:layout_constraintEnd_toStartOf="@id/left_collar_button"
app:layout_constraintStart_toEndOf="@+id/right_collar_button"
app:layout_constraintTop_toBottomOf="@+id/chin_button" />
<Button
android:id="@+id/right_collar_button"
android:layout_width="47dp"
android:layout_height="32dp"
android:layout_marginStart="415dp"
android:layout_marginTop="45dp"
android:background="@drawable/circle_button"
app:layout_constraintBottom_toTopOf="@+id/right_breast_button"
app:layout_constraintEnd_toStartOf="@+id/neck_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/right_eye_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -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>

View File

@ -0,0 +1,21 @@
<?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="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>

View File

@ -12,5 +12,6 @@
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="buttonIcon">@color/white</item>
</style> </style>
</resources> </resources>

View File

@ -1,5 +1,34 @@
<resources> <resources>
<string name="app_name">dermy_app</string> <string name="app_name">Dermy</string>
<!-- TODO: Remove or change this placeholder text --> <!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string> <string name="hello_blank_fragment">Hello blank fragment</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">
1. General Information
\nThe information provided by Dermy (“we,” “us” or “our”) on [Your App Name] (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-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> </resources>

View File

@ -12,5 +12,6 @@
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="buttonIcon">@color/black</item>
</style> </style>
</resources> </resources>

View File

@ -1,4 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:app="http://schemas.android.com/apk/res-auto">
</androidx.preference.PreferenceScreen> <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>

View File

@ -1,6 +1,7 @@
[versions] [versions]
agp = "8.4.1" agp = "8.4.1"
fragmentKtx = "1.7.1" fragmentKtx = "1.7.1"
jsoup = "1.17.2"
kotlin = "1.9.0" kotlin = "1.9.0"
coreKtx = "1.13.1" coreKtx = "1.13.1"
junit = "4.13.2" junit = "4.13.2"
@ -12,12 +13,19 @@ cameraView = "1.3.3"
cameraCore = "1.3.3" cameraCore = "1.3.3"
cameraLifecycle = "1.3.3" cameraLifecycle = "1.3.3"
mongodbDriverKotlinSync = "5.1.1" mongodbDriverKotlinSync = "5.1.1"
playServicesCronet = "18.1.0"
preference = "1.2.1" preference = "1.2.1"
retrofit = "1"
retrofitVersion = "2.11.0"
roomCommon = "2.6.1" roomCommon = "2.6.1"
volley = "1.2.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }
converter-jackson = { module = "com.squareup.retrofit2:converter-jackson", version.ref = "retrofitVersion" }
jsoup = { module = "org.jsoup:jsoup" }
jsoup-jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@ -29,6 +37,10 @@ androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycl
androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" } androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "roomCommon" } androidx-room-common = { group = "androidx.room", name = "room-common", version.ref = "roomCommon" }
mongodb-driver-kotlin-sync = { module = "org.mongodb:mongodb-driver-kotlin-sync", version.ref = "mongodbDriverKotlinSync" } mongodb-driver-kotlin-sync = { module = "org.mongodb:mongodb-driver-kotlin-sync", version.ref = "mongodbDriverKotlinSync" }
play-services-cronet = { module = "com.google.android.gms:play-services-cronet", version.ref = "playServicesCronet" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-v2110 = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofitVersion" }
volley = { module = "com.android.volley:volley", version.ref = "volley" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }