Compare commits

...

37 Commits
0.1.0 ... 1.4.0

Author SHA1 Message Date
f45ff8963d Merge remote-tracking branch 'origin/main' 2025-09-06 23:19:36 -06:00
5988cbf1fb 1.4.0 - Shortcuts & Widgets 2025-09-06 23:19:26 -06:00
13654cde70 Update README.md 2025-09-01 07:14:52 +00:00
9064dbe2ef Update README.md 2025-09-01 07:14:40 +00:00
0537da79e4 1.3.1 - Graphing Fixes Cont'd 2025-08-31 19:05:18 -06:00
4804049274 1.3.1 - Graphing Fixes Cont'd 2025-08-31 19:03:43 -06:00
8db6ed0e82 1.3.0 - Graphing Fixes 2025-08-28 00:18:54 -06:00
8b9901383a 1.1.2 - More fixes for notification reliability 2025-08-27 22:21:53 -06:00
cf2adeef7a 1.1.2 - More fixes for notification reliability 2025-08-22 23:22:23 -06:00
a7481135b4 1.1.1 - More fixes for notification reliabilityyyyy 2025-08-22 21:00:08 -06:00
748a23e1c0 1.1.1 - More fixes for notification reliability 2025-08-22 20:59:36 -06:00
f078cfc6e1 1.1.0 - Export/Import overhaul 2025-08-22 20:39:19 -06:00
8bb1f422c1 1.0.1 - Notification reliability update... again 2025-08-22 19:13:40 -06:00
327dfba425 1.0.1 - Notification reliability update 2025-08-22 19:11:21 -06:00
96759e402d 1.0.0 - 1.0 baybeeeee 2025-08-22 16:19:25 -06:00
ed76fb2fb2 Remove outdated comment 2025-08-22 10:56:43 -06:00
870278f240 0.5.0 - Optimizations and better session management 2025-08-19 00:35:04 -06:00
4eef77bd3b 0.4.5 2025-08-18 09:53:40 -06:00
2d957db948 0.4.5 2025-08-18 09:52:53 -06:00
22bed6a961 Merge pull request 'Updated to support API version 36 properly' (#2) from dependencies into main
Reviewed-on: atridad/OpenClimb#2
2025-08-18 15:49:36 +00:00
b443c18a19 Updated to support API version 36 properly 2025-08-18 00:46:28 -06:00
89f1e350b3 0.4.4 - Cleaned up range views 2025-08-17 01:29:15 -06:00
0f976f685f 0.4.3 - Removing Average Grade... its not useful 2025-08-17 01:13:10 -06:00
c07186a7df 0.4.2 - Fixed issue with photo upload streams 2025-08-17 00:57:48 -06:00
15a5e217a5 0.4.1 - Small fix for share image 2025-08-16 20:20:30 -06:00
b86ab591fe Readme changes w obtainium 2025-08-16 19:06:39 -06:00
70c85d159e 0.4.0 - Bug fixes and improvements 2025-08-16 19:04:11 -06:00
d6c5e937df MOAR 2025-08-16 02:35:12 -06:00
829bbbff7a More cleanup 2025-08-16 02:34:29 -06:00
e1ebf412bd Add .gitignore 2025-08-16 02:33:43 -06:00
5c133b655e Remove files I dont need committed... 2025-08-16 02:33:11 -06:00
cc1edbc65c 0.3.3 2025-08-16 02:31:52 -06:00
ca770b9db3 0.3.2 - Optimizations 2025-08-16 00:48:38 -06:00
7edb7c8191 0.3.1 - Bugfix for status bar 2025-08-15 19:38:01 -06:00
1ca6b33882 Build 2025-08-15 19:31:35 -06:00
bd6b5cc652 0.3.0 - Filtering and Better Scales 2025-08-15 19:30:50 -06:00
6e16a30429 0.2.0 quick fixes 2025-08-15 14:51:30 -06:00
140 changed files with 4783 additions and 5211 deletions

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# Gradle files
.gradle/
build/
release/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.aab
*.apk
output-metadata.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof

Binary file not shown.

View File

@@ -1,769 +0,0 @@
package org.gradle.accessors.dm;
import org.gradle.api.NonNullApi;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.plugin.use.PluginDependency;
import org.gradle.api.artifacts.ExternalModuleDependencyBundle;
import org.gradle.api.artifacts.MutableVersionConstraint;
import org.gradle.api.provider.Provider;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.internal.catalog.AbstractExternalDependencyFactory;
import org.gradle.api.internal.catalog.DefaultVersionCatalog;
import java.util.Map;
import org.gradle.api.internal.attributes.ImmutableAttributesFactory;
import org.gradle.api.internal.artifacts.dsl.CapabilityNotationParser;
import javax.inject.Inject;
/**
* A catalog of dependencies accessible via the {@code libs} extension.
*/
@NonNullApi
public class LibrariesForLibs extends AbstractExternalDependencyFactory {
private final AbstractExternalDependencyFactory owner = this;
private final AndroidxLibraryAccessors laccForAndroidxLibraryAccessors = new AndroidxLibraryAccessors(owner);
private final CoilLibraryAccessors laccForCoilLibraryAccessors = new CoilLibraryAccessors(owner);
private final KotlinxLibraryAccessors laccForKotlinxLibraryAccessors = new KotlinxLibraryAccessors(owner);
private final VersionAccessors vaccForVersionAccessors = new VersionAccessors(providers, config);
private final BundleAccessors baccForBundleAccessors = new BundleAccessors(objects, providers, config, attributesFactory, capabilityNotationParser);
private final PluginAccessors paccForPluginAccessors = new PluginAccessors(providers, config);
@Inject
public LibrariesForLibs(DefaultVersionCatalog config, ProviderFactory providers, ObjectFactory objects, ImmutableAttributesFactory attributesFactory, CapabilityNotationParser capabilityNotationParser) {
super(config, providers, objects, attributesFactory, capabilityNotationParser);
}
/**
* Dependency provider for <b>junit</b> with <b>junit:junit</b> coordinates and
* with version reference <b>junit</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getJunit() {
return create("junit");
}
/**
* Dependency provider for <b>mpandroidchart</b> with <b>com.github.PhilJay:MPAndroidChart</b> coordinates and
* with version <b>v3.1.0</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getMpandroidchart() {
return create("mpandroidchart");
}
/**
* Group of libraries at <b>androidx</b>
*/
public AndroidxLibraryAccessors getAndroidx() {
return laccForAndroidxLibraryAccessors;
}
/**
* Group of libraries at <b>coil</b>
*/
public CoilLibraryAccessors getCoil() {
return laccForCoilLibraryAccessors;
}
/**
* Group of libraries at <b>kotlinx</b>
*/
public KotlinxLibraryAccessors getKotlinx() {
return laccForKotlinxLibraryAccessors;
}
/**
* Group of versions at <b>versions</b>
*/
public VersionAccessors getVersions() {
return vaccForVersionAccessors;
}
/**
* Group of bundles at <b>bundles</b>
*/
public BundleAccessors getBundles() {
return baccForBundleAccessors;
}
/**
* Group of plugins at <b>plugins</b>
*/
public PluginAccessors getPlugins() {
return paccForPluginAccessors;
}
public static class AndroidxLibraryAccessors extends SubDependencyFactory {
private final AndroidxActivityLibraryAccessors laccForAndroidxActivityLibraryAccessors = new AndroidxActivityLibraryAccessors(owner);
private final AndroidxComposeLibraryAccessors laccForAndroidxComposeLibraryAccessors = new AndroidxComposeLibraryAccessors(owner);
private final AndroidxCoreLibraryAccessors laccForAndroidxCoreLibraryAccessors = new AndroidxCoreLibraryAccessors(owner);
private final AndroidxEspressoLibraryAccessors laccForAndroidxEspressoLibraryAccessors = new AndroidxEspressoLibraryAccessors(owner);
private final AndroidxLifecycleLibraryAccessors laccForAndroidxLifecycleLibraryAccessors = new AndroidxLifecycleLibraryAccessors(owner);
private final AndroidxNavigationLibraryAccessors laccForAndroidxNavigationLibraryAccessors = new AndroidxNavigationLibraryAccessors(owner);
private final AndroidxRoomLibraryAccessors laccForAndroidxRoomLibraryAccessors = new AndroidxRoomLibraryAccessors(owner);
private final AndroidxUiLibraryAccessors laccForAndroidxUiLibraryAccessors = new AndroidxUiLibraryAccessors(owner);
public AndroidxLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>junit</b> with <b>androidx.test.ext:junit</b> coordinates and
* with version reference <b>junitVersion</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getJunit() {
return create("androidx.junit");
}
/**
* Dependency provider for <b>material3</b> with <b>androidx.compose.material3:material3</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getMaterial3() {
return create("androidx.material3");
}
/**
* Group of libraries at <b>androidx.activity</b>
*/
public AndroidxActivityLibraryAccessors getActivity() {
return laccForAndroidxActivityLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.compose</b>
*/
public AndroidxComposeLibraryAccessors getCompose() {
return laccForAndroidxComposeLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.core</b>
*/
public AndroidxCoreLibraryAccessors getCore() {
return laccForAndroidxCoreLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.espresso</b>
*/
public AndroidxEspressoLibraryAccessors getEspresso() {
return laccForAndroidxEspressoLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.lifecycle</b>
*/
public AndroidxLifecycleLibraryAccessors getLifecycle() {
return laccForAndroidxLifecycleLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.navigation</b>
*/
public AndroidxNavigationLibraryAccessors getNavigation() {
return laccForAndroidxNavigationLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.room</b>
*/
public AndroidxRoomLibraryAccessors getRoom() {
return laccForAndroidxRoomLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.ui</b>
*/
public AndroidxUiLibraryAccessors getUi() {
return laccForAndroidxUiLibraryAccessors;
}
}
public static class AndroidxActivityLibraryAccessors extends SubDependencyFactory {
public AndroidxActivityLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>compose</b> with <b>androidx.activity:activity-compose</b> coordinates and
* with version reference <b>activityCompose</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getCompose() {
return create("androidx.activity.compose");
}
}
public static class AndroidxComposeLibraryAccessors extends SubDependencyFactory {
public AndroidxComposeLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>bom</b> with <b>androidx.compose:compose-bom</b> coordinates and
* with version reference <b>composeBom</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getBom() {
return create("androidx.compose.bom");
}
}
public static class AndroidxCoreLibraryAccessors extends SubDependencyFactory {
public AndroidxCoreLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>ktx</b> with <b>androidx.core:core-ktx</b> coordinates and
* with version reference <b>coreKtx</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getKtx() {
return create("androidx.core.ktx");
}
}
public static class AndroidxEspressoLibraryAccessors extends SubDependencyFactory {
public AndroidxEspressoLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>core</b> with <b>androidx.test.espresso:espresso-core</b> coordinates and
* with version reference <b>espressoCore</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getCore() {
return create("androidx.espresso.core");
}
}
public static class AndroidxLifecycleLibraryAccessors extends SubDependencyFactory {
private final AndroidxLifecycleRuntimeLibraryAccessors laccForAndroidxLifecycleRuntimeLibraryAccessors = new AndroidxLifecycleRuntimeLibraryAccessors(owner);
private final AndroidxLifecycleViewmodelLibraryAccessors laccForAndroidxLifecycleViewmodelLibraryAccessors = new AndroidxLifecycleViewmodelLibraryAccessors(owner);
public AndroidxLifecycleLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Group of libraries at <b>androidx.lifecycle.runtime</b>
*/
public AndroidxLifecycleRuntimeLibraryAccessors getRuntime() {
return laccForAndroidxLifecycleRuntimeLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.lifecycle.viewmodel</b>
*/
public AndroidxLifecycleViewmodelLibraryAccessors getViewmodel() {
return laccForAndroidxLifecycleViewmodelLibraryAccessors;
}
}
public static class AndroidxLifecycleRuntimeLibraryAccessors extends SubDependencyFactory {
public AndroidxLifecycleRuntimeLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>ktx</b> with <b>androidx.lifecycle:lifecycle-runtime-ktx</b> coordinates and
* with version reference <b>lifecycleRuntimeKtx</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getKtx() {
return create("androidx.lifecycle.runtime.ktx");
}
}
public static class AndroidxLifecycleViewmodelLibraryAccessors extends SubDependencyFactory {
public AndroidxLifecycleViewmodelLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>compose</b> with <b>androidx.lifecycle:lifecycle-viewmodel-compose</b> coordinates and
* with version reference <b>viewmodel</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getCompose() {
return create("androidx.lifecycle.viewmodel.compose");
}
}
public static class AndroidxNavigationLibraryAccessors extends SubDependencyFactory {
public AndroidxNavigationLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>compose</b> with <b>androidx.navigation:navigation-compose</b> coordinates and
* with version reference <b>navigation</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getCompose() {
return create("androidx.navigation.compose");
}
}
public static class AndroidxRoomLibraryAccessors extends SubDependencyFactory {
public AndroidxRoomLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>compiler</b> with <b>androidx.room:room-compiler</b> coordinates and
* with version reference <b>room</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getCompiler() {
return create("androidx.room.compiler");
}
/**
* Dependency provider for <b>ktx</b> with <b>androidx.room:room-ktx</b> coordinates and
* with version reference <b>room</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getKtx() {
return create("androidx.room.ktx");
}
/**
* Dependency provider for <b>runtime</b> with <b>androidx.room:room-runtime</b> coordinates and
* with version reference <b>room</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getRuntime() {
return create("androidx.room.runtime");
}
}
public static class AndroidxUiLibraryAccessors extends SubDependencyFactory implements DependencyNotationSupplier {
private final AndroidxUiTestLibraryAccessors laccForAndroidxUiTestLibraryAccessors = new AndroidxUiTestLibraryAccessors(owner);
private final AndroidxUiToolingLibraryAccessors laccForAndroidxUiToolingLibraryAccessors = new AndroidxUiToolingLibraryAccessors(owner);
public AndroidxUiLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>ui</b> with <b>androidx.compose.ui:ui</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> asProvider() {
return create("androidx.ui");
}
/**
* Dependency provider for <b>graphics</b> with <b>androidx.compose.ui:ui-graphics</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getGraphics() {
return create("androidx.ui.graphics");
}
/**
* Group of libraries at <b>androidx.ui.test</b>
*/
public AndroidxUiTestLibraryAccessors getTest() {
return laccForAndroidxUiTestLibraryAccessors;
}
/**
* Group of libraries at <b>androidx.ui.tooling</b>
*/
public AndroidxUiToolingLibraryAccessors getTooling() {
return laccForAndroidxUiToolingLibraryAccessors;
}
}
public static class AndroidxUiTestLibraryAccessors extends SubDependencyFactory {
public AndroidxUiTestLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>junit4</b> with <b>androidx.compose.ui:ui-test-junit4</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getJunit4() {
return create("androidx.ui.test.junit4");
}
/**
* Dependency provider for <b>manifest</b> with <b>androidx.compose.ui:ui-test-manifest</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getManifest() {
return create("androidx.ui.test.manifest");
}
}
public static class AndroidxUiToolingLibraryAccessors extends SubDependencyFactory implements DependencyNotationSupplier {
public AndroidxUiToolingLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>tooling</b> with <b>androidx.compose.ui:ui-tooling</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> asProvider() {
return create("androidx.ui.tooling");
}
/**
* Dependency provider for <b>preview</b> with <b>androidx.compose.ui:ui-tooling-preview</b> coordinates and
* with <b>no version specified</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getPreview() {
return create("androidx.ui.tooling.preview");
}
}
public static class CoilLibraryAccessors extends SubDependencyFactory {
public CoilLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>compose</b> with <b>io.coil-kt:coil-compose</b> coordinates and
* with version reference <b>coil</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getCompose() {
return create("coil.compose");
}
}
public static class KotlinxLibraryAccessors extends SubDependencyFactory {
private final KotlinxCoroutinesLibraryAccessors laccForKotlinxCoroutinesLibraryAccessors = new KotlinxCoroutinesLibraryAccessors(owner);
private final KotlinxSerializationLibraryAccessors laccForKotlinxSerializationLibraryAccessors = new KotlinxSerializationLibraryAccessors(owner);
public KotlinxLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Group of libraries at <b>kotlinx.coroutines</b>
*/
public KotlinxCoroutinesLibraryAccessors getCoroutines() {
return laccForKotlinxCoroutinesLibraryAccessors;
}
/**
* Group of libraries at <b>kotlinx.serialization</b>
*/
public KotlinxSerializationLibraryAccessors getSerialization() {
return laccForKotlinxSerializationLibraryAccessors;
}
}
public static class KotlinxCoroutinesLibraryAccessors extends SubDependencyFactory {
public KotlinxCoroutinesLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>android</b> with <b>org.jetbrains.kotlinx:kotlinx-coroutines-android</b> coordinates and
* with version reference <b>kotlinxCoroutines</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getAndroid() {
return create("kotlinx.coroutines.android");
}
}
public static class KotlinxSerializationLibraryAccessors extends SubDependencyFactory {
public KotlinxSerializationLibraryAccessors(AbstractExternalDependencyFactory owner) { super(owner); }
/**
* Dependency provider for <b>json</b> with <b>org.jetbrains.kotlinx:kotlinx-serialization-json</b> coordinates and
* with version reference <b>kotlinxSerialization</b>
* <p>
* This dependency was declared in catalog libs.versions.toml
*/
public Provider<MinimalExternalModuleDependency> getJson() {
return create("kotlinx.serialization.json");
}
}
public static class VersionAccessors extends VersionFactory {
public VersionAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
/**
* Version alias <b>activityCompose</b> with value <b>1.10.1</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getActivityCompose() { return getVersion("activityCompose"); }
/**
* Version alias <b>agp</b> with value <b>8.9.1</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getAgp() { return getVersion("agp"); }
/**
* Version alias <b>coil</b> with value <b>2.7.0</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getCoil() { return getVersion("coil"); }
/**
* Version alias <b>composeBom</b> with value <b>2024.09.00</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getComposeBom() { return getVersion("composeBom"); }
/**
* Version alias <b>coreKtx</b> with value <b>1.15.0</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getCoreKtx() { return getVersion("coreKtx"); }
/**
* Version alias <b>espressoCore</b> with value <b>3.7.0</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getEspressoCore() { return getVersion("espressoCore"); }
/**
* Version alias <b>junit</b> with value <b>4.13.2</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getJunit() { return getVersion("junit"); }
/**
* Version alias <b>junitVersion</b> with value <b>1.3.0</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getJunitVersion() { return getVersion("junitVersion"); }
/**
* Version alias <b>kotlin</b> with value <b>2.0.21</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getKotlin() { return getVersion("kotlin"); }
/**
* Version alias <b>kotlinxCoroutines</b> with value <b>1.9.0</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getKotlinxCoroutines() { return getVersion("kotlinxCoroutines"); }
/**
* Version alias <b>kotlinxSerialization</b> with value <b>1.7.1</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getKotlinxSerialization() { return getVersion("kotlinxSerialization"); }
/**
* Version alias <b>ksp</b> with value <b>2.0.21-1.0.25</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getKsp() { return getVersion("ksp"); }
/**
* Version alias <b>lifecycleRuntimeKtx</b> with value <b>2.9.2</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getLifecycleRuntimeKtx() { return getVersion("lifecycleRuntimeKtx"); }
/**
* Version alias <b>navigation</b> with value <b>2.8.4</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getNavigation() { return getVersion("navigation"); }
/**
* Version alias <b>room</b> with value <b>2.6.1</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getRoom() { return getVersion("room"); }
/**
* Version alias <b>viewmodel</b> with value <b>2.9.2</b>
* <p>
* If the version is a rich version and cannot be represented as a
* single version string, an empty string is returned.
* <p>
* This version was declared in catalog libs.versions.toml
*/
public Provider<String> getViewmodel() { return getVersion("viewmodel"); }
}
public static class BundleAccessors extends BundleFactory {
public BundleAccessors(ObjectFactory objects, ProviderFactory providers, DefaultVersionCatalog config, ImmutableAttributesFactory attributesFactory, CapabilityNotationParser capabilityNotationParser) { super(objects, providers, config, attributesFactory, capabilityNotationParser); }
}
public static class PluginAccessors extends PluginFactory {
private final AndroidPluginAccessors paccForAndroidPluginAccessors = new AndroidPluginAccessors(providers, config);
private final KotlinPluginAccessors paccForKotlinPluginAccessors = new KotlinPluginAccessors(providers, config);
public PluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
/**
* Plugin provider for <b>ksp</b> with plugin id <b>com.google.devtools.ksp</b> and
* with version reference <b>ksp</b>
* <p>
* This plugin was declared in catalog libs.versions.toml
*/
public Provider<PluginDependency> getKsp() { return createPlugin("ksp"); }
/**
* Group of plugins at <b>plugins.android</b>
*/
public AndroidPluginAccessors getAndroid() {
return paccForAndroidPluginAccessors;
}
/**
* Group of plugins at <b>plugins.kotlin</b>
*/
public KotlinPluginAccessors getKotlin() {
return paccForKotlinPluginAccessors;
}
}
public static class AndroidPluginAccessors extends PluginFactory {
public AndroidPluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
/**
* Plugin provider for <b>android.application</b> with plugin id <b>com.android.application</b> and
* with version reference <b>agp</b>
* <p>
* This plugin was declared in catalog libs.versions.toml
*/
public Provider<PluginDependency> getApplication() { return createPlugin("android.application"); }
}
public static class KotlinPluginAccessors extends PluginFactory {
public KotlinPluginAccessors(ProviderFactory providers, DefaultVersionCatalog config) { super(providers, config); }
/**
* Plugin provider for <b>kotlin.android</b> with plugin id <b>org.jetbrains.kotlin.android</b> and
* with version reference <b>kotlin</b>
* <p>
* This plugin was declared in catalog libs.versions.toml
*/
public Provider<PluginDependency> getAndroid() { return createPlugin("kotlin.android"); }
/**
* Plugin provider for <b>kotlin.compose</b> with plugin id <b>org.jetbrains.kotlin.plugin.compose</b> and
* with version reference <b>kotlin</b>
* <p>
* This plugin was declared in catalog libs.versions.toml
*/
public Provider<PluginDependency> getCompose() { return createPlugin("kotlin.compose"); }
/**
* Plugin provider for <b>kotlin.serialization</b> with plugin id <b>org.jetbrains.kotlin.plugin.serialization</b> and
* with version reference <b>kotlin</b>
* <p>
* This plugin was declared in catalog libs.versions.toml
*/
public Provider<PluginDependency> getSerialization() { return createPlugin("kotlin.serialization"); }
}
}

View File

@@ -1,2 +0,0 @@
#Fri Aug 15 14:37:16 MDT 2025
gradle.version=8.11.1

View File

@@ -1,2 +0,0 @@
#Fri Aug 15 12:29:02 MDT 2025
java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home

Binary file not shown.

View File

@@ -51,6 +51,18 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="35" />
<option name="brand" value="OnePlus" />
<option name="codename" value="OP5552L1" />
<option name="id" value="OP5552L1" />
<option name="labId" value="google" />
<option name="manufacturer" value="OnePlus" />
<option name="name" value="CPH2415" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2412" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="OPPO" />
@@ -817,6 +829,19 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="google" />
<option name="codename" value="tokay" />
<option name="default" value="true" />
<option name="id" value="tokay" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 9" />
<option name="screenDensity" value="420" />
<option name="screenX" value="1080" />
<option name="screenY" value="2424" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="34" />
<option name="brand" value="samsung" />

View File

@@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-09-07T04:49:14.182787Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/atridad/.android/avd/Medium_Phone.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

3
.idea/misc.xml generated
View File

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

View File

@@ -1,87 +0,0 @@
kotlin version: 2.0.21
error message: java.lang.IllegalStateException: Storage for [/Users/atridad/Developer/personal/OpenClimb/app/build/kspCaches/debug/symbolLookups/id-to-file.tab] is already registered
at org.jetbrains.kotlin.com.intellij.util.io.FilePageCache.registerPagedFileStorage(FilePageCache.java:410)
at org.jetbrains.kotlin.com.intellij.util.io.PagedFileStorage.<init>(PagedFileStorage.java:72)
at org.jetbrains.kotlin.com.intellij.util.io.ResizeableMappedFile.<init>(ResizeableMappedFile.java:55)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentBTreeEnumerator.<init>(PersistentBTreeEnumerator.java:128)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumerator.createDefaultEnumerator(PersistentEnumerator.java:52)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:165)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:140)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.buildImplementation(PersistentMapBuilder.java:88)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.build(PersistentMapBuilder.java:71)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:45)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:71)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.createMap(LazyStorage.kt:62)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.getStorageOrCreateNew(LazyStorage.kt:59)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.set(PersistentStorage.kt:96)
at org.jetbrains.kotlin.incremental.LookupStorage.addFileIfNeeded(LookupStorage.kt:165)
at org.jetbrains.kotlin.incremental.LookupStorage.addAll$lambda$4(LookupStorage.kt:117)
at org.jetbrains.kotlin.utils.CollectionsKt.keysToMap(collections.kt:117)
at org.jetbrains.kotlin.incremental.LookupStorage.addAll(LookupStorage.kt:117)
at org.jetbrains.kotlin.incremental.BuildUtilKt.update(buildUtil.kt:134)
at com.google.devtools.ksp.LookupStorageWrapperImpl.update(IncrementalContext.kt:231)
at com.google.devtools.ksp.common.IncrementalContextBase.updateLookupCache(IncrementalContextBase.kt:133)
at com.google.devtools.ksp.common.IncrementalContextBase.updateCaches(IncrementalContextBase.kt:365)
at com.google.devtools.ksp.common.IncrementalContextBase.updateCachesAndOutputs(IncrementalContextBase.kt:471)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:362)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.repeatAnalysisIfNeeded(KotlinToJVMBytecodeCompiler.kt:282)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1555)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:714)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Suppressed: java.lang.Exception: Storage[/Users/atridad/Developer/personal/OpenClimb/app/build/kspCaches/debug/symbolLookups/id-to-file.tab] registration stack trace
at org.jetbrains.kotlin.com.intellij.util.io.FilePageCache.registerPagedFileStorage(FilePageCache.java:437)
at org.jetbrains.kotlin.com.intellij.util.io.PagedFileStorage.<init>(PagedFileStorage.java:72)
at org.jetbrains.kotlin.com.intellij.util.io.ResizeableMappedFile.<init>(ResizeableMappedFile.java:55)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentBTreeEnumerator.<init>(PersistentBTreeEnumerator.java:128)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumerator.createDefaultEnumerator(PersistentEnumerator.java:52)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:165)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:140)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.buildImplementation(PersistentMapBuilder.java:88)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.build(PersistentMapBuilder.java:71)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:45)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:71)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.createMap(LazyStorage.kt:62)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.getStorageIfExists(LazyStorage.kt:53)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.get(LazyStorage.kt:76)
at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.get(PersistentStorage.kt:92)
at org.jetbrains.kotlin.incremental.LookupStorage.get(LookupStorage.kt:99)
at com.google.devtools.ksp.LookupStorageWrapperImpl.get(IncrementalContext.kt:224)
at com.google.devtools.ksp.common.IncrementalContextBase.calcDirtyFiles(IncrementalContextBase.kt:234)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:196)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:189)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:189)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
... 23 more

