-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add: Image share option for dive plans
Right now this serves as an initial starting point, a sort of MVP implementation. It would be good to offer more information in shared images in the future; like graphs, additional general information (max depth, average depth) and more. Also the way in which compose is used to render images is experimental at best. Implements: #1
- Loading branch information
Showing
26 changed files
with
1,039 additions
and
235 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
...roidMain/kotlin/org/neotech/app/abysner/presentation/utilities/ShareExtensions.android.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Abysner - Dive planner | ||
* Copyright (C) 2024 Neotech | ||
* | ||
* Abysner is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License version 3, | ||
* as published by the Free Software Foundation. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see https://www.gnu.org/licenses/. | ||
*/ | ||
|
||
package org.neotech.app.abysner.presentation.utilities | ||
|
||
import android.content.Intent | ||
import android.graphics.Bitmap | ||
import android.net.Uri | ||
import androidx.compose.ui.graphics.ImageBitmap | ||
import androidx.compose.ui.graphics.asAndroidBitmap | ||
import androidx.core.content.FileProvider | ||
import org.neotech.app.abysner.applicationContext | ||
import org.neotech.app.abysner.currentActivity | ||
import java.io.File | ||
import java.io.FileOutputStream | ||
|
||
actual fun shareImageBitmap(image: ImageBitmap) { | ||
val bitmap = image.asAndroidBitmap() | ||
|
||
val file = File(applicationContext.cacheDir, "share/shared-image.png") | ||
file.parentFile?.mkdirs() | ||
FileOutputStream(file).use { out -> | ||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out) | ||
} | ||
|
||
val uri: Uri = FileProvider.getUriForFile( | ||
applicationContext, | ||
"${applicationContext.packageName}.provider", | ||
file | ||
) | ||
|
||
val intent = Intent(Intent.ACTION_SEND).apply { | ||
type = "image/png" | ||
putExtra(Intent.EXTRA_STREAM, uri) | ||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) | ||
} | ||
|
||
currentActivity.get()!!.startActivity(Intent.createChooser(intent, "Share Image")) | ||
} |
15 changes: 15 additions & 0 deletions
15
composeApp/src/androidMain/res/xml/file_provider_paths.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!-- | ||
~ Abysner - Dive planner | ||
~ Copyright (C) 2024 Neotech | ||
~ | ||
~ Abysner is free software: you can redistribute it and/or modify | ||
~ it under the terms of the GNU Affero General Public License version 3, | ||
~ as published by the Free Software Foundation. | ||
~ | ||
~ You should have received a copy of the GNU Affero General Public License | ||
~ along with this program. If not, see https://www.gnu.org/licenses/. | ||
--> | ||
|
||
<paths xmlns:android="http://schemas.android.com/apk/res/android"> | ||
<cache-path name="cache" path="share" /> | ||
</paths> |
5 changes: 5 additions & 0 deletions
5
composeApp/src/commonMain/composeResources/drawable/ic_outline_share_24_android.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||
|
||
<path android:fillColor="#ffffff" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92c0,-1.61 -1.31,-2.92 -2.92,-2.92zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20.02c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/> | ||
|
||
</vector> |
5 changes: 5 additions & 0 deletions
5
composeApp/src/commonMain/composeResources/drawable/ic_outline_share_24_ios.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="512" android:viewportWidth="512" android:width="24dp"> | ||
|
||
<path android:pathData="M336,192h40a40,40 0,0 1,40 40v192a40,40 0,0 1,-40 40H136a40,40 0,0 1,-40 -40V232a40,40 0,0 1,40 -40h40M336,128l-80,-80 -80,80M256,321V48" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="32"/> | ||
|
||
</vector> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/preview/PreviewData.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Abysner - Dive planner | ||
* Copyright (C) 2024 Neotech | ||
* | ||
* Abysner is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License version 3, | ||
* as published by the Free Software Foundation. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see https://www.gnu.org/licenses/. | ||
*/ | ||
|
||
package org.neotech.app.abysner.presentation.preview | ||
|
||
import org.neotech.app.abysner.domain.core.model.Configuration | ||
import org.neotech.app.abysner.domain.core.model.Cylinder | ||
import org.neotech.app.abysner.domain.core.model.Gas | ||
import org.neotech.app.abysner.domain.diveplanning.DivePlanner | ||
import org.neotech.app.abysner.domain.diveplanning.model.DivePlanSet | ||
import org.neotech.app.abysner.domain.diveplanning.model.DiveProfileSection | ||
import org.neotech.app.abysner.domain.gasplanning.GasPlanner | ||
|
||
object PreviewData { | ||
val divePlan: DivePlanSet | ||
get() { | ||
val divePlan = DivePlanner().apply { | ||
configuration = Configuration() | ||
}.getDecoPlan( | ||
plan = listOf( | ||
DiveProfileSection(16, 45, Cylinder(gas = Gas.Air, pressure = 232.0, waterVolume = 12.0)), | ||
), | ||
decoGases = listOf(Cylinder.aluminium80Cuft(Gas.Oxygen50)), | ||
) | ||
|
||
val gasPlan = GasPlanner().calculateGasPlan( | ||
divePlan | ||
) | ||
return DivePlanSet( | ||
base = divePlan, | ||
deeperAndLonger = divePlan, | ||
gasPlan = gasPlan | ||
) | ||
} | ||
} |
206 changes: 206 additions & 0 deletions
206
composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/ShareImage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
/* | ||
* Abysner - Dive planner | ||
* Copyright (C) 2024 Neotech | ||
* | ||
* Abysner is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License version 3, | ||
* as published by the Free Software Foundation. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see https://www.gnu.org/licenses/. | ||
*/ | ||
|
||
package org.neotech.app.abysner.presentation.screens | ||
|
||
import abysner.composeapp.generated.resources.Res | ||
import abysner.composeapp.generated.resources.abysner_logo | ||
import androidx.compose.desktop.ui.tooling.preview.Preview | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Row | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.layout.width | ||
import androidx.compose.material3.Card | ||
import androidx.compose.material3.LocalTextStyle | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.Text | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.CompositionLocalProvider | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.draw.drawBehind | ||
import androidx.compose.ui.graphics.drawscope.translate | ||
import androidx.compose.ui.graphics.vector.rememberVectorPainter | ||
import androidx.compose.ui.platform.LocalDensity | ||
import androidx.compose.ui.text.buildAnnotatedString | ||
import androidx.compose.ui.text.font.FontStyle | ||
import androidx.compose.ui.text.font.FontWeight | ||
import androidx.compose.ui.text.style.TextAlign | ||
import androidx.compose.ui.unit.Density | ||
import androidx.compose.ui.unit.dp | ||
import kotlinx.datetime.Clock | ||
import kotlinx.datetime.LocalDate | ||
import kotlinx.datetime.TimeZone | ||
import kotlinx.datetime.format | ||
import kotlinx.datetime.toLocalDateTime | ||
import org.jetbrains.compose.resources.vectorResource | ||
import org.neotech.app.abysner.domain.diveplanning.model.DivePlanSet | ||
import org.neotech.app.abysner.domain.settings.model.SettingsModel | ||
import org.neotech.app.abysner.domain.utilities.format | ||
import org.neotech.app.abysner.presentation.component.BigNumberDisplay | ||
import org.neotech.app.abysner.presentation.component.BigNumberSize | ||
import org.neotech.app.abysner.presentation.component.appendBold | ||
import org.neotech.app.abysner.presentation.preview.PreviewData | ||
import org.neotech.app.abysner.presentation.screens.planner.decoplan.DecoPlanTable | ||
import org.neotech.app.abysner.presentation.screens.planner.gasplan.CylindersTable | ||
import org.neotech.app.abysner.presentation.screens.planner.gasplan.GasLimitsTable | ||
import org.neotech.app.abysner.presentation.theme.AbysnerTheme | ||
import org.neotech.app.abysner.presentation.theme.platform | ||
import org.neotech.app.abysner.version.VersionInfo | ||
|
||
@Composable | ||
fun ShareImage( | ||
divePlan: DivePlanSet, | ||
settingsModel: SettingsModel, | ||
) { | ||
// Disable scaling and dark theme and dynamic color, the image should be the same for every device. | ||
CompositionLocalProvider(LocalDensity provides Density(LocalDensity.current.density, 1f)) { | ||
AbysnerTheme(darkTheme = false, dynamicColor = false) { | ||
Card { | ||
|
||
val backgroundImage = rememberVectorPainter(vectorResource(Res.drawable.abysner_logo)) | ||
|
||
Column( | ||
modifier = Modifier | ||
.drawBehind { | ||
val vectorSize = backgroundImage.intrinsicSize.times(2f) | ||
translate( | ||
left = (size.width - vectorSize.width) / 2f, | ||
top = (size.height - vectorSize.height) / 2f | ||
) { | ||
with(backgroundImage) { | ||
draw(size = backgroundImage.intrinsicSize.times(2f), alpha = 0.05f) | ||
} | ||
} | ||
} | ||
.padding(vertical = 16.dp, horizontal = 16.dp) | ||
.fillMaxWidth() | ||
) { | ||
Text( | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(bottom = 16.dp), | ||
textAlign = TextAlign.Center, | ||
style = MaterialTheme.typography.titleLarge, | ||
text = "Dive plan" | ||
) | ||
DecoPlanTable( | ||
divePlan = divePlan.base, | ||
settings = settingsModel | ||
) | ||
|
||
Row( | ||
modifier = Modifier.padding(top = 16.dp).fillMaxWidth(), | ||
horizontalArrangement = Arrangement.spacedBy( | ||
16.dp, | ||
Alignment.CenterHorizontally | ||
), | ||
) { | ||
BigNumberDisplay( | ||
modifier = Modifier.width(96.dp), | ||
size = BigNumberSize.SMALL, | ||
value = "${divePlan.base.totalCns.format(0)}%", | ||
label = "CNS" | ||
) | ||
BigNumberDisplay( | ||
modifier = Modifier.width(96.dp), | ||
size = BigNumberSize.SMALL, | ||
value = divePlan.base.totalOtu.format(0), | ||
label = "OTU" | ||
) | ||
} | ||
|
||
// DecoPlanExtraInfo( | ||
// modifier = Modifier.padding(horizontal = 16.dp).padding(top = 16.dp), | ||
// divePlan = divePlan.base | ||
// ) | ||
|
||
Text( | ||
modifier = Modifier.padding(top = 16.dp, bottom = 4.dp), | ||
text = "Cylinders", | ||
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold) | ||
) | ||
CylindersTable(divePlanSet = divePlan) | ||
|
||
Text( | ||
modifier = Modifier.padding(top = 16.dp, bottom = 4.dp), | ||
text = "Limits", | ||
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold) | ||
) | ||
GasLimitsTable(divePlanSet = divePlan) | ||
|
||
Text( | ||
modifier = Modifier.padding(top = 16.dp, bottom = 4.dp), | ||
text = "Configuration", | ||
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold) | ||
) | ||
|
||
val configuration = divePlan.configuration | ||
|
||
Text( | ||
text = buildAnnotatedString { | ||
appendBold("Deco model: ") | ||
append(configuration.algorithm.shortName) | ||
append(" (${configuration.gf})") | ||
}, | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
Text( | ||
text = buildAnnotatedString { | ||
appendBold("Salinity: ") | ||
append(configuration.environment.salinity.humanReadableName) | ||
append(" (${configuration.environment.salinity.density.format(0)} kg/m3)") | ||
}, | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
Text( | ||
text = buildAnnotatedString { | ||
appendBold("Atmospheric pressure: ") | ||
append(configuration.environment.atmosphericPressure.format(3)) | ||
append(" hPa") | ||
append(" (${configuration.altitude.format(0)} meters)") | ||
}, | ||
style = MaterialTheme.typography.bodySmall | ||
) | ||
|
||
val date = | ||
Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date | ||
Text( | ||
style = MaterialTheme.typography.bodySmall.copy( | ||
fontStyle = FontStyle.Italic, | ||
color = LocalTextStyle.current.color.copy(alpha = 0.8f) | ||
), | ||
modifier = Modifier.padding(top = 16.dp) | ||
.align(Alignment.CenterHorizontally), | ||
textAlign = TextAlign.Center, | ||
text = "Created with Abysner for ${platform().humanReadable} ${VersionInfo.VERSION_NAME} (${VersionInfo.COMMIT_HASH})\non ${date.format(LocalDate.Formats.ISO)}" | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
@Preview | ||
@Composable | ||
private fun ShareImagePreview() { | ||
ShareImage( | ||
divePlan = PreviewData.divePlan, | ||
settingsModel = SettingsModel( | ||
showBasicDecoTable = true, | ||
termsAndConditionsAccepted = true | ||
) | ||
) | ||
} |
Oops, something went wrong.