Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion toolkit/offline/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ apiValidation {
"com.arcgismaps.toolkit.offline.ui.ComposableSingletons\$MapAreaDetailsScreenKt",
"com.arcgismaps.toolkit.offline.ui.material3.ComposableSingletons\$ModalBottomSheetKt",
"com.arcgismaps.toolkit.offline.ui.ComposableSingletons\$OfflineMapAreasStatusScreenKt",
"com.arcgismaps.toolkit.offline.ondemand.ComposableSingletons\$OnDemandMapAreaSelectorKt"
"com.arcgismaps.toolkit.offline.ondemand.ComposableSingletons\$OnDemandMapAreaSelectorKt",
"com.arcgismaps.toolkit.offline.internal.utils.ComposableSingletons\$ButtonsKt",
"com.arcgismaps.toolkit.offline.ondemand.ComposableSingletons\$OnDemandMapAreasKt"
)
ignoredClasses.addAll(composableSingletons)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.arcgismaps.toolkit.offline.ondemand.OnDemandMapAreaSelector
import com.arcgismaps.toolkit.offline.ondemand.OnDemandMapAreas
import com.arcgismaps.toolkit.offline.preplanned.PreplannedMapAreas
import com.arcgismaps.toolkit.offline.ui.EmptyOnDemandOfflineAreas
import com.arcgismaps.toolkit.offline.ui.EmptyPreplannedOfflineAreas
Expand Down Expand Up @@ -112,11 +113,22 @@ public fun OfflineMapAreas(
}
// If not preplanned state & map has offline mode enabled, display the on demand areas
OfflineMapMode.OnDemand, OfflineMapMode.Unknown -> {
EmptyOnDemandOfflineAreas(
onAdd = {
isOnDemandMapAreaSelectorVisible = true
}
)
if (!isOnDemandMapAreaSelectorVisible && offlineMapState.onDemandMapAreaStates.isNotEmpty()) {
OnDemandMapAreas(
onDemandMapAreasStates = offlineMapState.onDemandMapAreaStates,
modifier = modifier,
onDownloadNewMapArea = {
isOnDemandMapAreaSelectorVisible = true
}
)
}
if (offlineMapState.onDemandMapAreaStates.isEmpty()) {
EmptyOnDemandOfflineAreas(
onAdd = {
isOnDemandMapAreaSelectorVisible = true
}
)
}
OnDemandMapAreaSelector(
localMap = offlineMapState.localMap,
showBottomSheet = isOnDemandMapAreaSelectorVisible,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import com.arcgismaps.tasks.offlinemaptask.OfflineMapTask
import com.arcgismaps.tasks.offlinemaptask.PreplannedMapArea
import com.arcgismaps.toolkit.offline.ondemand.OnDemandMapAreasState
import com.arcgismaps.toolkit.offline.preplanned.PreplannedMapAreaState
import com.arcgismaps.toolkit.offline.preplanned.Status
import com.arcgismaps.toolkit.offline.preplanned.PreplannedStatus
import com.arcgismaps.toolkit.offline.workmanager.OfflineURLs
import kotlinx.coroutines.CancellationException
import java.io.File
Expand Down Expand Up @@ -209,7 +209,7 @@ public class OfflineMapState(
preplannedMapAreaID = mapArea.portalItem.itemId
)
if (preplannedPath != null) {
preplannedMapAreaState.updateStatus(Status.Downloaded)
preplannedMapAreaState.updateStatus(PreplannedStatus.Downloaded)
preplannedMapAreaState.createAndLoadMMPKAndOfflineMap(
mobileMapPackagePath = preplannedPath
)
Expand Down Expand Up @@ -296,7 +296,7 @@ public class OfflineMapState(
preplannedMapAreaID = areaItemId
)
if (preplannedPath != null) {
preplannedMapAreaState.updateStatus(Status.Downloaded)
preplannedMapAreaState.updateStatus(PreplannedStatus.Downloaded)
preplannedMapAreaState.createAndLoadMMPKAndOfflineMap(
mobileMapPackagePath = preplannedPath
)
Expand All @@ -312,6 +312,7 @@ public class OfflineMapState(
*/
public fun resetSelectedMapArea() {
_preplannedMapAreaStates.forEach { it.setSelectedToOpen(false) }
_onDemandMapAreaStates.forEach { it.setSelectedToOpen(false) }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ import androidx.work.WorkManager
import androidx.work.workDataOf
import com.arcgismaps.mapping.PortalItem
import com.arcgismaps.toolkit.offline.ondemand.OnDemandMapAreasState
import com.arcgismaps.toolkit.offline.ondemand.OnDemandStatus
import com.arcgismaps.toolkit.offline.preplanned.PreplannedMapAreaState
import com.arcgismaps.toolkit.offline.preplanned.Status
import com.arcgismaps.toolkit.offline.preplanned.PreplannedStatus
import com.arcgismaps.toolkit.offline.workmanager.OfflineURLs
import com.arcgismaps.toolkit.offline.workmanager.OnDemandMapAreaJobWorker
import com.arcgismaps.toolkit.offline.workmanager.PreplannedMapAreaJobWorker
Expand Down Expand Up @@ -454,7 +455,7 @@ public object OfflineRepository {
when (workInfo.state) {
// if work completed successfully
WorkInfo.State.SUCCEEDED -> {
preplannedMapAreaState.updateStatus(Status.Downloaded)
preplannedMapAreaState.updateStatus(PreplannedStatus.Downloaded)
workInfo.outputData.getString(mobileMapPackagePathKey)?.let { path ->
// using the pending path, move the result to final destination path
val destDir = movePreplannedJobResultToDestination(context, path)
Expand All @@ -479,7 +480,7 @@ public object OfflineRepository {
}
} ?: run {
preplannedMapAreaState.updateStatus(
Status.MmpkLoadFailure(
PreplannedStatus.MmpkLoadFailure(
Exception("Mobile Map Package path is null")
)
)
Expand All @@ -493,7 +494,7 @@ public object OfflineRepository {
// until WorkManager auto-prunes
workManager.pruneWork()
preplannedMapAreaState.updateStatus(
Status.DownloadFailure(
PreplannedStatus.DownloadFailure(
Exception(
"${workInfo.tags}: FAILED. Reason: " +
"${workInfo.outputData.getString("Error")}"
Expand All @@ -504,7 +505,7 @@ public object OfflineRepository {
}
// if the work is currently in progress
WorkInfo.State.RUNNING -> {
preplannedMapAreaState.updateStatus(Status.Downloading)
preplannedMapAreaState.updateStatus(PreplannedStatus.Downloading)
}
// don't have to handle other states
else -> {}
Expand Down Expand Up @@ -548,8 +549,7 @@ public object OfflineRepository {
when (workInfo.state) {
// if work completed successfully
WorkInfo.State.SUCCEEDED -> {
// TODO: Wire in status handler
// onDemandMapAreasState.updateStatus(Status.Downloaded)
onDemandMapAreasState.updateStatus(OnDemandStatus.Downloaded)
workInfo.outputData.getString(mobileMapPackagePathKey)?.let { path ->
// using the pending path, move the result to final destination path
val destDir = moveOnDemandJobResultToDestination(context, path)
Expand All @@ -573,14 +573,11 @@ public object OfflineRepository {
}
}
} ?: run {
// TODO: Wire in status handler
/*
onDemandMapAreasState.updateStatus(
Status.MmpkLoadFailure(
OnDemandStatus.MmpkLoadFailure(
Exception("Mobile Map Package path is null")
)
)
*/
}
onDemandMapAreasState.disposeScope()
}
Expand All @@ -590,23 +587,19 @@ public object OfflineRepository {
// otherwise, the observer will emit the WorkInfo on every launch
// until WorkManager auto-prunes
workManager.pruneWork()
// TODO: Wire in status handler
/*
preplannedMapAreaState.updateStatus(
Status.DownloadFailure(
onDemandMapAreasState.updateStatus(
OnDemandStatus.DownloadFailure(
Exception(
"${workInfo.tags}: FAILED. Reason: " +
"${workInfo.outputData.getString("Error")}"
)
)
)
*/
onDemandMapAreasState.disposeScope()
}
// if the work is currently in progress
WorkInfo.State.RUNNING -> {
// TODO: Wire in status handler
// preplannedMapAreaState.updateStatus(Status.Downloading)
onDemandMapAreasState.updateStatus(OnDemandStatus.Downloading)
}
// don't have to handle other states
else -> {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
*
* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.arcgismaps.toolkit.offline.internal.utils

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.arcgismaps.toolkit.offline.R

@Composable
internal fun DownloadButton(onClick: () -> Unit) {
IconButton(
modifier = Modifier.size(30.dp),
onClick = onClick
) {
Icon(
imageVector = Icons.Filled.Download,
contentDescription = stringResource(R.string.download),
tint = MaterialTheme.colorScheme.primary,
)
}
}

@Composable
internal fun CancelDownloadButtonWithProgressIndicator(progress: Int, onClick: () -> Unit) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(30.dp)
.clickable { onClick.invoke() }
) {
// Circular Progress Indicator
CircularProgressIndicator(
progress = { progress / 100f },
)
// Square Button to cancel the download
Box(
modifier = Modifier
.size(10.dp)
.clip(RectangleShape)
.background(ButtonDefaults.buttonColors().containerColor),
)
}
}

@Composable
internal fun OpenButton(isEnabled: Boolean, onClick: () -> Unit) {
Button(
modifier = Modifier.widthIn(max = 80.dp), // restricts max width
contentPadding = PaddingValues(horizontal = 10.dp),
enabled = isEnabled,
onClick = onClick
) {
Text(
text = stringResource(R.string.open),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}

@Preview(showBackground = true)
@Composable
private fun ButtonsPreview() {
MaterialTheme {
Surface {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
DownloadButton(
onClick = { }
)
CancelDownloadButtonWithProgressIndicator(
progress = 55,
onClick = { }
)
OpenButton(
isEnabled = true,
onClick = { }
)
OpenButton(
isEnabled = false,
onClick = { }
)
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

package com.arcgismaps.toolkit.offline.internal.utils

import java.io.File
import android.text.Html
import java.io.File

internal fun getDirectorySize(path: String): Int {
val file = File(path)
Expand Down
Loading