Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
11850408be
|
|||
|
37beb4abb6
|
2
.gitattributes
vendored
@@ -75,5 +75,3 @@ pnpm-lock.yaml text -diff
|
||||
# Documentation
|
||||
LICENSE text eol=lf
|
||||
README.md text eol=lf
|
||||
|
||||
*.pxd linguist-vendored
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Ascently
|
||||
|
||||
<img src="https://git.atri.dad/atridad/Ascently/raw/branch/main/docs/src/assets/logo.png" alt="Ascently Logo" width="250" height="250">
|
||||
|
||||
_Formerly OpenClimb_
|
||||
|
||||
Ascently is an **offline-first FOSS** app designed to help climbers track their sessions, routes/problems, and overall progress. There is an optional self-hosted sync server and integrations with Apple Health and Health Connect. There are no analytics or tracking baked into any part of this project. I am committed to maintaining a transparent and open-source solution for climbers, ensuring that you have full control over your data and privacy.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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/ascently/`.
|
||||
|
||||
- `data/`: Handles all the app's data.
|
||||
|
||||
@@ -18,8 +18,8 @@ android {
|
||||
applicationId = "com.atridad.ascently"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 51
|
||||
versionName = "2.5.1"
|
||||
versionCode = 50
|
||||
versionName = "2.5.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 42 KiB |
@@ -13,6 +13,7 @@ data class ClimbDataBackup(
|
||||
val problems: List<BackupProblem>,
|
||||
val sessions: List<BackupClimbSession>,
|
||||
val attempts: List<BackupAttempt>,
|
||||
val deletedItems: List<DeletedItem> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@@ -33,7 +34,6 @@ data class BackupGym(
|
||||
@kotlinx.serialization.SerialName("customDifficultyGrades")
|
||||
val customDifficultyGrades: List<String>? = null,
|
||||
val notes: String? = null,
|
||||
val isDeleted: Boolean = false,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
) {
|
||||
@@ -47,26 +47,10 @@ data class BackupGym(
|
||||
difficultySystems = gym.difficultySystems,
|
||||
customDifficultyGrades = gym.customDifficultyGrades.ifEmpty { null },
|
||||
notes = gym.notes,
|
||||
isDeleted = false,
|
||||
createdAt = gym.createdAt,
|
||||
updatedAt = gym.updatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
fun createTombstone(id: String, deletedAt: String): BackupGym {
|
||||
return BackupGym(
|
||||
id = id,
|
||||
name = "DELETED",
|
||||
location = null,
|
||||
supportedClimbTypes = emptyList(),
|
||||
difficultySystems = emptyList(),
|
||||
customDifficultyGrades = null,
|
||||
notes = null,
|
||||
isDeleted = true,
|
||||
createdAt = deletedAt,
|
||||
updatedAt = deletedAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toGym(): Gym {
|
||||
@@ -99,7 +83,6 @@ data class BackupProblem(
|
||||
val isActive: Boolean = true,
|
||||
val dateSet: String? = null,
|
||||
val notes: String? = null,
|
||||
val isDeleted: Boolean = false,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
) {
|
||||
@@ -123,31 +106,10 @@ data class BackupProblem(
|
||||
isActive = problem.isActive,
|
||||
dateSet = problem.dateSet,
|
||||
notes = problem.notes,
|
||||
isDeleted = false,
|
||||
createdAt = problem.createdAt,
|
||||
updatedAt = problem.updatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
fun createTombstone(id: String, deletedAt: String): BackupProblem {
|
||||
return BackupProblem(
|
||||
id = id,
|
||||
gymId = "00000000-0000-0000-0000-000000000000",
|
||||
name = "DELETED",
|
||||
description = null,
|
||||
climbType = ClimbType.values().first(),
|
||||
difficulty = DifficultyGrade(DifficultySystem.values().first(), "0"),
|
||||
tags = null,
|
||||
location = null,
|
||||
imagePaths = null,
|
||||
isActive = false,
|
||||
dateSet = null,
|
||||
notes = null,
|
||||
isDeleted = true,
|
||||
createdAt = deletedAt,
|
||||
updatedAt = deletedAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toProblem(): Problem {
|
||||
@@ -185,7 +147,6 @@ data class BackupClimbSession(
|
||||
val duration: Long? = null,
|
||||
val status: SessionStatus,
|
||||
val notes: String? = null,
|
||||
val isDeleted: Boolean = false,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
) {
|
||||
@@ -200,27 +161,10 @@ data class BackupClimbSession(
|
||||
duration = session.duration,
|
||||
status = session.status,
|
||||
notes = session.notes,
|
||||
isDeleted = false,
|
||||
createdAt = session.createdAt,
|
||||
updatedAt = session.updatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
fun createTombstone(id: String, deletedAt: String): BackupClimbSession {
|
||||
return BackupClimbSession(
|
||||
id = id,
|
||||
gymId = "00000000-0000-0000-0000-000000000000",
|
||||
date = deletedAt,
|
||||
startTime = null,
|
||||
endTime = null,
|
||||
duration = null,
|
||||
status = SessionStatus.values().first(),
|
||||
notes = null,
|
||||
isDeleted = true,
|
||||
createdAt = deletedAt,
|
||||
updatedAt = deletedAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toClimbSession(): ClimbSession {
|
||||
@@ -251,7 +195,6 @@ data class BackupAttempt(
|
||||
val duration: Long? = null,
|
||||
val restTime: Long? = null,
|
||||
val timestamp: String,
|
||||
val isDeleted: Boolean = false,
|
||||
val createdAt: String,
|
||||
val updatedAt: String? = null,
|
||||
) {
|
||||
@@ -267,28 +210,10 @@ data class BackupAttempt(
|
||||
duration = attempt.duration,
|
||||
restTime = attempt.restTime,
|
||||
timestamp = attempt.timestamp,
|
||||
isDeleted = false,
|
||||
createdAt = attempt.createdAt,
|
||||
updatedAt = attempt.updatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
fun createTombstone(id: String, deletedAt: String): BackupAttempt {
|
||||
return BackupAttempt(
|
||||
id = id,
|
||||
sessionId = "00000000-0000-0000-0000-000000000000",
|
||||
problemId = "00000000-0000-0000-0000-000000000000",
|
||||
result = AttemptResult.values().first(),
|
||||
highestHold = null,
|
||||
notes = null,
|
||||
duration = null,
|
||||
restTime = null,
|
||||
timestamp = deletedAt,
|
||||
isDeleted = true,
|
||||
createdAt = deletedAt,
|
||||
updatedAt = deletedAt,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun toAttempt(): Attempt {
|
||||
|
||||
@@ -19,7 +19,6 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
|
||||
class ClimbRepository(database: AscentlyDatabase, private val context: Context) {
|
||||
private val gymDao = database.gymDao()
|
||||
@@ -39,7 +38,6 @@ class ClimbRepository(database: AscentlyDatabase, private val context: Context)
|
||||
|
||||
// Gym operations
|
||||
fun getAllGyms(): Flow<List<Gym>> = gymDao.getAllGyms()
|
||||
suspend fun getAllGymsSync(): List<Gym> = gymDao.getAllGyms().first()
|
||||
suspend fun getGymById(id: String): Gym? = gymDao.getGymById(id)
|
||||
suspend fun insertGym(gym: Gym) {
|
||||
gymDao.insertGym(gym)
|
||||
@@ -62,7 +60,6 @@ class ClimbRepository(database: AscentlyDatabase, private val context: Context)
|
||||
|
||||
// Problem operations
|
||||
fun getAllProblems(): Flow<List<Problem>> = problemDao.getAllProblems()
|
||||
suspend fun getAllProblemsSync(): List<Problem> = problemDao.getAllProblems().first()
|
||||
suspend fun getProblemById(id: String): Problem? = problemDao.getProblemById(id)
|
||||
fun getProblemsByGym(gymId: String): Flow<List<Problem>> = problemDao.getProblemsByGym(gymId)
|
||||
suspend fun insertProblem(problem: Problem) {
|
||||
@@ -83,7 +80,6 @@ class ClimbRepository(database: AscentlyDatabase, private val context: Context)
|
||||
|
||||
// Session operations
|
||||
fun getAllSessions(): Flow<List<ClimbSession>> = sessionDao.getAllSessions()
|
||||
suspend fun getAllSessionsSync(): List<ClimbSession> = sessionDao.getAllSessions().first()
|
||||
suspend fun getSessionById(id: String): ClimbSession? = sessionDao.getSessionById(id)
|
||||
fun getSessionsByGym(gymId: String): Flow<List<ClimbSession>> =
|
||||
sessionDao.getSessionsByGym(gymId)
|
||||
@@ -126,8 +122,6 @@ class ClimbRepository(database: AscentlyDatabase, private val context: Context)
|
||||
|
||||
// Attempt operations
|
||||
fun getAllAttempts(): Flow<List<Attempt>> = attemptDao.getAllAttempts()
|
||||
suspend fun getAllAttemptsSync(): List<Attempt> = attemptDao.getAllAttempts().first()
|
||||
suspend fun getAttemptById(id: String): Attempt? = attemptDao.getAttemptById(id)
|
||||
fun getAttemptsBySession(sessionId: String): Flow<List<Attempt>> =
|
||||
attemptDao.getAttemptsBySession(sessionId)
|
||||
|
||||
@@ -279,9 +273,10 @@ class ClimbRepository(database: AscentlyDatabase, private val context: Context)
|
||||
}
|
||||
|
||||
fun trackDeletion(itemId: String, itemType: String) {
|
||||
cleanupOldDeletions()
|
||||
val currentDeletions = getDeletedItems().toMutableList()
|
||||
val newDeletion =
|
||||
DeletedItem(id = itemId, type = itemType, deletedAt = DateFormatUtils.nowISO8601())
|
||||
currentDeletions.add(newDeletion)
|
||||
|
||||
val json = json.encodeToString(newDeletion)
|
||||
deletionPreferences.edit { putString("deleted_$itemId", json) }
|
||||
@@ -309,27 +304,6 @@ class ClimbRepository(database: AscentlyDatabase, private val context: Context)
|
||||
deletionPreferences.edit { clear() }
|
||||
}
|
||||
|
||||
private fun cleanupOldDeletions() {
|
||||
val allPrefs = deletionPreferences.all
|
||||
val cutoff = Instant.now().minusSeconds(90L * 24 * 60 * 60)
|
||||
|
||||
deletionPreferences.edit {
|
||||
for ((key, value) in allPrefs) {
|
||||
if (key.startsWith("deleted_") && value is String) {
|
||||
try {
|
||||
val deletion = json.decodeFromString<DeletedItem>(value)
|
||||
val deletedAt = Instant.parse(deletion.deletedAt)
|
||||
if (deletedAt.isBefore(cutoff)) {
|
||||
remove(key)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun validateDataIntegrity(
|
||||
gyms: List<Gym>,
|
||||
problems: List<Problem>,
|
||||
|
||||
@@ -4,6 +4,7 @@ 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.DeletedItem
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/** Request structure for delta sync - sends only changes since last sync */
|
||||
@@ -14,15 +15,16 @@ data class DeltaSyncRequest(
|
||||
val problems: List<BackupProblem>,
|
||||
val sessions: List<BackupClimbSession>,
|
||||
val attempts: List<BackupAttempt>,
|
||||
val deletedItems: List<DeletedItem>,
|
||||
)
|
||||
|
||||
/** Response structure for delta sync - receives only changes from server */
|
||||
@Serializable
|
||||
data class DeltaSyncResponse(
|
||||
val serverTime: String,
|
||||
val requestFullSync: Boolean = false,
|
||||
val gyms: List<BackupGym>,
|
||||
val problems: List<BackupProblem>,
|
||||
val sessions: List<BackupClimbSession>,
|
||||
val attempts: List<BackupAttempt>,
|
||||
val deletedItems: List<DeletedItem>,
|
||||
)
|
||||
|
||||
@@ -18,6 +18,4 @@ sealed class SyncException(message: String) : IOException(message), Serializable
|
||||
SyncException("Invalid server response: $details")
|
||||
|
||||
data class NetworkError(val details: String) : SyncException("Network error: $details")
|
||||
|
||||
data class General(val details: String) : SyncException(details)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.atridad.ascently.data.repository.ClimbRepository
|
||||
import com.atridad.ascently.data.state.DataStateManager
|
||||
import com.atridad.ascently.utils.AppLogger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -28,7 +27,7 @@ class SyncService(private val context: Context, private val repository: ClimbRep
|
||||
}
|
||||
|
||||
// Currently we only support one provider, but this allows for future expansion
|
||||
private val provider: SyncProvider = AscentlySyncProvider(context, repository, DataStateManager(context))
|
||||
private val provider: SyncProvider = AscentlySyncProvider(context, repository)
|
||||
|
||||
// State
|
||||
private val _isSyncing = MutableStateFlow(false)
|
||||
|
||||
@@ -1,74 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
android:viewportHeight="108">
|
||||
|
||||
<!-- Clean white background -->
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
6
android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 730 B |
|
Before Width: | Height: | Size: 868 B After Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 514 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 628 B |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 854 B |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 970 B |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 933 KiB |
|
Before Width: | Height: | Size: 698 KiB |
|
Before Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 684 KiB |
|
Before Width: | Height: | Size: 707 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 565 KiB |
|
Before Width: | Height: | Size: 533 KiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 551 KiB |
|
Before Width: | Height: | Size: 573 KiB |
BIN
branding/logos/logo-1024-dark.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
branding/logos/logo-1024-white.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
branding/logos/logo-1024.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
branding/logos/logo-128-dark.png
Normal file
|
After Width: | Height: | Size: 804 B |
BIN
branding/logos/logo-128-white.png
Normal file
|
After Width: | Height: | Size: 798 B |
BIN
branding/logos/logo-128.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
branding/logos/logo-2048-dark.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
branding/logos/logo-2048-white.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
branding/logos/logo-2048.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
branding/logos/logo-256-dark.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
branding/logos/logo-256-white.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
branding/logos/logo-256.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
branding/logos/logo-512-dark.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
branding/logos/logo-512-white.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
branding/logos/logo-512.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
branding/logos/logo-64-dark.png
Normal file
|
After Width: | Height: | Size: 411 B |
BIN
branding/logos/logo-64-white.png
Normal file
|
After Width: | Height: | Size: 413 B |
BIN
branding/logos/logo-64.png
Normal file
|
After Width: | Height: | Size: 413 B |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
8
branding/source/icon-dark.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1024" height="1024" fill="#1A1A1A" rx="180" ry="180"/>
|
||||
<g transform="translate(512, 512) scale(4.75) translate(-54, -42.5)">
|
||||
<polygon points="8,75 35,14.25 62,75" fill="#FFC107"/>
|
||||
<polygon points="31.25,75 65,0.75 98.75,75" fill="#F44336"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 411 B |
8
branding/source/icon-light.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1024" height="1024" fill="#FFFFFF" rx="180" ry="180"/>
|
||||
<g transform="translate(512, 512) scale(4.75) translate(-54, -42.5)">
|
||||
<polygon points="8,75 35,14.25 62,75" fill="#FFC107"/>
|
||||
<polygon points="31.25,75 65,0.75 98.75,75" fill="#F44336"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 411 B |
8
branding/source/icon-tinted.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1024" height="1024" fill="transparent" rx="180" ry="180"/>
|
||||
<g transform="translate(512, 512) scale(4.75) translate(-54, -42.5)">
|
||||
<polygon points="8,75 35,14.25 62,75" fill="#000000" opacity="0.8"/>
|
||||
<polygon points="31.25,75 65,0.75 98.75,75" fill="#000000" opacity="0.9"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 443 B |
5
branding/source/logo.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" xmlns="http://www.w3.org/2000/svg">
|
||||
<polygon points="8,75 35,14.25 62,75" fill="#FFC107"/>
|
||||
<polygon points="31.25,75 65,0.75 98.75,75" fill="#F44336"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 254 B |
@@ -6,63 +6,53 @@ import node from "@astrojs/node";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://docs.ascently.app",
|
||||
site: "https://docs.ascently.app",
|
||||
|
||||
integrations: [
|
||||
starlight({
|
||||
title: "Ascently",
|
||||
description:
|
||||
"An offline-first FOSS climb tracking app with an optional sync server.",
|
||||
logo: {
|
||||
light: "./src/assets/logo.png",
|
||||
dark: "./src/assets/logo.png",
|
||||
},
|
||||
favicon: "/favicon.png",
|
||||
social: [
|
||||
{
|
||||
icon: "seti:git",
|
||||
label: "Gitea",
|
||||
href: "https://git.atri.dad/atridad/Ascently",
|
||||
},
|
||||
{
|
||||
icon: "email",
|
||||
label: "Contact",
|
||||
href: "mailto:me@atri.dad",
|
||||
},
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
label: "Download",
|
||||
link: "/download/",
|
||||
},
|
||||
{
|
||||
label: "Self-Hosted Sync",
|
||||
items: [
|
||||
{ label: "Overview", slug: "sync/overview" },
|
||||
{ label: "Quick Start", slug: "sync/quick-start" },
|
||||
{ label: "API Reference", slug: "sync/api-reference" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Privacy",
|
||||
link: "/privacy/",
|
||||
},
|
||||
],
|
||||
customCss: ["./src/styles/custom.css"],
|
||||
}),
|
||||
],
|
||||
|
||||
adapter: node({
|
||||
mode: "standalone",
|
||||
integrations: [
|
||||
starlight({
|
||||
title: "Ascently",
|
||||
description:
|
||||
"An offline-first FOSS climb tracking app with an optional sync server.",
|
||||
logo: {
|
||||
light: "./src/assets/logo.svg",
|
||||
dark: "./src/assets/logo-dark.svg",
|
||||
},
|
||||
favicon: "/favicon.png",
|
||||
social: [
|
||||
{
|
||||
icon: "seti:git",
|
||||
label: "Gitea",
|
||||
href: "https://git.atri.dad/atridad/Ascently",
|
||||
},
|
||||
{
|
||||
icon: "email",
|
||||
label: "Contact",
|
||||
href: "mailto:me@atri.dad",
|
||||
},
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
label: "Download",
|
||||
link: "/download/",
|
||||
},
|
||||
{
|
||||
label: "Self-Hosted Sync",
|
||||
items: [
|
||||
{ label: "Overview", slug: "sync/overview" },
|
||||
{ label: "Quick Start", slug: "sync/quick-start" },
|
||||
{ label: "API Reference", slug: "sync/api-reference" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Privacy",
|
||||
link: "/privacy/",
|
||||
},
|
||||
],
|
||||
customCss: ["./src/styles/custom.css"],
|
||||
}),
|
||||
],
|
||||
|
||||
output: "server",
|
||||
|
||||
build: {
|
||||
inlineStylesheets: "always",
|
||||
},
|
||||
|
||||
experimental: {
|
||||
svgo: true,
|
||||
},
|
||||
adapter: node({
|
||||
mode: "standalone",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.5.2",
|
||||
"@astrojs/starlight": "^0.37.5",
|
||||
"astro": "^5.17.1",
|
||||
"@astrojs/node": "^9.5.1",
|
||||
"@astrojs/starlight": "^0.37.1",
|
||||
"astro": "^5.16.5",
|
||||
"qrcode": "^1.5.4",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
|
||||
789
docs/pnpm-lock.yaml
generated
BIN
docs/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 166 B |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 229 B |