Compare commits

...

2 Commits

Author SHA1 Message Date
d5cf14d466 2.0.0 - Rebranding
All checks were successful
Ascently Docker Deploy / build-and-push (push) Successful in 2m18s
2025-10-13 15:10:54 -06:00
09b4055985 Moved to Ascently
All checks were successful
Ascently Docker Deploy / build-and-push (push) Successful in 2m31s
2025-10-13 14:54:54 -06:00
137 changed files with 796 additions and 489 deletions

View File

@@ -1,4 +1,4 @@
name: OpenClimb Docker Deploy
name: Ascently Docker Deploy
on:
push:
branches: [main]
@@ -34,5 +34,5 @@ jobs:
platforms: linux/amd64
push: true
tags: |
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/openclimb-sync:${{ github.sha }}
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/openclimb-sync:latest
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/ascently-sync:${{ github.sha }}
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/ascently-sync:latest

View File

@@ -1,13 +1,15 @@
# OpenClimb
# Ascently
This is a FOSS app meant to help climbers track their sessions, routes/problems, and overall progress. This app is offline-only and requires no special permissions to run. Its built using Jetpack Compose with Material You support on Android and SwiftUI on iOS.
_Formerly OpenClimb_
This is a FOSS app meant to help climbers track their sessions, routes/problems, and overall progress. This app is offline-first, with an optional sync server and integrations with Apple Health and Health Connect. Its built using Jetpack Compose with Material You support on Android and SwiftUI on iOS.
## Download
For Android do one of the following:
1. Download the latest APK from the Releases page
2. [<img src="https://github.com/ImranR98/Obtainium/blob/main/assets/graphics/badge_obtainium.png?raw=true" alt="Obtainium" height="41">](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.atridad.openclimb%22%2C%22url%22%3A%22https%3A%2F%2Fgit.atri.dad%2Fatridad%2FOpenClimb%2Freleases%22%2C%22author%22%3A%22git.atri.dad%22%2C%22name%22%3A%22OpenClimb%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22intermediateLink%5C%22%3A%5B%5D%2C%5C%22customLinkFilterRegex%5C%22%3A%5C%22%5C%22%2C%5C%22filterByLinkText%5C%22%3Afalse%2C%5C%22skipSort%5C%22%3Afalse%2C%5C%22reverseSort%5C%22%3Afalse%2C%5C%22sortByLastLinkSegment%5C%22%3Afalse%2C%5C%22versionExtractWholePage%5C%22%3Afalse%2C%5C%22requestHeader%5C%22%3A%5B%7B%5C%22requestHeader%5C%22%3A%5C%22User-Agent%3A%20Mozilla%2F5.0%20(Linux%3B%20Android%2010%3B%20K)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F114.0.0.0%20Mobile%20Safari%2F537.36%5C%22%7D%5D%2C%5C%22defaultPseudoVersioningMethod%5C%22%3A%5C%22partialAPKHash%5C%22%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22OpenClimb%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%7D%22%2C%22overrideSource%22%3Anull%7D)
2. [<img src="https://github.com/ImranR98/Obtainium/blob/main/assets/graphics/badge_obtainium.png?raw=true" alt="Obtainium" height="41">](https://apps.obtainium.imranr.dev/redirect?r=obtainium://app/%7B%22id%22%3A%22com.atridad.ascently%22%2C%22url%22%3A%22https%3A%2F%2Fgit.atri.dad%2Fatridad%2FAscently%2Freleases%22%2C%22author%22%3A%22git.atri.dad%22%2C%22name%22%3A%22Ascently%22%2C%22preferredApkIndex%22%3A0%2C%22additionalSettings%22%3A%22%7B%5C%22intermediateLink%5C%22%3A%5B%5D%2C%5C%22customLinkFilterRegex%5C%22%3A%5C%22%5C%22%2C%5C%22filterByLinkText%5C%22%3Afalse%2C%5C%22skipSort%5C%22%3Afalse%2C%5C%22reverseSort%5C%22%3Afalse%2C%5C%22sortByLastLinkSegment%5C%22%3Afalse%2C%5C%22versionExtractWholePage%5C%22%3Afalse%2C%5C%22requestHeader%5C%22%3A%5B%7B%5C%22requestHeader%5C%22%3A%5C%22User-Agent%3A%20Mozilla%2F5.0%20(Linux%3B%20Android%2010%3B%20K)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F114.0.0.0%20Mobile%20Safari%2F537.36%5C%22%7D%5D%2C%5C%22defaultPseudoVersioningMethod%5C%22%3A%5C%22partialAPKHash%5C%22%2C%5C%22trackOnly%5C%22%3Afalse%2C%5C%22versionExtractionRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22matchGroupToUse%5C%22%3A%5C%22%5C%22%2C%5C%22versionDetection%5C%22%3Afalse%2C%5C%22useVersionCodeAsOSVersion%5C%22%3Afalse%2C%5C%22apkFilterRegEx%5C%22%3A%5C%22%5C%22%2C%5C%22invertAPKFilter%5C%22%3Afalse%2C%5C%22autoApkFilterByArch%5C%22%3Atrue%2C%5C%22appName%5C%22%3A%5C%22Ascently%5C%22%2C%5C%22appAuthor%5C%22%3A%5C%22%5C%22%2C%5C%22shizukuPretendToBeGooglePlay%5C%22%3Afalse%2C%5C%22allowInsecure%5C%22%3Afalse%2C%5C%22exemptFromBackgroundUpdates%5C%22%3Afalse%2C%5C%22skipUpdateNotifications%5C%22%3Afalse%2C%5C%22about%5C%22%3A%5C%22%5C%22%2C%5C%22refreshBeforeDownload%5C%22%3Afalse%7D%22%2C%22overrideSource%22%3Anull%7D)
For iOS:
@@ -22,12 +24,12 @@ You can run your own sync server to keep your data in sync across devices. The s
1. Create a `.env` file with your configuration:
```
IMAGE=git.atri.dad/atridad/openclimb-sync:latest
IMAGE=git.atri.dad/atridad/ascently-sync:latest
APP_PORT=8080
AUTH_TOKEN=your-secure-auth-token-here
DATA_FILE=/data/openclimb.json
DATA_FILE=/data/ascently.json
IMAGES_DIR=/data/images
ROOT_DIR=./openclimb-data
ROOT_DIR=./ascently-data
```
2. Use the provided `docker-compose.yml` in the `sync/` directory:

View File

@@ -1,10 +1,10 @@
# OpenClimb for Android
# Ascently for Android
This is the native Android app for OpenClimb, built with Kotlin and Jetpack Compose.
This is the native Android app for Ascently, built with Kotlin and Jetpack Compose.
## Project Structure
This is a standard Android Gradle project. The main code lives in `app/src/main/java/com/atridad/openclimb/`.
This is a standard Android Gradle project. The main code lives in `app/src/main/java/com/atridad/ascently/`.
- `data/`: Handles all the app's data.
- `database/`: Room database setup (DAOs, entities).

View File

@@ -9,15 +9,15 @@ plugins {
}
android {
namespace = "com.atridad.openclimb"
namespace = "com.atridad.ascently"
compileSdk = 36
defaultConfig {
applicationId = "com.atridad.openclimb"
applicationId = "com.atridad.ascently"
minSdk = 31
targetSdk = 36
versionCode = 39
versionName = "1.9.2"
versionCode = 40
versionName = "2.0.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -9,11 +9,11 @@ plugins {
}
android {
namespace = "com.atridad.openclimb"
namespace = "com.atridad.ascently"
compileSdk = 36
defaultConfig {
applicationId = "com.atridad.openclimb"
applicationId = "com.atridad.ascently"
minSdk = 31
targetSdk = 36
versionCode = 27

View File

@@ -50,13 +50,13 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OpenClimb"
android:theme="@style/Theme.Ascently"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.OpenClimb.Splash">
android:theme="@style/Theme.Ascently.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb
package com.atridad.ascently
import android.content.Intent
import android.os.Bundle
@@ -9,8 +9,9 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import com.atridad.openclimb.ui.OpenClimbApp
import com.atridad.openclimb.ui.theme.OpenClimbTheme
import com.atridad.ascently.ui.AscentlyApp
import com.atridad.ascently.ui.theme.AscentlyTheme
import com.atridad.ascently.utils.MigrationManager
class MainActivity : ComponentActivity() {
private var shortcutAction by mutableStateOf<String?>(null)
@@ -23,16 +24,19 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.Theme_OpenClimb)
setTheme(R.style.Theme_Ascently)
enableEdgeToEdge()
// Perform migration from OpenClimb to Ascently if needed
MigrationManager(this).migrateIfNeeded()
shortcutAction = intent?.action
lastUsedGymId = intent?.getStringExtra("LAST_USED_GYM_ID")
setContent {
OpenClimbTheme {
AscentlyTheme {
Surface(modifier = Modifier.fillMaxSize()) {
OpenClimbApp(
AscentlyApp(
shortcutAction = shortcutAction,
lastUsedGymId = lastUsedGymId,
onShortcutActionProcessed = { clearShortcutAction() }

View File

@@ -1,7 +1,7 @@
package com.atridad.openclimb.data.database
package com.atridad.ascently.data.database
import androidx.room.TypeConverter
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.model.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.data.database
package com.atridad.ascently.data.database
import android.content.Context
import androidx.room.Database
@@ -7,8 +7,8 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.atridad.openclimb.data.database.dao.*
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.database.dao.*
import com.atridad.ascently.data.model.*
@Database(
entities = [Gym::class, Problem::class, ClimbSession::class, Attempt::class],
@@ -16,7 +16,7 @@ import com.atridad.openclimb.data.model.*
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class OpenClimbDatabase : RoomDatabase() {
abstract class AscentlyDatabase : RoomDatabase() {
abstract fun gymDao(): GymDao
abstract fun problemDao(): ProblemDao
@@ -24,7 +24,7 @@ abstract class OpenClimbDatabase : RoomDatabase() {
abstract fun attemptDao(): AttemptDao
companion object {
@Volatile private var INSTANCE: OpenClimbDatabase? = null
@Volatile private var INSTANCE: AscentlyDatabase? = null
val MIGRATION_4_5 =
object : Migration(4, 5) {
@@ -84,14 +84,14 @@ abstract class OpenClimbDatabase : RoomDatabase() {
}
}
fun getDatabase(context: Context): OpenClimbDatabase {
fun getDatabase(context: Context): AscentlyDatabase {
return INSTANCE
?: synchronized(this) {
val instance =
Room.databaseBuilder(
context.applicationContext,
OpenClimbDatabase::class.java,
"openclimb_database"
AscentlyDatabase::class.java,
"ascently_database"
)
.addMigrations(MIGRATION_4_5, MIGRATION_5_6)
.enableMultiInstanceInvalidation()

View File

@@ -1,8 +1,8 @@
package com.atridad.openclimb.data.database.dao
package com.atridad.ascently.data.database.dao
import androidx.room.*
import com.atridad.openclimb.data.model.Attempt
import com.atridad.openclimb.data.model.AttemptResult
import com.atridad.ascently.data.model.Attempt
import com.atridad.ascently.data.model.AttemptResult
import kotlinx.coroutines.flow.Flow
@Dao

View File

@@ -1,8 +1,8 @@
package com.atridad.openclimb.data.database.dao
package com.atridad.ascently.data.database.dao
import androidx.room.*
import com.atridad.openclimb.data.model.ClimbSession
import com.atridad.openclimb.data.model.SessionStatus
import com.atridad.ascently.data.model.ClimbSession
import com.atridad.ascently.data.model.SessionStatus
import kotlinx.coroutines.flow.Flow
@Dao

View File

@@ -1,8 +1,8 @@
package com.atridad.openclimb.data.database.dao
package com.atridad.ascently.data.database.dao
import androidx.room.*
import com.atridad.openclimb.data.model.ClimbType
import com.atridad.openclimb.data.model.Gym
import com.atridad.ascently.data.model.ClimbType
import com.atridad.ascently.data.model.Gym
import kotlinx.coroutines.flow.Flow
@Dao

View File

@@ -1,8 +1,8 @@
package com.atridad.openclimb.data.database.dao
package com.atridad.ascently.data.database.dao
import androidx.room.*
import com.atridad.openclimb.data.model.ClimbType
import com.atridad.openclimb.data.model.Problem
import com.atridad.ascently.data.model.ClimbType
import com.atridad.ascently.data.model.Problem
import kotlinx.coroutines.flow.Flow
@Dao

View File

@@ -1,9 +1,9 @@
package com.atridad.openclimb.data.format
package com.atridad.ascently.data.format
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.model.*
import kotlinx.serialization.Serializable
// Root structure for OpenClimb backup data
// Root structure for Ascently backup data
@Serializable
data class ClimbDataBackup(
val exportedAt: String,

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.data.health
package com.atridad.ascently.data.health
import android.annotation.SuppressLint
import android.content.Context
@@ -12,9 +12,9 @@ import androidx.health.connect.client.records.ExerciseSessionRecord
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.TotalCaloriesBurnedRecord
import androidx.health.connect.client.units.Energy
import com.atridad.openclimb.data.model.ClimbSession
import com.atridad.openclimb.data.model.SessionStatus
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.ascently.data.model.ClimbSession
import com.atridad.ascently.data.model.SessionStatus
import com.atridad.ascently.utils.DateFormatUtils
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flow
/**
* Health Connect manager for OpenClimb that syncs climbing sessions to Samsung Health, Google Fit,
* Health Connect manager for Ascently that syncs climbing sessions to Samsung Health, Google Fit,
* and other health apps.
*/
@SuppressLint("RestrictedApi")
@@ -192,7 +192,7 @@ class HealthConnectManager(private val context: Context) {
} else {
results.add("❌ Health Connect not ready")
if (!available) results.add("- Health Connect not available on device")
if (!_isEnabled.value) results.add("- Not enabled in OpenClimb settings")
if (!_isEnabled.value) results.add("- Not enabled in Ascently settings")
if (!hasPerms) results.add("- Permissions not granted")
if (!_isCompatible.value) results.add("- API compatibility issues")
}

View File

@@ -1,10 +1,10 @@
package com.atridad.openclimb.data.model
package com.atridad.ascently.data.model
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.ascently.utils.DateFormatUtils
import kotlinx.serialization.Serializable
@Serializable

View File

@@ -1,10 +1,10 @@
package com.atridad.openclimb.data.model
package com.atridad.ascently.data.model
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.ascently.utils.DateFormatUtils
import kotlinx.serialization.Serializable
@Serializable

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.data.model
package com.atridad.ascently.data.model
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.data.model
package com.atridad.ascently.data.model
import kotlinx.serialization.Serializable

View File

@@ -1,8 +1,8 @@
package com.atridad.openclimb.data.model
package com.atridad.ascently.data.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.ascently.utils.DateFormatUtils
import kotlinx.serialization.Serializable
@Entity(tableName = "gyms")

View File

@@ -1,10 +1,10 @@
package com.atridad.openclimb.data.model
package com.atridad.ascently.data.model
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.ascently.utils.DateFormatUtils
import kotlinx.serialization.Serializable
@Entity(

View File

@@ -1,25 +1,25 @@
package com.atridad.openclimb.data.repository
package com.atridad.ascently.data.repository
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.atridad.openclimb.data.database.OpenClimbDatabase
import com.atridad.openclimb.data.format.BackupAttempt
import com.atridad.openclimb.data.format.BackupClimbSession
import com.atridad.openclimb.data.format.BackupGym
import com.atridad.openclimb.data.format.BackupProblem
import com.atridad.openclimb.data.format.ClimbDataBackup
import com.atridad.openclimb.data.format.DeletedItem
import com.atridad.openclimb.data.model.*
import com.atridad.openclimb.data.state.DataStateManager
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.openclimb.utils.ZipExportImportUtils
import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.format.BackupAttempt
import com.atridad.ascently.data.format.BackupClimbSession
import com.atridad.ascently.data.format.BackupGym
import com.atridad.ascently.data.format.BackupProblem
import com.atridad.ascently.data.format.ClimbDataBackup
import com.atridad.ascently.data.format.DeletedItem
import com.atridad.ascently.data.model.*
import com.atridad.ascently.data.state.DataStateManager
import com.atridad.ascently.utils.DateFormatUtils
import com.atridad.ascently.utils.ZipExportImportUtils
import java.io.File
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.serialization.json.Json
class ClimbRepository(database: OpenClimbDatabase, private val context: Context) {
class ClimbRepository(database: AscentlyDatabase, private val context: Context) {
private val gymDao = database.gymDao()
private val problemDao = database.problemDao()
private val sessionDao = database.climbSessionDao()
@@ -157,7 +157,7 @@ class ClimbRepository(database: OpenClimbDatabase, private val context: Context)
.filter { imagePath ->
try {
val imageFile =
com.atridad.openclimb.utils.ImageUtils.getImageFile(
com.atridad.ascently.utils.ImageUtils.getImageFile(
context,
imagePath
)

View File

@@ -1,10 +1,10 @@
package com.atridad.openclimb.data.state
package com.atridad.ascently.data.state
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import com.atridad.openclimb.utils.DateFormatUtils
import androidx.core.content.edit
import com.atridad.ascently.utils.DateFormatUtils
/**
* Manages the overall data state timestamp for sync purposes. This tracks when any data in the
@@ -14,7 +14,7 @@ class DataStateManager(context: Context) {
companion object {
private const val TAG = "DataStateManager"
private const val PREFS_NAME = "openclimb_data_state"
private const val PREFS_NAME = "ascently_data_state"
private const val KEY_LAST_MODIFIED = "last_modified_timestamp"
private const val KEY_INITIALIZED = "state_initialized"
}
@@ -58,5 +58,4 @@ class DataStateManager(context: Context) {
private fun markAsInitialized() {
prefs.edit { putBoolean(KEY_INITIALIZED, true) }
}
}

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.data.sync
package com.atridad.ascently.data.sync
import android.content.Context
import android.content.SharedPreferences
@@ -7,16 +7,16 @@ import android.net.NetworkCapabilities
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.core.content.edit
import com.atridad.openclimb.data.format.BackupAttempt
import com.atridad.openclimb.data.format.BackupClimbSession
import com.atridad.openclimb.data.format.BackupGym
import com.atridad.openclimb.data.format.BackupProblem
import com.atridad.openclimb.data.format.ClimbDataBackup
import com.atridad.openclimb.data.repository.ClimbRepository
import com.atridad.openclimb.data.state.DataStateManager
import com.atridad.openclimb.utils.DateFormatUtils
import com.atridad.openclimb.utils.ImageNamingUtils
import com.atridad.openclimb.utils.ImageUtils
import com.atridad.ascently.data.format.BackupAttempt
import com.atridad.ascently.data.format.BackupClimbSession
import com.atridad.ascently.data.format.BackupGym
import com.atridad.ascently.data.format.BackupProblem
import com.atridad.ascently.data.format.ClimbDataBackup
import com.atridad.ascently.data.repository.ClimbRepository
import com.atridad.ascently.data.state.DataStateManager
import com.atridad.ascently.utils.DateFormatUtils
import com.atridad.ascently.utils.ImageNamingUtils
import com.atridad.ascently.utils.ImageUtils
import java.io.IOException
import java.io.Serializable
import java.util.concurrent.TimeUnit

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.navigation
package com.atridad.ascently.navigation
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.navigation
package com.atridad.ascently.navigation
import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.service
package com.atridad.ascently.service
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -8,10 +8,10 @@ import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.atridad.openclimb.MainActivity
import com.atridad.openclimb.R
import com.atridad.openclimb.data.database.OpenClimbDatabase
import com.atridad.openclimb.data.repository.ClimbRepository
import com.atridad.ascently.MainActivity
import com.atridad.ascently.R
import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.repository.ClimbRepository
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import java.time.LocalDateTime
@@ -52,7 +52,7 @@ class SessionTrackingService : Service() {
override fun onCreate() {
super.onCreate()
val database = OpenClimbDatabase.getDatabase(this)
val database = AscentlyDatabase.getDatabase(this)
repository = ClimbRepository(database, this)
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
@@ -75,8 +75,8 @@ class SessionTrackingService : Service() {
sessionId != null -> repository.getSessionById(sessionId)
else -> repository.getActiveSession()
}
if (targetSession != null && targetSession.status == com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
val completed = with(com.atridad.openclimb.data.model.ClimbSession) { targetSession.complete() }
if (targetSession != null && targetSession.status == com.atridad.ascently.data.model.SessionStatus.ACTIVE) {
val completed = with(com.atridad.ascently.data.model.ClimbSession) { targetSession.complete() }
repository.updateSession(completed)
}
} finally {
@@ -127,7 +127,7 @@ class SessionTrackingService : Service() {
}
val session = repository.getSessionById(sessionId)
if (session == null || session.status != com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
if (session == null || session.status != com.atridad.ascently.data.model.SessionStatus.ACTIVE) {
stopSessionTracking()
break
}
@@ -175,7 +175,7 @@ class SessionTrackingService : Service() {
val session = runBlocking {
repository.getSessionById(sessionId)
}
if (session == null || session.status != com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
if (session == null || session.status != com.atridad.ascently.data.model.SessionStatus.ACTIVE) {
stopSessionTracking()
return
}

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui
package com.atridad.ascently.ui
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -17,21 +17,21 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.toRoute
import com.atridad.openclimb.data.database.OpenClimbDatabase
import com.atridad.openclimb.data.repository.ClimbRepository
import com.atridad.openclimb.data.sync.SyncService
import com.atridad.openclimb.navigation.Screen
import com.atridad.openclimb.navigation.bottomNavigationItems
import com.atridad.openclimb.ui.components.NotificationPermissionDialog
import com.atridad.openclimb.ui.screens.*
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.openclimb.ui.viewmodel.ClimbViewModelFactory
import com.atridad.openclimb.utils.AppShortcutManager
import com.atridad.openclimb.utils.NotificationPermissionUtils
import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.repository.ClimbRepository
import com.atridad.ascently.data.sync.SyncService
import com.atridad.ascently.navigation.Screen
import com.atridad.ascently.navigation.bottomNavigationItems
import com.atridad.ascently.ui.components.NotificationPermissionDialog
import com.atridad.ascently.ui.screens.*
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.ui.viewmodel.ClimbViewModelFactory
import com.atridad.ascently.utils.AppShortcutManager
import com.atridad.ascently.utils.NotificationPermissionUtils
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OpenClimbApp(
fun AscentlyApp(
shortcutAction: String? = null,
lastUsedGymId: String? = null,
onShortcutActionProcessed: () -> Unit = {}
@@ -39,9 +39,9 @@ fun OpenClimbApp(
val navController = rememberNavController()
val context = LocalContext.current
var lastUsedGym by remember { mutableStateOf<com.atridad.openclimb.data.model.Gym?>(null) }
var lastUsedGym by remember { mutableStateOf<com.atridad.ascently.data.model.Gym?>(null) }
val database = remember { OpenClimbDatabase.getDatabase(context) }
val database = remember { AscentlyDatabase.getDatabase(context) }
val repository = remember { ClimbRepository(database, context) }
val syncService = remember { SyncService(context, repository) }
val viewModel: ClimbViewModel =
@@ -115,7 +115,7 @@ fun OpenClimbApp(
LaunchedEffect(shortcutAction, activeSession, gyms, lastUsedGym) {
if (shortcutAction == AppShortcutManager.ACTION_START_SESSION && gyms.isNotEmpty()) {
android.util.Log.d(
"OpenClimbApp",
"AscentlyApp",
"Processing shortcut action: activeSession=$activeSession, gyms.size=${gyms.size}, lastUsedGymId=$lastUsedGymId, lastUsedGym=${lastUsedGym?.name}"
)
@@ -125,12 +125,12 @@ fun OpenClimbApp(
context
)
) {
android.util.Log.d("OpenClimbApp", "Showing notification permission dialog")
android.util.Log.d("AscentlyApp", "Showing notification permission dialog")
showNotificationPermissionDialog = true
} else {
if (gyms.size == 1) {
android.util.Log.d(
"OpenClimbApp",
"AscentlyApp",
"Starting session with single gym: ${gyms.first().name}"
)
viewModel.startSession(context, gyms.first().id)
@@ -141,13 +141,13 @@ fun OpenClimbApp(
if (targetGym != null) {
android.util.Log.d(
"OpenClimbApp",
"AscentlyApp",
"Starting session with target gym: ${targetGym.name}"
)
viewModel.startSession(context, targetGym.id)
} else {
android.util.Log.d(
"OpenClimbApp",
"AscentlyApp",
"No target gym found, navigating to selection"
)
navController.navigate(Screen.AddEditSession())
@@ -156,7 +156,7 @@ fun OpenClimbApp(
}
} else {
android.util.Log.d(
"OpenClimbApp",
"AscentlyApp",
"Active session already exists: ${activeSession?.id}"
)
}
@@ -168,7 +168,7 @@ fun OpenClimbApp(
var fabConfig by remember { mutableStateOf<FabConfig?>(null) }
Scaffold(
bottomBar = { OpenClimbBottomNavigation(navController = navController) },
bottomBar = { AscentlyBottomNavigation(navController = navController) },
floatingActionButton = {
fabConfig?.let { config ->
FloatingActionButton(
@@ -363,7 +363,7 @@ fun OpenClimbApp(
}
@Composable
fun OpenClimbBottomNavigation(navController: NavHostController) {
fun AscentlyBottomNavigation(navController: NavHostController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -10,9 +10,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.data.model.ClimbSession
import com.atridad.openclimb.data.model.Gym
import com.atridad.openclimb.ui.theme.CustomIcons
import com.atridad.ascently.data.model.ClimbSession
import com.atridad.ascently.data.model.Gym
import com.atridad.ascently.ui.theme.CustomIcons
import kotlinx.coroutines.delay
import java.time.LocalDateTime
import java.time.temporal.ChronoUnit

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import android.Manifest
import android.content.pm.PackageManager
@@ -25,7 +25,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import com.atridad.openclimb.utils.ImageUtils
import com.atridad.ascently.utils.ImageUtils
import java.io.File
import java.text.SimpleDateFormat
import java.util.*

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Box

View File

@@ -0,0 +1,76 @@
package com.atridad.ascently.ui.components
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@Composable
fun NotificationPermissionDialog(onDismiss: () -> Unit, onRequestPermission: () -> Unit) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Notifications,
contentDescription = "Notifications",
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Enable Notifications",
style = MaterialTheme.typography.headlineSmall,
fontWeight = MaterialTheme.typography.headlineSmall.fontWeight,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text =
"Ascently needs notification permission to show your active climbing session. This helps you track your progress and ensures the session doesn't get interrupted.",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
TextButton(onClick = onDismiss, modifier = Modifier.weight(1f)) {
Text("Not Now")
}
Button(
onClick = {
onRequestPermission()
onDismiss()
},
modifier = Modifier.weight(1f)
) { Text("Enable") }
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import android.graphics.BitmapFactory
import android.graphics.Matrix
@@ -12,7 +12,7 @@ import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.exifinterface.media.ExifInterface
import com.atridad.openclimb.utils.ImageUtils
import com.atridad.ascently.utils.ImageUtils
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.components
package com.atridad.ascently.ui.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.health
package com.atridad.ascently.ui.health
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.*
@@ -13,7 +13,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.data.health.HealthConnectManager
import com.atridad.ascently.data.health.HealthConnectManager
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -17,9 +17,9 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.data.model.*
import com.atridad.openclimb.ui.components.ImagePicker
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.data.model.*
import com.atridad.ascently.ui.components.ImagePicker
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
import java.time.LocalDateTime
import kotlinx.coroutines.flow.first

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -9,14 +9,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.R
import com.atridad.openclimb.data.model.AttemptResult
import com.atridad.openclimb.data.model.ClimbType
import com.atridad.openclimb.data.model.DifficultySystem
import com.atridad.openclimb.ui.components.BarChart
import com.atridad.openclimb.ui.components.BarChartDataPoint
import com.atridad.openclimb.ui.components.SyncIndicator
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.R
import com.atridad.ascently.data.model.AttemptResult
import com.atridad.ascently.data.model.ClimbType
import com.atridad.ascently.data.model.DifficultySystem
import com.atridad.ascently.ui.components.BarChart
import com.atridad.ascently.ui.components.BarChartDataPoint
import com.atridad.ascently.ui.components.SyncIndicator
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -39,7 +39,7 @@ fun AnalyticsScreen(viewModel: ClimbViewModel) {
) {
Icon(
painter = painterResource(id = R.drawable.ic_mountains),
contentDescription = "OpenClimb Logo",
contentDescription = "Ascently Logo",
modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary
)
@@ -200,7 +200,9 @@ fun GradeDistributionChartCard(gradeDistributionData: List<GradeDistributionData
},
modifier =
Modifier.menuAnchor(
type = ExposedDropdownMenuAnchorType.PrimaryNotEditable,
type =
ExposedDropdownMenuAnchorType
.PrimaryNotEditable,
enabled = true
)
.width(120.dp),
@@ -394,9 +396,9 @@ data class GradeDistributionDataPoint(
)
fun calculateGradeDistribution(
sessions: List<com.atridad.openclimb.data.model.ClimbSession>,
problems: List<com.atridad.openclimb.data.model.Problem>,
attempts: List<com.atridad.openclimb.data.model.Attempt>
sessions: List<com.atridad.ascently.data.model.ClimbSession>,
problems: List<com.atridad.ascently.data.model.Problem>,
attempts: List<com.atridad.ascently.data.model.Attempt>
): List<GradeDistributionDataPoint> {
if (sessions.isEmpty() || problems.isEmpty() || attempts.isEmpty()) {
return emptyList()

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
@@ -31,11 +31,11 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewModelScope
import com.atridad.openclimb.data.model.*
import com.atridad.openclimb.ui.components.FullscreenImageViewer
import com.atridad.openclimb.ui.components.ImageDisplaySection
import com.atridad.openclimb.ui.theme.CustomIcons
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.data.model.*
import com.atridad.ascently.ui.components.FullscreenImageViewer
import com.atridad.ascently.ui.components.ImageDisplaySection
import com.atridad.ascently.ui.theme.CustomIcons
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.flow.first

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -10,10 +10,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.R
import com.atridad.openclimb.data.model.Gym
import com.atridad.openclimb.ui.components.SyncIndicator
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.R
import com.atridad.ascently.data.model.Gym
import com.atridad.ascently.ui.components.SyncIndicator
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -28,7 +28,7 @@ fun GymsScreen(viewModel: ClimbViewModel, onNavigateToGymDetail: (String) -> Uni
) {
Icon(
painter = painterResource(id = R.drawable.ic_mountains),
contentDescription = "OpenClimb Logo",
contentDescription = "Ascently Logo",
modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary
)

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -15,14 +15,14 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.R
import com.atridad.openclimb.data.model.Attempt
import com.atridad.openclimb.data.model.AttemptResult
import com.atridad.openclimb.data.model.ClimbType
import com.atridad.openclimb.data.model.Gym
import com.atridad.openclimb.data.model.Problem
import com.atridad.openclimb.ui.components.SyncIndicator
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.R
import com.atridad.ascently.data.model.Attempt
import com.atridad.ascently.data.model.AttemptResult
import com.atridad.ascently.data.model.ClimbType
import com.atridad.ascently.data.model.Gym
import com.atridad.ascently.data.model.Problem
import com.atridad.ascently.ui.components.SyncIndicator
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -57,7 +57,7 @@ fun ProblemsScreen(viewModel: ClimbViewModel, onNavigateToProblemDetail: (String
) {
Icon(
painter = painterResource(id = R.drawable.ic_mountains),
contentDescription = "OpenClimb Logo",
contentDescription = "Ascently Logo",
modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary
)

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -16,12 +16,12 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.R
import com.atridad.openclimb.data.model.ClimbSession
import com.atridad.openclimb.data.model.SessionStatus
import com.atridad.openclimb.ui.components.ActiveSessionBanner
import com.atridad.openclimb.ui.components.SyncIndicator
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.R
import com.atridad.ascently.data.model.ClimbSession
import com.atridad.ascently.data.model.SessionStatus
import com.atridad.ascently.ui.components.ActiveSessionBanner
import com.atridad.ascently.ui.components.SyncIndicator
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
@@ -46,7 +46,7 @@ fun SessionsScreen(viewModel: ClimbViewModel, onNavigateToSessionDetail: (String
) {
Icon(
painter = painterResource(id = R.drawable.ic_mountains),
contentDescription = "OpenClimb Logo",
contentDescription = "Ascently Logo",
modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary
)

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.screens
package com.atridad.ascently.ui.screens
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -15,10 +15,10 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.atridad.openclimb.R
import com.atridad.openclimb.ui.components.SyncIndicator
import com.atridad.openclimb.ui.health.HealthConnectCard
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
import com.atridad.ascently.R
import com.atridad.ascently.ui.components.SyncIndicator
import com.atridad.ascently.ui.health.HealthConnectCard
import com.atridad.ascently.ui.viewmodel.ClimbViewModel
import java.io.File
import java.time.Instant
import kotlinx.coroutines.launch
@@ -86,7 +86,7 @@ fun SettingsScreen(viewModel: ClimbViewModel) {
// Only allow ZIP files
if (!fileName.lowercase().endsWith(".zip")) {
viewModel.setError(
"Only ZIP files are supported for import. Please use a ZIP file exported from OpenClimb."
"Only ZIP files are supported for import. Please use a ZIP file exported from Ascently."
)
return@let
}
@@ -129,7 +129,7 @@ fun SettingsScreen(viewModel: ClimbViewModel) {
) {
Icon(
painter = painterResource(id = R.drawable.ic_mountains),
contentDescription = "OpenClimb Logo",
contentDescription = "Ascently Logo",
modifier = Modifier.size(32.dp),
tint = MaterialTheme.colorScheme.primary
)
@@ -336,7 +336,7 @@ fun SettingsScreen(viewModel: ClimbViewModel) {
ListItem(
headlineContent = { Text("Setup Sync") },
supportingContent = {
Text("Connect to your OpenClimb sync server")
Text("Connect to your Ascently sync server")
},
leadingContent = {
Icon(Icons.Default.CloudSync, contentDescription = null)
@@ -421,7 +421,7 @@ fun SettingsScreen(viewModel: ClimbViewModel) {
TextButton(
onClick = {
val defaultFileName =
"openclimb_export_${
"ascently_export_${
java.time.LocalDateTime.now()
.toString()
.replace(":", "-")
@@ -604,11 +604,11 @@ fun SettingsScreen(viewModel: ClimbViewModel) {
painterResource(
id = R.drawable.ic_mountains
),
contentDescription = "OpenClimb Logo",
contentDescription = "Ascently Logo",
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.primary
)
Text("OpenClimb")
Text("Ascently")
}
},
supportingContent = { Text("Track your climbing progress") },

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.theme
package com.atridad.ascently.ui.theme
import androidx.compose.ui.graphics.Color

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.theme
package com.atridad.ascently.ui.theme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.theme
package com.atridad.ascently.ui.theme
import android.app.Activity
import androidx.compose.foundation.isSystemInDarkTheme
@@ -88,7 +88,7 @@ private val LightColorScheme = lightColorScheme(
)
@Composable
fun OpenClimbTheme(
fun AscentlyTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+ and provides full Material You theming
// When enabled, it adapts to the user's system wallpaper colors

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.ui.theme
package com.atridad.ascently.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle

View File

@@ -1,17 +1,17 @@
package com.atridad.openclimb.ui.viewmodel
package com.atridad.ascently.ui.viewmodel
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.atridad.openclimb.data.health.HealthConnectManager
import com.atridad.openclimb.data.model.*
import com.atridad.openclimb.data.repository.ClimbRepository
import com.atridad.openclimb.data.sync.SyncService
import com.atridad.openclimb.service.SessionTrackingService
import com.atridad.openclimb.utils.ImageNamingUtils
import com.atridad.openclimb.utils.ImageUtils
import com.atridad.openclimb.utils.SessionShareUtils
import com.atridad.openclimb.widget.ClimbStatsWidgetProvider
import com.atridad.ascently.data.health.HealthConnectManager
import com.atridad.ascently.data.model.*
import com.atridad.ascently.data.repository.ClimbRepository
import com.atridad.ascently.data.sync.SyncService
import com.atridad.ascently.service.SessionTrackingService
import com.atridad.ascently.utils.ImageNamingUtils
import com.atridad.ascently.utils.ImageUtils
import com.atridad.ascently.utils.SessionShareUtils
import com.atridad.ascently.widget.ClimbStatsWidgetProvider
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
@@ -260,7 +260,7 @@ class ClimbViewModel(
viewModelScope.launch {
android.util.Log.d("ClimbViewModel", "startSession called with gymId: $gymId")
if (!com.atridad.openclimb.utils.NotificationPermissionUtils
if (!com.atridad.ascently.utils.NotificationPermissionUtils
.isNotificationPermissionGranted(context)
) {
android.util.Log.d("ClimbViewModel", "Notification permission not granted")
@@ -305,7 +305,7 @@ class ClimbViewModel(
fun endSession(context: Context, sessionId: String) {
viewModelScope.launch {
if (!com.atridad.openclimb.utils.NotificationPermissionUtils
if (!com.atridad.ascently.utils.NotificationPermissionUtils
.isNotificationPermissionGranted(context)
) {
_uiState.value =
@@ -416,7 +416,7 @@ class ClimbViewModel(
if (!file.name.lowercase().endsWith(".zip")) {
throw Exception(
"Only ZIP files are supported for import. Please use a ZIP file exported from OpenClimb."
"Only ZIP files are supported for import. Please use a ZIP file exported from Ascently."
)
}

View File

@@ -1,10 +1,10 @@
package com.atridad.openclimb.ui.viewmodel
package com.atridad.ascently.ui.viewmodel
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.atridad.openclimb.data.repository.ClimbRepository
import com.atridad.openclimb.data.sync.SyncService
import com.atridad.ascently.data.repository.ClimbRepository
import com.atridad.ascently.data.sync.SyncService
class ClimbViewModelFactory(
private val repository: ClimbRepository,

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import java.time.Instant
import java.time.ZoneOffset

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import java.security.MessageDigest
import java.util.*

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import android.annotation.SuppressLint
import android.content.Context

View File

@@ -0,0 +1,175 @@
package com.atridad.ascently.utils
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.core.content.edit
/**
* Handles migration of data from OpenClimb to Ascently This includes SharedPreferences, database
* names, and other local storage
*/
class MigrationManager(private val context: Context) {
companion object {
private const val TAG = "MigrationManager"
private const val MIGRATION_PREFS = "ascently_migration_state"
private const val MIGRATION_COMPLETED_KEY = "openclimb_to_ascently_completed"
}
private val migrationPrefs: SharedPreferences =
context.getSharedPreferences(MIGRATION_PREFS, Context.MODE_PRIVATE)
/**
* Perform migration from OpenClimb to Ascently if needed This should be called early in app
* startup
*/
fun migrateIfNeeded() {
if (migrationPrefs.getBoolean(MIGRATION_COMPLETED_KEY, false)) {
Log.d(TAG, "Migration already completed, skipping")
return
}
Log.i(TAG, "🔄 Starting migration from OpenClimb to Ascently...")
var migrationCount = 0
// Migrate SharedPreferences
migrationCount += migrateSharedPreferences()
// Mark migration as completed
migrationPrefs.edit { putBoolean(MIGRATION_COMPLETED_KEY, true) }
if (migrationCount > 0) {
Log.i(
TAG,
"🎉 Migration completed! Migrated $migrationCount items from OpenClimb to Ascently"
)
} else {
Log.i(TAG, " No OpenClimb data found to migrate")
}
}
/** Migrate SharedPreferences from OpenClimb naming to Ascently naming */
private fun migrateSharedPreferences(): Int {
var count = 0
// Define preference file migrations
val preferenceFileMigrations =
listOf(
"openclimb_data_state" to "ascently_data_state",
"health_connect_prefs" to "health_connect_prefs", // Keep same name
"deleted_items" to "deleted_items", // Keep same name
"sync_preferences" to "sync_preferences" // Keep same name
)
for ((oldFileName, newFileName) in preferenceFileMigrations) {
if (oldFileName != newFileName) {
count += migratePreferenceFile(oldFileName, newFileName)
}
}
// Migrate specific keys within preference files
count += migratePreferenceKeys()
return count
}
/** Migrate an entire SharedPreferences file */
private fun migratePreferenceFile(oldFileName: String, newFileName: String): Int {
val oldPrefs = context.getSharedPreferences(oldFileName, Context.MODE_PRIVATE)
val newPrefs = context.getSharedPreferences(newFileName, Context.MODE_PRIVATE)
// If old prefs exist and new prefs are empty, migrate
if (oldPrefs.all.isNotEmpty() && newPrefs.all.isEmpty()) {
newPrefs.edit {
oldPrefs.all.forEach { (key, value) ->
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Float -> putFloat(key, value)
is Boolean -> putBoolean(key, value)
is Set<*> -> {
@Suppress("UNCHECKED_CAST") putStringSet(key, value as Set<String>)
}
}
}
}
// Clear old preferences
oldPrefs.edit { clear() }
Log.d(
TAG,
"✅ Migrated preference file: $oldFileName$newFileName (${oldPrefs.all.size} keys)"
)
return oldPrefs.all.size
}
return 0
}
/** Migrate specific keys within preference files that might contain openclimb references */
private fun migratePreferenceKeys(): Int {
var count = 0
// Check for any openclimb-prefixed keys across all preference files
val preferencesToCheck =
listOf(
"ascently_data_state",
"health_connect_prefs",
"deleted_items",
"sync_preferences"
)
for (prefFileName in preferencesToCheck) {
val prefs = context.getSharedPreferences(prefFileName, Context.MODE_PRIVATE)
val keysToMigrate = mutableListOf<Pair<String, String>>()
// Find keys that start with openclimb_ and should be ascently_
prefs.all.keys.forEach { key ->
if (key.startsWith("openclimb_")) {
val newKey = key.replace("openclimb_", "ascently_")
keysToMigrate.add(key to newKey)
}
}
// Perform the key migrations
if (keysToMigrate.isNotEmpty()) {
prefs.edit {
keysToMigrate.forEach { (oldKey, newKey) ->
val value = prefs.all[oldKey]
when (value) {
is String -> putString(newKey, value)
is Int -> putInt(newKey, value)
is Long -> putLong(newKey, value)
is Float -> putFloat(newKey, value)
is Boolean -> putBoolean(newKey, value)
is Set<*> -> {
@Suppress("UNCHECKED_CAST")
putStringSet(newKey, value as Set<String>)
}
}
remove(oldKey)
}
}
Log.d(TAG, "✅ Migrated ${keysToMigrate.size} keys in $prefFileName")
count += keysToMigrate.size
}
}
return count
}
/** Check if migration has been completed */
fun isMigrationCompleted(): Boolean {
return migrationPrefs.getBoolean(MIGRATION_COMPLETED_KEY, false)
}
/** Reset migration state (for testing purposes) */
fun resetMigrationState() {
migrationPrefs.edit { putBoolean(MIGRATION_COMPLETED_KEY, false) }
Log.d(TAG, "Migration state reset")
}
}

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import android.Manifest
import android.content.Context

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import android.content.Context
import android.content.Intent
@@ -7,7 +7,7 @@ import android.graphics.drawable.GradientDrawable
import androidx.core.content.FileProvider
import androidx.core.graphics.createBitmap
import androidx.core.graphics.toColorInt
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.model.*
import java.io.File
import java.io.FileOutputStream
import java.time.LocalDateTime
@@ -382,7 +382,7 @@ object SessionShareUtils {
isAntiAlias = true
textAlign = Paint.Align.CENTER
}
canvas.drawText("OpenClimb", width / 2f, height - 40f, brandingPaint)
canvas.drawText("Ascently", width / 2f, height - 40f, brandingPaint)
// Save to file
val shareDir = File(context.cacheDir, "shares")
@@ -481,7 +481,7 @@ object SessionShareUtils {
action = Intent.ACTION_SEND
type = "image/png"
putExtra(Intent.EXTRA_STREAM, uri)
putExtra(Intent.EXTRA_TEXT, "Check out my climbing session! #OpenClimb")
putExtra(Intent.EXTRA_TEXT, "Check out my climbing session! #Ascently")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import android.content.Context
import android.content.Intent
@@ -7,23 +7,23 @@ import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import com.atridad.openclimb.MainActivity
import com.atridad.openclimb.R
import com.atridad.ascently.MainActivity
import com.atridad.ascently.R
object AppShortcutManager {
const val SHORTCUT_START_SESSION = "start_session"
const val SHORTCUT_END_SESSION = "end_session"
const val ACTION_START_SESSION = "com.atridad.openclimb.action.START_SESSION"
const val ACTION_END_SESSION = "com.atridad.openclimb.action.END_SESSION"
const val ACTION_START_SESSION = "com.atridad.ascently.action.START_SESSION"
const val ACTION_END_SESSION = "com.atridad.ascently.action.END_SESSION"
/** Updates the app shortcuts based on current session state */
fun updateShortcuts(
context: Context,
hasActiveSession: Boolean,
hasGyms: Boolean,
lastUsedGym: com.atridad.openclimb.data.model.Gym? = null
lastUsedGym: com.atridad.ascently.data.model.Gym? = null
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
val shortcutManager = context.getSystemService(ShortcutManager::class.java)
@@ -45,7 +45,7 @@ object AppShortcutManager {
@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun createStartSessionShortcut(
context: Context,
lastUsedGym: com.atridad.openclimb.data.model.Gym? = null
lastUsedGym: com.atridad.ascently.data.model.Gym? = null
): ShortcutInfo {
val startIntent =
Intent(context, MainActivity::class.java).apply {

View File

@@ -1,8 +1,8 @@
package com.atridad.openclimb.utils
package com.atridad.ascently.utils
import android.content.Context
import com.atridad.openclimb.data.format.BackupProblem
import com.atridad.openclimb.data.format.ClimbDataBackup
import com.atridad.ascently.data.format.BackupProblem
import com.atridad.ascently.data.format.ClimbDataBackup
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
@@ -33,14 +33,14 @@ object ZipExportImportUtils {
context.getExternalFilesDir(
android.os.Environment.DIRECTORY_DOCUMENTS
),
"OpenClimb"
"Ascently"
)
if (!exportDir.exists()) {
exportDir.mkdirs()
}
val timestamp = LocalDateTime.now().toString().replace(":", "-").replace(".", "-")
val zipFile = File(exportDir, "openclimb_export_$timestamp.zip")
val zipFile = File(exportDir, "ascently_export_$timestamp.zip")
try {
ZipOutputStream(FileOutputStream(zipFile)).use { zipOut ->
@@ -182,8 +182,8 @@ object ZipExportImportUtils {
referencedImagePaths: Set<String>
): String {
return buildString {
appendLine("OpenClimb Export Metadata")
appendLine("=======================")
appendLine("Ascently Export Metadata")
appendLine("========================")
appendLine("Export Date: ${exportData.exportedAt}")
appendLine("Version: ${exportData.version}")
appendLine("Gyms: ${exportData.gyms.size}")

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb.widget
package com.atridad.ascently.widget
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
@@ -7,10 +7,10 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import com.atridad.openclimb.MainActivity
import com.atridad.openclimb.R
import com.atridad.openclimb.data.database.OpenClimbDatabase
import com.atridad.openclimb.data.repository.ClimbRepository
import com.atridad.ascently.MainActivity
import com.atridad.ascently.R
import com.atridad.ascently.data.database.AscentlyDatabase
import com.atridad.ascently.data.repository.ClimbRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@@ -45,7 +45,7 @@ class ClimbStatsWidgetProvider : AppWidgetProvider() {
) {
coroutineScope.launch {
try {
val database = OpenClimbDatabase.getDatabase(context)
val database = AscentlyDatabase.getDatabase(context)
val repository = ClimbRepository(database, context)
// Fetch stats data
@@ -65,10 +65,10 @@ class ClimbStatsWidgetProvider : AppWidgetProvider() {
attempts.any { attempt ->
attempt.problemId == problem.id &&
(attempt.result ==
com.atridad.openclimb.data.model
com.atridad.ascently.data.model
.AttemptResult.SUCCESS ||
attempt.result ==
com.atridad.openclimb.data.model
com.atridad.ascently.data.model
.AttemptResult.FLASH)
}
}

View File

@@ -1,89 +0,0 @@
package com.atridad.openclimb.ui.components
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@Composable
fun NotificationPermissionDialog(
onDismiss: () -> Unit,
onRequestPermission: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false
)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = MaterialTheme.shapes.medium
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Default.Notifications,
contentDescription = "Notifications",
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Enable Notifications",
style = MaterialTheme.typography.headlineSmall,
fontWeight = MaterialTheme.typography.headlineSmall.fontWeight,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = "OpenClimb needs notification permission to show your active climbing session. This helps you track your progress and ensures the session doesn't get interrupted.",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
TextButton(
onClick = onDismiss,
modifier = Modifier.weight(1f)
) {
Text("Not Now")
}
Button(
onClick = {
onRequestPermission()
onDismiss()
},
modifier = Modifier.weight(1f)
) {
Text("Enable")
}
}
}
}
}
}

View File

@@ -26,7 +26,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="OpenClimb"
android:text="Ascently"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/widget_text_primary" />

View File

@@ -1,5 +1,5 @@
<resources>
<string name="app_name">OpenClimb</string>
<string name="app_name">Ascently</string>
<string name="session_tracking_service_description">Tracks active climbing sessions and displays session information in the notification area</string>
<!-- App Shortcuts -->

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.OpenClimb" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.Ascently" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.OpenClimb.Splash" parent="Theme.OpenClimb">
<style name="Theme.Ascently.Splash" parent="Theme.Ascently">
<item name="android:windowSplashScreenBackground">@color/splash_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_mountains</item>
<item name="android:windowSplashScreenAnimationDuration">200</item>
</style>
</resources>
</resources>

View File

@@ -1,7 +1,7 @@
package com.atridad.openclimb
package com.atridad.ascently
import com.atridad.openclimb.data.format.*
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.format.*
import com.atridad.ascently.data.model.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import org.junit.Assert.*

View File

@@ -1,7 +1,7 @@
package com.atridad.openclimb
package com.atridad.ascently
import com.atridad.openclimb.data.format.*
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.format.*
import com.atridad.ascently.data.model.*
import java.time.Instant
import java.time.format.DateTimeFormatter
import org.junit.Assert.*

View File

@@ -1,7 +1,7 @@
package com.atridad.openclimb
package com.atridad.ascently
import com.atridad.openclimb.data.format.*
import com.atridad.openclimb.data.model.*
import com.atridad.ascently.data.format.*
import com.atridad.ascently.data.model.*
import org.junit.Assert.*
import org.junit.Test

View File

@@ -1,4 +1,4 @@
package com.atridad.openclimb
package com.atridad.ascently
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

View File

@@ -11,6 +11,7 @@ pluginManagement {
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@@ -20,5 +21,6 @@ dependencyResolutionManagement {
}
}
rootProject.name = "OpenClimb"
rootProject.name = "Ascently"
include(":app")

View File

@@ -20,7 +20,7 @@
containerPortal = D24C19602E75002A0045894C /* Project object */;
proxyType = 1;
remoteGlobalIDString = D24C19672E75002A0045894C;
remoteInfo = OpenClimb;
remoteInfo = Ascently;
};
D2FE949E2E78FEE1008CDB25 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
@@ -46,9 +46,9 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
D24C19682E75002A0045894C /* OpenClimb.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenClimb.app; sourceTree = BUILT_PRODUCTS_DIR; };
D24C19682E75002A0045894C /* Ascently.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ascently.app; sourceTree = BUILT_PRODUCTS_DIR; };
D268B79E2E83894A003AA641 /* SessionStatusLiveExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionStatusLiveExtension.entitlements; sourceTree = "<group>"; };
D2F32FAD2E90B26500B1BC56 /* OpenClimbTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenClimbTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D2F32FAD2E90B26500B1BC56 /* AscentlyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AscentlyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D2FE94802E78E958008CDB25 /* ActivityKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActivityKit.framework; path = System/Library/Frameworks/ActivityKit.framework; sourceTree = SDKROOT; };
D2FE948B2E78FEE0008CDB25 /* SessionStatusLiveExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionStatusLiveExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
D2FE948C2E78FEE0008CDB25 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
@@ -56,12 +56,12 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
D28C3C8B2E75111D00F7AEE9 /* Exceptions for "OpenClimb" folder in "OpenClimb" target */ = {
D28C3C8B2E75111D00F7AEE9 /* Exceptions for "Ascently" folder in "Ascently" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = D24C19672E75002A0045894C /* OpenClimb */;
target = D24C19672E75002A0045894C /* Ascently */;
};
D2FE94A42E78FEE1008CDB25 /* Exceptions for "SessionStatusLive" folder in "SessionStatusLiveExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
@@ -73,17 +73,17 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
D24C196A2E75002A0045894C /* OpenClimb */ = {
D24C196A2E75002A0045894C /* Ascently */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
D28C3C8B2E75111D00F7AEE9 /* Exceptions for "OpenClimb" folder in "OpenClimb" target */,
D28C3C8B2E75111D00F7AEE9 /* Exceptions for "Ascently" folder in "Ascently" target */,
);
path = OpenClimb;
path = Ascently;
sourceTree = "<group>";
};
D2F32FAE2E90B26500B1BC56 /* OpenClimbTests */ = {
D2F32FAE2E90B26500B1BC56 /* AscentlyTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = OpenClimbTests;
path = AscentlyTests;
sourceTree = "<group>";
};
D2FE94902E78FEE0008CDB25 /* SessionStatusLive */ = {
@@ -129,9 +129,9 @@
isa = PBXGroup;
children = (
D268B79E2E83894A003AA641 /* SessionStatusLiveExtension.entitlements */,
D24C196A2E75002A0045894C /* OpenClimb */,
D24C196A2E75002A0045894C /* Ascently */,
D2FE94902E78FEE0008CDB25 /* SessionStatusLive */,
D2F32FAE2E90B26500B1BC56 /* OpenClimbTests */,
D2F32FAE2E90B26500B1BC56 /* AscentlyTests */,
D2FE947F2E78E958008CDB25 /* Frameworks */,
D24C19692E75002A0045894C /* Products */,
);
@@ -140,9 +140,9 @@
D24C19692E75002A0045894C /* Products */ = {
isa = PBXGroup;
children = (
D24C19682E75002A0045894C /* OpenClimb.app */,
D24C19682E75002A0045894C /* Ascently.app */,
D2FE948B2E78FEE0008CDB25 /* SessionStatusLiveExtension.appex */,
D2F32FAD2E90B26500B1BC56 /* OpenClimbTests.xctest */,
D2F32FAD2E90B26500B1BC56 /* AscentlyTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@@ -160,9 +160,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
D24C19672E75002A0045894C /* OpenClimb */ = {
D24C19672E75002A0045894C /* Ascently */ = {
isa = PBXNativeTarget;
buildConfigurationList = D24C19732E75002A0045894C /* Build configuration list for PBXNativeTarget "OpenClimb" */;
buildConfigurationList = D24C19732E75002A0045894C /* Build configuration list for PBXNativeTarget "Ascently" */;
buildPhases = (
D24C19642E75002A0045894C /* Sources */,
D24C19652E75002A0045894C /* Frameworks */,
@@ -175,18 +175,18 @@
D2FE949F2E78FEE1008CDB25 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
D24C196A2E75002A0045894C /* OpenClimb */,
D24C196A2E75002A0045894C /* Ascently */,
);
name = OpenClimb;
name = Ascently;
packageProductDependencies = (
);
productName = OpenClimb;
productReference = D24C19682E75002A0045894C /* OpenClimb.app */;
productName = Ascently;
productReference = D24C19682E75002A0045894C /* Ascently.app */;
productType = "com.apple.product-type.application";
};
D2F32FAC2E90B26500B1BC56 /* OpenClimbTests */ = {
D2F32FAC2E90B26500B1BC56 /* AscentlyTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = D2F32FB52E90B26500B1BC56 /* Build configuration list for PBXNativeTarget "OpenClimbTests" */;
buildConfigurationList = D2F32FB52E90B26500B1BC56 /* Build configuration list for PBXNativeTarget "AscentlyTests" */;
buildPhases = (
D2F32FA92E90B26500B1BC56 /* Sources */,
D2F32FAA2E90B26500B1BC56 /* Frameworks */,
@@ -198,13 +198,13 @@
D2F32FB22E90B26500B1BC56 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
D2F32FAE2E90B26500B1BC56 /* OpenClimbTests */,
D2F32FAE2E90B26500B1BC56 /* AscentlyTests */,
);
name = OpenClimbTests;
name = AscentlyTests;
packageProductDependencies = (
);
productName = OpenClimbTests;
productReference = D2F32FAD2E90B26500B1BC56 /* OpenClimbTests.xctest */;
productName = AscentlyTests;
productReference = D2F32FAD2E90B26500B1BC56 /* AscentlyTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
D2FE948A2E78FEE0008CDB25 /* SessionStatusLiveExtension */ = {
@@ -251,7 +251,7 @@
};
};
};
buildConfigurationList = D24C19632E75002A0045894C /* Build configuration list for PBXProject "OpenClimb" */;
buildConfigurationList = D24C19632E75002A0045894C /* Build configuration list for PBXProject "Ascently" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@@ -265,9 +265,9 @@
projectDirPath = "";
projectRoot = "";
targets = (
D24C19672E75002A0045894C /* OpenClimb */,
D24C19672E75002A0045894C /* Ascently */,
D2FE948A2E78FEE0008CDB25 /* SessionStatusLiveExtension */,
D2F32FAC2E90B26500B1BC56 /* OpenClimbTests */,
D2F32FAC2E90B26500B1BC56 /* AscentlyTests */,
);
};
/* End PBXProject section */
@@ -323,7 +323,7 @@
/* Begin PBXTargetDependency section */
D2F32FB22E90B26500B1BC56 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D24C19672E75002A0045894C /* OpenClimb */;
target = D24C19672E75002A0045894C /* Ascently */;
targetProxy = D2F32FB12E90B26500B1BC56 /* PBXContainerItemProxy */;
};
D2FE949F2E78FEE1008CDB25 /* PBXTargetDependency */ = {
@@ -462,20 +462,20 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = OpenClimb/OpenClimb.entitlements;
CODE_SIGN_ENTITLEMENTS = Ascently/Ascently.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
DRIVERKIT_DEPLOYMENT_TARGET = 24.6;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = OpenClimb/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = OpenClimb;
INFOPLIST_FILE = Ascently/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Ascently;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCameraUsageDescription = "OpenClimb needs camera access to take photos of climbing problems.";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "OpenClimb needs access to your photo library to save and display climbing problem images.";
INFOPLIST_KEY_NSCameraUsageDescription = "Ascently needs camera access to take photos of climbing problems.";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Ascently needs access to your photo library to save and display climbing problem images.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -487,8 +487,8 @@
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.Ascently;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -510,20 +510,20 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = OpenClimb/OpenClimb.entitlements;
CODE_SIGN_ENTITLEMENTS = Ascently/Ascently.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
DRIVERKIT_DEPLOYMENT_TARGET = 24.6;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = OpenClimb/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = OpenClimb;
INFOPLIST_FILE = Ascently/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Ascently;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSCameraUsageDescription = "OpenClimb needs camera access to take photos of climbing problems.";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "OpenClimb needs access to your photo library to save and display climbing problem images.";
INFOPLIST_KEY_NSCameraUsageDescription = "Ascently needs camera access to take photos of climbing problems.";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Ascently needs access to your photo library to save and display climbing problem images.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -535,8 +535,8 @@
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.6;
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.Ascently;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -562,7 +562,7 @@
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.OpenClimb.Watch.OpenClimbTests;
PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.OpenClimb.Watch.AscentlyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -570,7 +570,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OpenClimb.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenClimb";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ascently.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenClimb";
};
name = Debug;
};
@@ -583,7 +583,7 @@
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.OpenClimb.Watch.OpenClimbTests;
PRODUCT_BUNDLE_IDENTIFIER = com.atri.dad.OpenClimb.Watch.AscentlyTests;
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
@@ -591,7 +591,7 @@
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OpenClimb.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenClimb";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Ascently.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenClimb";
};
name = Release;
};
@@ -602,7 +602,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = SessionStatusLiveExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SessionStatusLive/Info.plist;
@@ -613,8 +613,8 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb.SessionStatusLive;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.Ascently.SessionStatusLive;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -632,7 +632,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = SessionStatusLiveExtension.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25;
CURRENT_PROJECT_VERSION = 26;
DEVELOPMENT_TEAM = 4BC9Y2LL4B;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = SessionStatusLive/Info.plist;
@@ -643,8 +643,8 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.4.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.OpenClimb.SessionStatusLive;
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.atridad.Ascently.SessionStatusLive;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -658,7 +658,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
D24C19632E75002A0045894C /* Build configuration list for PBXProject "OpenClimb" */ = {
D24C19632E75002A0045894C /* Build configuration list for PBXProject "Ascently" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D24C19712E75002A0045894C /* Debug */,
@@ -667,7 +667,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D24C19732E75002A0045894C /* Build configuration list for PBXNativeTarget "OpenClimb" */ = {
D24C19732E75002A0045894C /* Build configuration list for PBXNativeTarget "Ascently" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D24C19742E75002A0045894C /* Debug */,
@@ -676,7 +676,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D2F32FB52E90B26500B1BC56 /* Build configuration list for PBXNativeTarget "OpenClimbTests" */ = {
D2F32FB52E90B26500B1BC56 /* Build configuration list for PBXNativeTarget "AscentlyTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D2F32FB32E90B26500B1BC56 /* Debug */,

View File

@@ -15,9 +15,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D24C19672E75002A0045894C"
BuildableName = "OpenClimb.app"
BlueprintName = "OpenClimb"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "Ascently.app"
BlueprintName = "Ascently"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -34,9 +34,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2F32FAC2E90B26500B1BC56"
BuildableName = "OpenClimbTests.xctest"
BlueprintName = "OpenClimbTests"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "AscentlyTests.xctest"
BlueprintName = "AscentlyTests"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
@@ -56,9 +56,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D24C19672E75002A0045894C"
BuildableName = "OpenClimb.app"
BlueprintName = "OpenClimb"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "Ascently.app"
BlueprintName = "Ascently"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
@@ -73,9 +73,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D24C19672E75002A0045894C"
BuildableName = "OpenClimb.app"
BlueprintName = "OpenClimb"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "Ascently.app"
BlueprintName = "Ascently"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>

View File

@@ -19,7 +19,7 @@
BlueprintIdentifier = "D2FE948A2E78FEE0008CDB25"
BuildableName = "SessionStatusLiveExtension.appex"
BlueprintName = "SessionStatusLiveExtension"
ReferencedContainer = "container:OpenClimb.xcodeproj">
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
@@ -31,9 +31,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D24C19672E75002A0045894C"
BuildableName = "OpenClimb.app"
BlueprintName = "OpenClimb"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "Ascently.app"
BlueprintName = "Ascently"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -51,9 +51,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2F32FAC2E90B26500B1BC56"
BuildableName = "OpenClimbTests.xctest"
BlueprintName = "OpenClimbTests"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "AscentlyTests.xctest"
BlueprintName = "AscentlyTests"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
@@ -75,9 +75,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D24C19672E75002A0045894C"
BuildableName = "OpenClimb.app"
BlueprintName = "OpenClimb"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "Ascently.app"
BlueprintName = "Ascently"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
@@ -111,9 +111,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D24C19672E75002A0045894C"
BuildableName = "OpenClimb.app"
BlueprintName = "OpenClimb"
ReferencedContainer = "container:OpenClimb.xcodeproj">
BuildableName = "Ascently.app"
BlueprintName = "Ascently"
ReferencedContainer = "container:Ascently.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>

View File

@@ -4,7 +4,12 @@
<dict>
<key>SchemeUserState</key>
<dict>
<key>OpenClimb.xcscheme_^#shared#^_</key>
<key>AscentlyTests.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>Ascently.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>

View File

@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.atridad.OpenClimb</string>
<string>group.com.atridad.Ascently</string>
</array>
<key>com.apple.developer.healthkit</key>
<true/>

View File

@@ -1,8 +1,7 @@
import SwiftUI
@main
struct OpenClimbApp: App {
struct AscentlyApp: App {
var body: some Scene {
WindowGroup {
ContentView()

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -5,7 +5,7 @@ import Foundation
// MARK: - Backup Format Specification v2.0
/// Root structure for OpenClimb backup data
/// Root structure for Ascently backup data
struct DeletedItem: Codable, Hashable {
let id: String
let type: String // "gym", "problem", "session", "attempt"

Some files were not shown because too many files have changed in this diff Show More