View File

@@ -1,87 +0,0 @@
kotlin version: 2.0.21
error message: java.lang.IllegalStateException: Storage for [/Users/atridad/Developer/personal/OpenClimb/app/build/kspCaches/debug/symbolLookups/id-to-file.tab] is already registered
at org.jetbrains.kotlin.com.intellij.util.io.FilePageCache.registerPagedFileStorage(FilePageCache.java:410)
at org.jetbrains.kotlin.com.intellij.util.io.PagedFileStorage.<init>(PagedFileStorage.java:72)
at org.jetbrains.kotlin.com.intellij.util.io.ResizeableMappedFile.<init>(ResizeableMappedFile.java:55)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentBTreeEnumerator.<init>(PersistentBTreeEnumerator.java:128)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumerator.createDefaultEnumerator(PersistentEnumerator.java:52)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:165)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:140)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.buildImplementation(PersistentMapBuilder.java:88)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.build(PersistentMapBuilder.java:71)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:45)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:71)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.createMap(LazyStorage.kt:62)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.getStorageOrCreateNew(LazyStorage.kt:59)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.set(PersistentStorage.kt:96)
at org.jetbrains.kotlin.incremental.LookupStorage.addFileIfNeeded(LookupStorage.kt:165)
at org.jetbrains.kotlin.incremental.LookupStorage.addAll$lambda$4(LookupStorage.kt:117)
at org.jetbrains.kotlin.utils.CollectionsKt.keysToMap(collections.kt:117)
at org.jetbrains.kotlin.incremental.LookupStorage.addAll(LookupStorage.kt:117)
at org.jetbrains.kotlin.incremental.BuildUtilKt.update(buildUtil.kt:134)
at com.google.devtools.ksp.LookupStorageWrapperImpl.update(IncrementalContext.kt:231)
at com.google.devtools.ksp.common.IncrementalContextBase.updateLookupCache(IncrementalContextBase.kt:133)
at com.google.devtools.ksp.common.IncrementalContextBase.updateCaches(IncrementalContextBase.kt:365)
at com.google.devtools.ksp.common.IncrementalContextBase.updateCachesAndOutputs(IncrementalContextBase.kt:471)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:362)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.repeatAnalysisIfNeeded(KotlinToJVMBytecodeCompiler.kt:282)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:195)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:106)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:170)
at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:43)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:103)
at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:49)
at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1555)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:714)
at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Suppressed: java.lang.Exception: Storage[/Users/atridad/Developer/personal/OpenClimb/app/build/kspCaches/debug/symbolLookups/id-to-file.tab] registration stack trace
at org.jetbrains.kotlin.com.intellij.util.io.FilePageCache.registerPagedFileStorage(FilePageCache.java:437)
at org.jetbrains.kotlin.com.intellij.util.io.PagedFileStorage.<init>(PagedFileStorage.java:72)
at org.jetbrains.kotlin.com.intellij.util.io.ResizeableMappedFile.<init>(ResizeableMappedFile.java:55)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentBTreeEnumerator.<init>(PersistentBTreeEnumerator.java:128)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentEnumerator.createDefaultEnumerator(PersistentEnumerator.java:52)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:165)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.<init>(PersistentMapImpl.java:140)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.buildImplementation(PersistentMapBuilder.java:88)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapBuilder.build(PersistentMapBuilder.java:71)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:45)
at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.<init>(PersistentHashMap.java:71)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.createMap(LazyStorage.kt:62)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.getStorageIfExists(LazyStorage.kt:53)
at org.jetbrains.kotlin.incremental.storage.LazyStorage.get(LazyStorage.kt:76)
at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.get(PersistentStorage.kt:92)
at org.jetbrains.kotlin.incremental.LookupStorage.get(LookupStorage.kt:99)
at com.google.devtools.ksp.LookupStorageWrapperImpl.get(IncrementalContext.kt:224)
at com.google.devtools.ksp.common.IncrementalContextBase.calcDirtyFiles(IncrementalContextBase.kt:234)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:196)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$2.invoke(KotlinSymbolProcessingExtension.kt:189)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:414)
at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:189)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$12(KotlinToJVMBytecodeCompiler.kt:373)
at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112)
at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:364)
... 23 more

View File

@@ -1,3 +0,0 @@
{
"java.configuration.updateBuildConfiguration": "disabled"
}

View File

@@ -6,8 +6,8 @@ This is a FOSS Android app meant to help climbers track their sessions, routes/p
You have two options:
1. Download the latest APK from the Released page
2. Use <a href="">Obtainium</a>
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)
## Requirements

View File

@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -8,21 +10,21 @@ plugins {
android {
namespace = "com.atridad.openclimb"
compileSdk = 35
compileSdk = 36
defaultConfig {
applicationId = "com.atridad.openclimb"
minSdk = 31
targetSdk = 35
versionCode = 1
versionName = "0.1.0"
minSdk = 34
targetSdk = 36
versionCode = 21
versionName = "1.4.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@@ -30,17 +32,27 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "11"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
buildFeatures {
compose = true
}
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
dependencies {
// Core Android libraries
implementation(libs.androidx.core.ktx)
@@ -57,6 +69,7 @@ dependencies {
// Room Database
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
// Navigation
@@ -74,13 +87,18 @@ dependencies {
// Image Loading
implementation(libs.coil.compose)
// Charts - Placeholder for future implementation
// Charts will be implemented with a stable library in future versions
// Testing
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)

Binary file not shown.

View File

@@ -1,37 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.atridad.openclimb",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "0.1.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 31
}

View File

@@ -7,12 +7,15 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- Hardware features -->
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Permissions for notifications and foreground service -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -27,14 +30,16 @@
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.OpenClimb">
android:theme="@style/Theme.OpenClimb.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- FileProvider for sharing images -->
<provider
android:name="androidx.core.content.FileProvider"
@@ -45,17 +50,30 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
<!-- Session tracking service -->
<service
android:name=".service.SessionTrackingService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse">
android:foregroundServiceType="specialUse"
android:description="@string/session_tracking_service_description">
<meta-data
android:name="android.app.foreground_service_type"
android:value="specialUse" />
</service>
<!-- Widget Provider -->
<receiver
android:name=".widget.ClimbStatsWidgetProvider"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_climb_stats_info" />
</receiver>
</application>
</manifest>
</manifest>

View File

@@ -1,27 +1,40 @@
package com.atridad.openclimb
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
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
class MainActivity : ComponentActivity() {
private var shortcutAction by mutableStateOf<String?>(null)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.Theme_OpenClimb)
enableEdgeToEdge()
shortcutAction = intent?.action
setContent {
OpenClimbTheme {
Surface(
modifier = Modifier.fillMaxSize()
) {
OpenClimbApp()
Surface(modifier = Modifier.fillMaxSize()) {
OpenClimbApp(shortcutAction = shortcutAction)
}
}
}
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
shortcutAction = intent.action
}
}

View File

@@ -53,6 +53,9 @@ interface AttemptDao {
@Query("SELECT COUNT(*) FROM attempts")
suspend fun getAttemptsCount(): Int
@Query("DELETE FROM attempts")
suspend fun deleteAllAttempts()
@Query("SELECT COUNT(*) FROM attempts WHERE sessionId = :sessionId")
suspend fun getAttemptsCountBySession(sessionId: String): Int

View File

@@ -59,6 +59,9 @@ interface ClimbSessionDao {
@Query("SELECT * FROM climb_sessions WHERE status = 'ACTIVE' ORDER BY date DESC LIMIT 1")
suspend fun getActiveSession(): ClimbSession?
@Query("DELETE FROM climb_sessions")
suspend fun deleteAllSessions()
@Query("SELECT * FROM climb_sessions WHERE status = 'ACTIVE' ORDER BY date DESC LIMIT 1")
fun getActiveSessionFlow(): Flow<ClimbSession?>
}

View File

@@ -37,4 +37,7 @@ interface GymDao {
@Query("SELECT * FROM gyms WHERE name LIKE '%' || :searchQuery || '%' OR location LIKE '%' || :searchQuery || '%'")
fun searchGyms(searchQuery: String): Flow<List<Gym>>
@Query("DELETE FROM gyms")
suspend fun deleteAllGyms()
}

View File

@@ -59,4 +59,10 @@ interface ProblemDao {
ORDER BY updatedAt DESC
""")
fun searchProblems(searchQuery: String): Flow<List<Problem>>
@Query("SELECT COUNT(*) FROM problems")
suspend fun getProblemsCount(): Int
@Query("DELETE FROM problems")
suspend fun deleteAllProblems()
}

View File

@@ -9,12 +9,10 @@ import java.time.LocalDateTime
@Serializable
enum class AttemptResult {
SUCCESS, // Completed the problem/route
FALL, // Fell but made progress
NO_PROGRESS, // Couldn't make meaningful progress
FLASH, // Completed on first try
REDPOINT, // Completed after previous attempts
ONSIGHT // Completed on first try without prior knowledge
SUCCESS,
FALL,
NO_PROGRESS,
FLASH,
}
@Entity(

View File

@@ -31,10 +31,10 @@ data class ClimbSession(
@PrimaryKey
val id: String,
val gymId: String,
val date: String, // ISO date string
val startTime: String? = null, // When session was started
val endTime: String? = null, // When session was completed
val duration: Long? = null, // Duration in minutes (calculated when completed)
val date: String,
val startTime: String? = null,
val endTime: String? = null,
val duration: Long? = null,
val status: SessionStatus = SessionStatus.ACTIVE,
val notes: String? = null,
val createdAt: String,
@@ -65,7 +65,7 @@ data class ClimbSession(
val start = LocalDateTime.parse(startTime)
val end = LocalDateTime.parse(endTime)
java.time.Duration.between(start, end).toMinutes()
} catch (e: Exception) {
} catch (_: Exception) {
null
}
} else null

View File

@@ -5,5 +5,13 @@ import kotlinx.serialization.Serializable
@Serializable
enum class ClimbType {
ROPE,
BOULDER
BOULDER;
/**
* Get the display name
*/
fun getDisplayName(): String = when (this) {
ROPE -> "Rope"
BOULDER -> "Bouldering"
}
}

View File

@@ -4,23 +4,105 @@ import kotlinx.serialization.Serializable
@Serializable
enum class DifficultySystem {
// Rope climbing systems
YDS, // Yosemite Decimal System (5.1 - 5.15d)
FRENCH, // French system (3 - 9c+)
UIAA, // UIAA system (I - XII+)
BRITISH, // British system (Mod - E11)
// Bouldering
V_SCALE, // V-Scale (VB - V17)
FONT, // Fontainebleau (3 - 8C+)
// Bouldering systems
V_SCALE, // V-Scale (VB - V17)
FONT, // Fontainebleau (3 - 9A+)
// Rope
YDS, // Yosemite Decimal System (5.0 - 5.15d)
// Custom system for gyms that use their own colors/naming
CUSTOM
// Custom difficulty systems
CUSTOM;
/**
* Get the display name for the UI
*/
fun getDisplayName(): String = when (this) {
V_SCALE -> "V Scale"
FONT -> "Font Scale"
YDS -> "YDS (Yosemite)"
CUSTOM -> "Custom"
}
/**
* Check if this system is for bouldering
*/
fun isBoulderingSystem(): Boolean = when (this) {
V_SCALE, FONT -> true
YDS -> false
CUSTOM -> true // Custom is available for all
}
/**
* Check if this system is for rope climbing
*/
fun isRopeSystem(): Boolean = when (this) {
YDS -> true
V_SCALE, FONT -> false
CUSTOM -> true
}
/**
* Get available grades for this system
*/
fun getAvailableGrades(): List<String> = when (this) {
V_SCALE -> listOf("VB", "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10", "V11", "V12", "V13", "V14", "V15", "V16", "V17")
FONT -> listOf("3", "4A", "4B", "4C", "5A", "5B", "5C", "6A", "6A+", "6B", "6B+", "6C", "6C+", "7A", "7A+", "7B", "7B+", "7C", "7C+", "8A", "8A+", "8B", "8B+", "8C", "8C+")
YDS -> listOf("5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "5.6", "5.7", "5.8", "5.9", "5.10a", "5.10b", "5.10c", "5.10d", "5.11a", "5.11b", "5.11c", "5.11d", "5.12a", "5.12b", "5.12c", "5.12d", "5.13a", "5.13b", "5.13c", "5.13d", "5.14a", "5.14b", "5.14c", "5.14d", "5.15a", "5.15b", "5.15c", "5.15d")
CUSTOM -> emptyList()
}
companion object {
/**
* Get all difficulty systems based on type
*/
fun getSystemsForClimbType(climbType: ClimbType): List<DifficultySystem> = when (climbType) {
ClimbType.BOULDER -> entries.filter { it.isBoulderingSystem() }
ClimbType.ROPE -> entries.filter { it.isRopeSystem() }
}
}
}
@Serializable
data class DifficultyGrade(
val system: DifficultySystem,
val grade: String,
val numericValue: Int // For comparison and analytics
)
val numericValue: Int
) {
/**
* Compare this grade with another grade of the same system
* Returns negative if this grade is easier, positive if harder, 0 if equal
*/
fun compareTo(other: DifficultyGrade): Int {
if (system != other.system) return 0
return when (system) {
DifficultySystem.V_SCALE -> compareVScaleGrades(grade, other.grade)
DifficultySystem.FONT -> compareFontGrades(grade, other.grade)
DifficultySystem.YDS -> compareYDSGrades(grade, other.grade)
DifficultySystem.CUSTOM -> grade.compareTo(other.grade)
}
}
private fun compareVScaleGrades(grade1: String, grade2: String): Int {
// Handle VB (easiest) specially
if (grade1 == "VB" && grade2 != "VB") return -1
if (grade2 == "VB" && grade1 != "VB") return 1
if (grade1 == "VB" && grade2 == "VB") return 0
// Extract numeric values for V grades
val num1 = grade1.removePrefix("V").toIntOrNull() ?: 0
val num2 = grade2.removePrefix("V").toIntOrNull() ?: 0
return num1.compareTo(num2)
}
private fun compareFontGrades(grade1: String, grade2: String): Int {
// Simple string comparison for Font grades
return grade1.compareTo(grade2)
}
private fun compareYDSGrades(grade1: String, grade2: String): Int {
// Simple string comparison for YDS grades
return grade1.compareTo(grade2)
}
}

View File

@@ -13,10 +13,10 @@ data class Gym(
val name: String,
val location: String? = null,
val supportedClimbTypes: List<ClimbType>,
val difficultySystems: List<DifficultySystem>, // What systems this gym uses
val customDifficultyGrades: List<String> = emptyList(), // For gyms using colors/custom names
val difficultySystems: List<DifficultySystem>,
val customDifficultyGrades: List<String> = emptyList(),
val notes: String? = null,
val createdAt: String, // ISO string format for serialization
val createdAt: String,
val updatedAt: String
) {
companion object {

View File

@@ -28,12 +28,12 @@ data class Problem(
val description: String? = null,
val climbType: ClimbType,
val difficulty: DifficultyGrade,
val setter: String? = null, // Route setter name
val tags: List<String> = emptyList(), // e.g., "overhang", "slab", "crimpy"
val location: String? = null, // Wall section, area in gym
val imagePaths: List<String> = emptyList(), // Local file paths to photos
val isActive: Boolean = true, // Whether the problem is still up
val dateSet: String? = null, // When the problem was set
val setter: String? = null,
val tags: List<String> = emptyList(),
val location: String? = null,
val imagePaths: List<String> = emptyList(),
val isActive: Boolean = true,
val dateSet: String? = null,
val notes: String? = null,
val createdAt: String,
val updatedAt: String

View File

@@ -1,50 +0,0 @@
package com.atridad.openclimb.data.model
import kotlinx.serialization.Serializable
import java.time.LocalDateTime
@Serializable
data class ProblemProgress(
val problemId: String,
val totalAttempts: Int,
val successfulAttempts: Int,
val firstAttemptDate: String,
val lastAttemptDate: String,
val bestResult: AttemptResult,
val averageAttempts: Double,
val successRate: Double,
val personalBest: String? = null, // Highest hold or completion details
val notes: String? = null
)
@Serializable
data class SessionSummary(
val sessionId: String,
val date: String,
val totalAttempts: Int,
val successfulAttempts: Int,
val uniqueProblems: Int,
val avgDifficulty: Double,
val maxDifficulty: DifficultyGrade,
val climbTypes: List<ClimbType>,
val duration: Long?, // in minutes
val notes: String? = null
)
@Serializable
data class ClimbingStats(
val totalSessions: Int,
val totalAttempts: Int,
val totalSuccesses: Int,
val overallSuccessRate: Double,
val uniqueProblemsAttempted: Int,
val uniqueProblemsCompleted: Int,
val averageSessionDuration: Double, // in minutes
val favoriteGym: String?,
val mostAttemptedDifficulty: DifficultyGrade?,
val currentStreak: Int, // consecutive sessions
val longestStreak: Int,
val firstClimbDate: String?,
val lastClimbDate: String?,
val improvementTrend: String? = null // "improving", "stable", "declining"
)

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.os.Environment
import com.atridad.openclimb.data.database.OpenClimbDatabase
import com.atridad.openclimb.data.model.*
import com.atridad.openclimb.utils.ImageUtils
import com.atridad.openclimb.utils.ZipExportImportUtils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
@@ -14,7 +13,7 @@ import java.io.File
import java.time.LocalDateTime
class ClimbRepository(
private val database: OpenClimbDatabase,
database: OpenClimbDatabase,
private val context: Context
) {
private val gymDao = database.gymDao()
@@ -40,7 +39,6 @@ class ClimbRepository(
fun getAllProblems(): Flow<List<Problem>> = problemDao.getAllProblems()
suspend fun getProblemById(id: String): Problem? = problemDao.getProblemById(id)
fun getProblemsByGym(gymId: String): Flow<List<Problem>> = problemDao.getProblemsByGym(gymId)
fun getActiveProblems(): Flow<List<Problem>> = problemDao.getActiveProblems()
suspend fun insertProblem(problem: Problem) = problemDao.insertProblem(problem)
suspend fun updateProblem(problem: Problem) = problemDao.updateProblem(problem)
suspend fun deleteProblem(problem: Problem) = problemDao.deleteProblem(problem)
@@ -50,17 +48,14 @@ class ClimbRepository(
fun getAllSessions(): Flow<List<ClimbSession>> = sessionDao.getAllSessions()
suspend fun getSessionById(id: String): ClimbSession? = sessionDao.getSessionById(id)
fun getSessionsByGym(gymId: String): Flow<List<ClimbSession>> = sessionDao.getSessionsByGym(gymId)
fun getRecentSessions(limit: Int = 10): Flow<List<ClimbSession>> = sessionDao.getRecentSessions(limit)
suspend fun getActiveSession(): ClimbSession? = sessionDao.getActiveSession()
fun getActiveSessionFlow(): Flow<ClimbSession?> = sessionDao.getActiveSessionFlow()
fun getSessionsByStatus(status: SessionStatus): Flow<List<ClimbSession>> = sessionDao.getSessionsByStatus(status)
suspend fun insertSession(session: ClimbSession) = sessionDao.insertSession(session)
suspend fun updateSession(session: ClimbSession) = sessionDao.updateSession(session)
suspend fun deleteSession(session: ClimbSession) = sessionDao.deleteSession(session)
// Attempt operations
fun getAllAttempts(): Flow<List<Attempt>> = attemptDao.getAllAttempts()
suspend fun getAttemptById(id: String): Attempt? = attemptDao.getAttemptById(id)
fun getAttemptsBySession(sessionId: String): Flow<List<Attempt>> = attemptDao.getAttemptsBySession(sessionId)
fun getAttemptsByProblem(problemId: String): Flow<List<Attempt>> = attemptDao.getAttemptsByProblem(problemId)
suspend fun insertAttempt(attempt: Attempt) = attemptDao.insertAttempt(attempt)
@@ -69,181 +64,148 @@ class ClimbRepository(
// JSON Export functionality
suspend fun exportAllDataToJson(directory: File? = null): File {
val exportDir = directory ?: File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "OpenClimb")
if (!exportDir.exists()) {
exportDir.mkdirs()
}
val timestamp = LocalDateTime.now().toString().replace(":", "-").replace(".", "-")
val exportFile = File(exportDir, "openclimb_export_$timestamp.json")
val allGyms = gymDao.getAllGyms().first()
val allProblems = problemDao.getAllProblems().first()
val allSessions = sessionDao.getAllSessions().first()
val allAttempts = attemptDao.getAllAttempts().first()
val exportData = ClimbDataExport(
exportedAt = LocalDateTime.now().toString(),
gyms = allGyms,
problems = allProblems,
sessions = allSessions,
attempts = allAttempts
)
val jsonString = json.encodeToString(exportData)
exportFile.writeText(jsonString)
return exportFile
}
suspend fun exportAllDataToUri(context: Context, uri: android.net.Uri) {
val gyms = gymDao.getAllGyms().first()
val problems = problemDao.getAllProblems().first()
val sessions = sessionDao.getAllSessions().first()
val attempts = attemptDao.getAllAttempts().first()
val exportData = ClimbDataExport(
exportedAt = LocalDateTime.now().toString(),
gyms = gyms,
problems = problems,
sessions = sessions,
attempts = attempts
)
val jsonString = json.encodeToString(exportData)
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
outputStream.write(jsonString.toByteArray())
} ?: throw Exception("Could not open output stream")
}
suspend fun importDataFromJson(file: File) {
try {
val jsonContent = file.readText()
val importData = json.decodeFromString<ClimbDataExport>(jsonContent)
// Import gyms (replace if exists due to primary key constraint)
importData.gyms.forEach { gym ->
try {
gymDao.insertGym(gym)
} catch (e: Exception) {
// If insertion fails due to primary key conflict, update instead
gymDao.updateGym(gym)
}
}
// Import problems
importData.problems.forEach { problem ->
try {
problemDao.insertProblem(problem)
} catch (e: Exception) {
problemDao.updateProblem(problem)
}
}
// Import sessions
importData.sessions.forEach { session ->
try {
sessionDao.insertSession(session)
} catch (e: Exception) {
sessionDao.updateSession(session)
}
}
// Import attempts
importData.attempts.forEach { attempt ->
try {
attemptDao.insertAttempt(attempt)
} catch (e: Exception) {
attemptDao.updateAttempt(attempt)
}
}
} catch (e: Exception) {
throw Exception("Failed to import data: ${e.message}")
}
}
// ZIP Export functionality with images
// ZIP Export with images - Single format for reliability
suspend fun exportAllDataToZip(directory: File? = null): File {
val allGyms = gymDao.getAllGyms().first()
val allProblems = problemDao.getAllProblems().first()
val allSessions = sessionDao.getAllSessions().first()
val allAttempts = attemptDao.getAllAttempts().first()
val exportData = ClimbDataExport(
exportedAt = LocalDateTime.now().toString(),
gyms = allGyms,
problems = allProblems,
sessions = allSessions,
attempts = allAttempts
)
// Collect all referenced image paths
val referencedImagePaths = allProblems.flatMap { it.imagePaths }.toSet()
return ZipExportImportUtils.createExportZip(
context = context,
exportData = exportData,
referencedImagePaths = referencedImagePaths,
directory = directory
)
try {
// Collect all data with proper error handling
val allGyms = gymDao.getAllGyms().first()
val allProblems = problemDao.getAllProblems().first()
val allSessions = sessionDao.getAllSessions().first()
val allAttempts = attemptDao.getAllAttempts().first()
// Validate data integrity before export
validateDataIntegrity(allGyms, allProblems, allSessions, allAttempts)
val exportData = ClimbDataExport(
exportedAt = LocalDateTime.now().toString(),
version = "1.0",
gyms = allGyms,
problems = allProblems,
sessions = allSessions,
attempts = allAttempts
)
// Collect all referenced image paths and validate they exist
val referencedImagePaths = allProblems.flatMap { it.imagePaths }.toSet()
val validImagePaths = referencedImagePaths.filter { imagePath ->
try {
val imageFile = com.atridad.openclimb.utils.ImageUtils.getImageFile(context, imagePath)
imageFile.exists() && imageFile.length() > 0
} catch (e: Exception) {
false
}
}.toSet()
// Log any missing images for debugging
val missingImages = referencedImagePaths - validImagePaths
if (missingImages.isNotEmpty()) {
android.util.Log.w("ClimbRepository", "Some referenced images are missing: $missingImages")
}
return ZipExportImportUtils.createExportZip(
context = context,
exportData = exportData,
referencedImagePaths = validImagePaths,
directory = directory
)
} catch (e: Exception) {
throw Exception("Export failed: ${e.message}")
}
}
suspend fun exportAllDataToZipUri(context: Context, uri: android.net.Uri) {
val gyms = gymDao.getAllGyms().first()
val problems = problemDao.getAllProblems().first()
val sessions = sessionDao.getAllSessions().first()
val attempts = attemptDao.getAllAttempts().first()
val exportData = ClimbDataExport(
exportedAt = LocalDateTime.now().toString(),
gyms = gyms,
problems = problems,
sessions = sessions,
attempts = attempts
)
// Collect all referenced image paths
val referencedImagePaths = problems.flatMap { it.imagePaths }.toSet()
ZipExportImportUtils.createExportZipToUri(
context = context,
uri = uri,
exportData = exportData,
referencedImagePaths = referencedImagePaths
)
try {
// Collect all data with proper error handling
val allGyms = gymDao.getAllGyms().first()
val allProblems = problemDao.getAllProblems().first()
val allSessions = sessionDao.getAllSessions().first()
val allAttempts = attemptDao.getAllAttempts().first()
// Validate data integrity before export
validateDataIntegrity(allGyms, allProblems, allSessions, allAttempts)
val exportData = ClimbDataExport(
exportedAt = LocalDateTime.now().toString(),
version = "1.0",
gyms = allGyms,
problems = allProblems,
sessions = allSessions,
attempts = allAttempts
)
// Collect all referenced image paths and validate they exist
val referencedImagePaths = allProblems.flatMap { it.imagePaths }.toSet()
val validImagePaths = referencedImagePaths.filter { imagePath ->
try {
val imageFile = com.atridad.openclimb.utils.ImageUtils.getImageFile(context, imagePath)
imageFile.exists() && imageFile.length() > 0
} catch (e: Exception) {
false
}
}.toSet()
ZipExportImportUtils.createExportZipToUri(
context = context,
uri = uri,
exportData = exportData,
referencedImagePaths = validImagePaths
)
} catch (e: Exception) {
throw Exception("Export failed: ${e.message}")
}
}
suspend fun importDataFromZip(file: File) {
try {
val importResult = ZipExportImportUtils.extractImportZip(context, file)
val importData = json.decodeFromString<ClimbDataExport>(importResult.jsonContent)
// Validate the ZIP file
if (!file.exists() || file.length() == 0L) {
throw Exception("Invalid ZIP file: file is empty or doesn't exist")
}
// Update problem image paths with the new imported paths
// Extract and validate the ZIP contents
val importResult = ZipExportImportUtils.extractImportZip(context, file)
// Validate JSON content
if (importResult.jsonContent.isBlank()) {
throw Exception("Invalid ZIP file: no data.json found or empty content")
}
// Parse and validate the data structure
val importData = try {
json.decodeFromString<ClimbDataExport>(importResult.jsonContent)
} catch (e: Exception) {
throw Exception("Invalid data format: ${e.message}")
}
// Validate data integrity
validateImportData(importData)
// Clear existing data to avoid conflicts
attemptDao.deleteAllAttempts()
sessionDao.deleteAllSessions()
problemDao.deleteAllProblems()
gymDao.deleteAllGyms()
// Import gyms first (problems depend on gyms)
importData.gyms.forEach { gym ->
try {
gymDao.insertGym(gym)
} catch (e: Exception) {
throw Exception("Failed to import gym ${gym.name}: ${e.message}")
}
}
// Import problems with updated image paths
val updatedProblems = ZipExportImportUtils.updateProblemImagePaths(
importData.problems,
importResult.importedImagePaths
)
// Import gyms (replace if exists due to primary key constraint)
importData.gyms.forEach { gym ->
try {
gymDao.insertGym(gym)
} catch (e: Exception) {
// If insertion fails due to primary key conflict, update instead
gymDao.updateGym(gym)
}
}
// Import problems with updated image paths
updatedProblems.forEach { problem ->
try {
problemDao.insertProblem(problem)
} catch (e: Exception) {
problemDao.updateProblem(problem)
throw Exception("Failed to import problem ${problem.name}: ${e.message}")
}
}
@@ -252,21 +214,100 @@ class ClimbRepository(
try {
sessionDao.insertSession(session)
} catch (e: Exception) {
sessionDao.updateSession(session)
throw Exception("Failed to import session: ${e.message}")
}
}
// Import attempts
// Import attempts last (depends on problems and sessions)
importData.attempts.forEach { attempt ->
try {
attemptDao.insertAttempt(attempt)
} catch (e: Exception) {
attemptDao.updateAttempt(attempt)
throw Exception("Failed to import attempt: ${e.message}")
}
}
} catch (e: Exception) {
throw Exception("Failed to import data: ${e.message}")
throw Exception("Import failed: ${e.message}")
}
}
private fun validateDataIntegrity(
gyms: List<Gym>,
problems: List<Problem>,
sessions: List<ClimbSession>,
attempts: List<Attempt>
) {
// Validate that all problems reference valid gyms
val gymIds = gyms.map { it.id }.toSet()
val invalidProblems = problems.filter { it.gymId !in gymIds }
if (invalidProblems.isNotEmpty()) {
throw Exception("Data integrity error: ${invalidProblems.size} problems reference non-existent gyms")
}
// Validate that all sessions reference valid gyms
val invalidSessions = sessions.filter { it.gymId !in gymIds }
if (invalidSessions.isNotEmpty()) {
throw Exception("Data integrity error: ${invalidSessions.size} sessions reference non-existent gyms")
}
// Validate that all attempts reference valid problems and sessions
val problemIds = problems.map { it.id }.toSet()
val sessionIds = sessions.map { it.id }.toSet()
val invalidAttempts = attempts.filter {
it.problemId !in problemIds || it.sessionId !in sessionIds
}
if (invalidAttempts.isNotEmpty()) {
throw Exception("Data integrity error: ${invalidAttempts.size} attempts reference non-existent problems or sessions")
}
}
private fun validateImportData(importData: ClimbDataExport) {
if (importData.gyms.isEmpty()) {
throw Exception("Import data is invalid: no gyms found")
}
if (importData.version.isBlank()) {
throw Exception("Import data is invalid: no version information")
}
// Check for reasonable data sizes to prevent malicious imports
if (importData.gyms.size > 1000 ||
importData.problems.size > 10000 ||
importData.sessions.size > 10000 ||
importData.attempts.size > 100000) {
throw Exception("Import data is too large: possible corruption or malicious file")
}
}
suspend fun resetAllData() {
try {
// Clear all data from database
attemptDao.deleteAllAttempts()
sessionDao.deleteAllSessions()
problemDao.deleteAllProblems()
gymDao.deleteAllGyms()
// Clear all images from storage
clearAllImages()
} catch (e: Exception) {
throw Exception("Reset failed: ${e.message}")
}
}
private fun clearAllImages() {
try {
// Get the images directory
val imagesDir = File(context.filesDir, "images")
if (imagesDir.exists() && imagesDir.isDirectory) {
val deletedCount = imagesDir.listFiles()?.size ?: 0
imagesDir.deleteRecursively()
android.util.Log.i("ClimbRepository", "Cleared $deletedCount image files")
}
} catch (e: Exception) {
android.util.Log.w("ClimbRepository", "Failed to clear some images: ${e.message}")
}
}
}
@@ -274,6 +315,7 @@ class ClimbRepository(
@kotlinx.serialization.Serializable
data class ClimbDataExport(
val exportedAt: String,
val version: String = "1.0",
val gyms: List<Gym>,
val problems: List<Problem>,
val sessions: List<ClimbSession>,

View File

@@ -6,7 +6,6 @@ import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.atridad.openclimb.MainActivity
@@ -16,15 +15,17 @@ import com.atridad.openclimb.data.repository.ClimbRepository
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import kotlinx.coroutines.runBlocking
class SessionTrackingService : Service() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var notificationJob: Job? = null
private var monitoringJob: Job? = null
private lateinit var repository: ClimbRepository
private lateinit var notificationManager: NotificationManager
companion object {
const val NOTIFICATION_ID = 1001
@@ -40,9 +41,10 @@ class SessionTrackingService : Service() {
}
}
fun createStopIntent(context: Context): Intent {
fun createStopIntent(context: Context, sessionId: String): Intent {
return Intent(context, SessionTrackingService::class.java).apply {
action = ACTION_STOP_SESSION
putExtra(EXTRA_SESSION_ID, sessionId)
}
}
}
@@ -52,6 +54,7 @@ class SessionTrackingService : Service() {
val database = OpenClimbDatabase.getDatabase(this)
repository = ClimbRepository(database, this)
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannel()
}
@@ -65,87 +68,185 @@ class SessionTrackingService : Service() {
}
}
ACTION_STOP_SESSION -> {
stopSessionTracking()
val sessionId = intent.getStringExtra(EXTRA_SESSION_ID)
serviceScope.launch {
try {
val targetSession = when {
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() }
repository.updateSession(completed)
}
} finally {
stopSessionTracking()
}
}
}
}
return START_STICKY
return START_REDELIVER_INTENT
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
}
override fun onBind(intent: Intent?): IBinder? = null
private fun startSessionTracking(sessionId: String) {
notificationJob?.cancel()
monitoringJob?.cancel()
try {
createAndShowNotification(sessionId)
} catch (e: Exception) {
e.printStackTrace()
}
notificationJob = serviceScope.launch {
while (isActive) {
updateNotification(sessionId)
delay(1000)
try {
if (!isNotificationActive()) {
delay(1000L)
createAndShowNotification(sessionId)
}
while (isActive) {
delay(5000L)
updateNotification(sessionId)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
monitoringJob = serviceScope.launch {
try {
while (isActive) {
delay(10000L)
if (!isNotificationActive()) {
updateNotification(sessionId)
}
val session = repository.getSessionById(sessionId)
if (session == null || session.status != com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
stopSessionTracking()
break
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun stopSessionTracking() {
notificationJob?.cancel()
monitoringJob?.cancel()
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
}
private fun isNotificationActive(): Boolean {
return try {
val activeNotifications = notificationManager.activeNotifications
activeNotifications.any { it.id == NOTIFICATION_ID }
} catch (e: Exception) {
false
}
}
private suspend fun updateNotification(sessionId: String) {
try {
val session = repository.getSessionById(sessionId)
createAndShowNotification(sessionId)
} catch (e: Exception) {
e.printStackTrace()
try {
delay(10000L)
createAndShowNotification(sessionId)
} catch (retryException: Exception) {
retryException.printStackTrace()
stopSessionTracking()
}
}
}
private fun createAndShowNotification(sessionId: String) {
try {
val session = runBlocking {
repository.getSessionById(sessionId)
}
if (session == null || session.status != com.atridad.openclimb.data.model.SessionStatus.ACTIVE) {
stopSessionTracking()
return
}
val gym = repository.getGymById(session.gymId)
val attempts = repository.getAttemptsBySession(sessionId).firstOrNull() ?: emptyList()
val gym = runBlocking {
repository.getGymById(session.gymId)
}
val attempts = runBlocking {
repository.getAttemptsBySession(sessionId).firstOrNull() ?: emptyList()
}
val duration = session.startTime?.let { startTime ->
try {
val start = LocalDateTime.parse(startTime)
val now = LocalDateTime.now()
val minutes = ChronoUnit.MINUTES.between(start, now)
val hours = minutes / 60
val remainingMinutes = minutes % 60
val totalSeconds = ChronoUnit.SECONDS.between(start, now)
val hours = totalSeconds / 3600
val minutes = (totalSeconds % 3600) / 60
val seconds = totalSeconds % 60
when {
hours > 0 -> "${hours}h ${remainingMinutes}m"
remainingMinutes > 0 -> "${remainingMinutes}m"
else -> "< 1m"
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
minutes > 0 -> "${minutes}m ${seconds}s"
else -> "${totalSeconds}s"
}
} catch (e: Exception) {
} catch (_: Exception) {
"Active"
}
} ?: "Active"
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("OpenClimb Session Active")
.setContentTitle("Climbing Session Active")
.setContentText("${gym?.name ?: "Gym"}$duration${attempts.size} attempts")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setSmallIcon(R.drawable.ic_mountains)
.setOngoing(true)
.setAutoCancel(false)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(createOpenAppIntent())
.addAction(
R.drawable.ic_launcher_foreground,
R.drawable.ic_mountains,
"Open Session",
createOpenAppIntent()
)
.addAction(
R.drawable.ic_launcher_foreground,
android.R.drawable.ic_menu_close_clear_cancel,
"End Session",
createStopIntent()
createStopPendingIntent(sessionId)
)
.build()
startForeground(NOTIFICATION_ID, notification)
notificationManager.notify(NOTIFICATION_ID, notification)
} catch (e: Exception) {
// Handle errors gracefully
stopSessionTracking()
e.printStackTrace()
throw e
}
}
private fun createOpenAppIntent(): PendingIntent {
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
action = "OPEN_SESSION"
}
return PendingIntent.getActivity(
this,
@@ -155,8 +256,8 @@ class SessionTrackingService : Service() {
)
}
private fun createStopIntent(): PendingIntent {
val intent = createStopIntent(this)
private fun createStopPendingIntent(sessionId: String): PendingIntent {
val intent = createStopIntent(this, sessionId)
return PendingIntent.getService(
this,
1,
@@ -166,24 +267,26 @@ class SessionTrackingService : Service() {
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Session Tracking",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Shows active climbing session information"
setShowBadge(false)
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
val channel = NotificationChannel(
CHANNEL_ID,
"Session Tracking",
NotificationManager.IMPORTANCE_DEFAULT
).apply {
description = "Shows active climbing session information"
setShowBadge(false)
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
enableLights(false)
enableVibration(false)
setSound(null, null)
}
notificationManager.createNotificationChannel(channel)
}
override fun onDestroy() {
super.onDestroy()
notificationJob?.cancel()
monitoringJob?.cancel()
serviceScope.cancel()
}
}

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