Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
870278f240
|
|||
|
4eef77bd3b
|
|||
|
2d957db948
|
|||
| 22bed6a961 | |||
|
b443c18a19
|
|||
|
89f1e350b3
|
|||
|
0f976f685f
|
|||
|
c07186a7df
|
|||
|
15a5e217a5
|
|||
|
b86ab591fe
|
|||
|
70c85d159e
|
|||
|
d6c5e937df
|
|||
|
829bbbff7a
|
|||
|
e1ebf412bd
|
|||
|
5c133b655e
|
|||
|
cc1edbc65c
|
|||
|
ca770b9db3
|
|||
|
7edb7c8191
|
|||
|
1ca6b33882
|
|||
|
bd6b5cc652
|
35
.gitignore
vendored
Normal file
35
.gitignore
vendored
Normal 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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"); }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
#Fri Aug 15 14:37:16 MDT 2025
|
|
||||||
gradle.version=8.11.1
|
|
||||||
Binary file not shown.
@@ -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.
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<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>
|
</project>
|
||||||
@@ -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
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
|
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"java.configuration.updateBuildConfiguration": "disabled"
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ This is a FOSS Android app meant to help climbers track their sessions, routes/p
|
|||||||
You have two options:
|
You have two options:
|
||||||
|
|
||||||
1. Download the latest APK from the Released page
|
1. Download the latest APK from the Released page
|
||||||
2. Use <a href="">Obtainium</a>
|
2. Use <a href="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">Obtainium</a>
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,21 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.atridad.openclimb"
|
namespace = "com.atridad.openclimb"
|
||||||
compileSdk = 35
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.atridad.openclimb"
|
applicationId = "com.atridad.openclimb"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 35
|
targetSdk = 36
|
||||||
versionCode = 2
|
versionCode = 13
|
||||||
versionName = "0.2.0"
|
versionName = "0.5.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = true
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
@@ -30,12 +30,20 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure consistent JVM toolchain across all tasks
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(17))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
@@ -79,8 +87,14 @@ dependencies {
|
|||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
testImplementation(libs.mockk)
|
||||||
|
testImplementation(libs.kotlinx.coroutines.test)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
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(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"artifactType": {
|
|
||||||
"type": "APK",
|
|
||||||
"kind": "Directory"
|
|
||||||
},
|
|
||||||
"applicationId": "com.atridad.openclimb",
|
|
||||||
"variantName": "release",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "SINGLE",
|
|
||||||
"filters": [],
|
|
||||||
"attributes": [],
|
|
||||||
"versionCode": 2,
|
|
||||||
"versionName": "0.2.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
|
|
||||||
}
|
|
||||||
@@ -9,12 +9,10 @@ import java.time.LocalDateTime
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class AttemptResult {
|
enum class AttemptResult {
|
||||||
SUCCESS, // Completed the problem/route
|
SUCCESS,
|
||||||
FALL, // Fell but made progress
|
FALL,
|
||||||
NO_PROGRESS, // Couldn't make meaningful progress
|
NO_PROGRESS,
|
||||||
FLASH, // Completed on first try
|
FLASH,
|
||||||
REDPOINT, // Completed after previous attempts
|
|
||||||
ONSIGHT // Completed on first try without prior knowledge
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ data class ClimbSession(
|
|||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
val id: String,
|
val id: String,
|
||||||
val gymId: String,
|
val gymId: String,
|
||||||
val date: String, // ISO date string
|
val date: String,
|
||||||
val startTime: String? = null, // When session was started
|
val startTime: String? = null,
|
||||||
val endTime: String? = null, // When session was completed
|
val endTime: String? = null,
|
||||||
val duration: Long? = null, // Duration in minutes (calculated when completed)
|
val duration: Long? = null,
|
||||||
val status: SessionStatus = SessionStatus.ACTIVE,
|
val status: SessionStatus = SessionStatus.ACTIVE,
|
||||||
val notes: String? = null,
|
val notes: String? = null,
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
@@ -65,7 +65,7 @@ data class ClimbSession(
|
|||||||
val start = LocalDateTime.parse(startTime)
|
val start = LocalDateTime.parse(startTime)
|
||||||
val end = LocalDateTime.parse(endTime)
|
val end = LocalDateTime.parse(endTime)
|
||||||
java.time.Duration.between(start, end).toMinutes()
|
java.time.Duration.between(start, end).toMinutes()
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|||||||
@@ -5,5 +5,13 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
enum class ClimbType {
|
enum class ClimbType {
|
||||||
ROPE,
|
ROPE,
|
||||||
BOULDER
|
BOULDER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display name
|
||||||
|
*/
|
||||||
|
fun getDisplayName(): String = when (this) {
|
||||||
|
ROPE -> "Rope"
|
||||||
|
BOULDER -> "Bouldering"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,105 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class DifficultySystem {
|
enum class DifficultySystem {
|
||||||
// Rope climbing systems
|
// Bouldering
|
||||||
YDS, // Yosemite Decimal System (5.1 - 5.15d)
|
V_SCALE, // V-Scale (VB - V17)
|
||||||
FRENCH, // French system (3 - 9c+)
|
FONT, // Fontainebleau (3 - 8C+)
|
||||||
UIAA, // UIAA system (I - XII+)
|
|
||||||
BRITISH, // British system (Mod - E11)
|
|
||||||
|
|
||||||
// Bouldering systems
|
// Rope
|
||||||
V_SCALE, // V-Scale (VB - V17)
|
YDS, // Yosemite Decimal System (5.0 - 5.15d)
|
||||||
FONT, // Fontainebleau (3 - 9A+)
|
|
||||||
|
|
||||||
// Custom system for gyms that use their own colors/naming
|
// Custom difficulty systems
|
||||||
CUSTOM
|
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
|
@Serializable
|
||||||
data class DifficultyGrade(
|
data class DifficultyGrade(
|
||||||
val system: DifficultySystem,
|
val system: DifficultySystem,
|
||||||
val grade: String,
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ data class Gym(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val location: String? = null,
|
val location: String? = null,
|
||||||
val supportedClimbTypes: List<ClimbType>,
|
val supportedClimbTypes: List<ClimbType>,
|
||||||
val difficultySystems: List<DifficultySystem>, // What systems this gym uses
|
val difficultySystems: List<DifficultySystem>,
|
||||||
val customDifficultyGrades: List<String> = emptyList(), // For gyms using colors/custom names
|
val customDifficultyGrades: List<String> = emptyList(),
|
||||||
val notes: String? = null,
|
val notes: String? = null,
|
||||||
val createdAt: String, // ISO string format for serialization
|
val createdAt: String,
|
||||||
val updatedAt: String
|
val updatedAt: String
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ data class Problem(
|
|||||||
val description: String? = null,
|
val description: String? = null,
|
||||||
val climbType: ClimbType,
|
val climbType: ClimbType,
|
||||||
val difficulty: DifficultyGrade,
|
val difficulty: DifficultyGrade,
|
||||||
val setter: String? = null, // Route setter name
|
val setter: String? = null,
|
||||||
val tags: List<String> = emptyList(), // e.g., "overhang", "slab", "crimpy"
|
val tags: List<String> = emptyList(),
|
||||||
val location: String? = null, // Wall section, area in gym
|
val location: String? = null,
|
||||||
val imagePaths: List<String> = emptyList(), // Local file paths to photos
|
val imagePaths: List<String> = emptyList(),
|
||||||
val isActive: Boolean = true, // Whether the problem is still up
|
val isActive: Boolean = true,
|
||||||
val dateSet: String? = null, // When the problem was set
|
val dateSet: String? = null,
|
||||||
val notes: String? = null,
|
val notes: String? = null,
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
val updatedAt: String
|
val updatedAt: String
|
||||||
|
|||||||
@@ -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"
|
|
||||||
)
|
|
||||||
@@ -4,7 +4,6 @@ import android.content.Context
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import com.atridad.openclimb.data.database.OpenClimbDatabase
|
import com.atridad.openclimb.data.database.OpenClimbDatabase
|
||||||
import com.atridad.openclimb.data.model.*
|
import com.atridad.openclimb.data.model.*
|
||||||
import com.atridad.openclimb.utils.ImageUtils
|
|
||||||
import com.atridad.openclimb.utils.ZipExportImportUtils
|
import com.atridad.openclimb.utils.ZipExportImportUtils
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@@ -14,7 +13,7 @@ import java.io.File
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
class ClimbRepository(
|
class ClimbRepository(
|
||||||
private val database: OpenClimbDatabase,
|
database: OpenClimbDatabase,
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) {
|
) {
|
||||||
private val gymDao = database.gymDao()
|
private val gymDao = database.gymDao()
|
||||||
@@ -40,7 +39,6 @@ class ClimbRepository(
|
|||||||
fun getAllProblems(): Flow<List<Problem>> = problemDao.getAllProblems()
|
fun getAllProblems(): Flow<List<Problem>> = problemDao.getAllProblems()
|
||||||
suspend fun getProblemById(id: String): Problem? = problemDao.getProblemById(id)
|
suspend fun getProblemById(id: String): Problem? = problemDao.getProblemById(id)
|
||||||
fun getProblemsByGym(gymId: String): Flow<List<Problem>> = problemDao.getProblemsByGym(gymId)
|
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 insertProblem(problem: Problem) = problemDao.insertProblem(problem)
|
||||||
suspend fun updateProblem(problem: Problem) = problemDao.updateProblem(problem)
|
suspend fun updateProblem(problem: Problem) = problemDao.updateProblem(problem)
|
||||||
suspend fun deleteProblem(problem: Problem) = problemDao.deleteProblem(problem)
|
suspend fun deleteProblem(problem: Problem) = problemDao.deleteProblem(problem)
|
||||||
@@ -50,17 +48,14 @@ class ClimbRepository(
|
|||||||
fun getAllSessions(): Flow<List<ClimbSession>> = sessionDao.getAllSessions()
|
fun getAllSessions(): Flow<List<ClimbSession>> = sessionDao.getAllSessions()
|
||||||
suspend fun getSessionById(id: String): ClimbSession? = sessionDao.getSessionById(id)
|
suspend fun getSessionById(id: String): ClimbSession? = sessionDao.getSessionById(id)
|
||||||
fun getSessionsByGym(gymId: String): Flow<List<ClimbSession>> = sessionDao.getSessionsByGym(gymId)
|
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()
|
suspend fun getActiveSession(): ClimbSession? = sessionDao.getActiveSession()
|
||||||
fun getActiveSessionFlow(): Flow<ClimbSession?> = sessionDao.getActiveSessionFlow()
|
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 insertSession(session: ClimbSession) = sessionDao.insertSession(session)
|
||||||
suspend fun updateSession(session: ClimbSession) = sessionDao.updateSession(session)
|
suspend fun updateSession(session: ClimbSession) = sessionDao.updateSession(session)
|
||||||
suspend fun deleteSession(session: ClimbSession) = sessionDao.deleteSession(session)
|
suspend fun deleteSession(session: ClimbSession) = sessionDao.deleteSession(session)
|
||||||
|
|
||||||
// Attempt operations
|
// Attempt operations
|
||||||
fun getAllAttempts(): Flow<List<Attempt>> = attemptDao.getAllAttempts()
|
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 getAttemptsBySession(sessionId: String): Flow<List<Attempt>> = attemptDao.getAttemptsBySession(sessionId)
|
||||||
fun getAttemptsByProblem(problemId: String): Flow<List<Attempt>> = attemptDao.getAttemptsByProblem(problemId)
|
fun getAttemptsByProblem(problemId: String): Flow<List<Attempt>> = attemptDao.getAttemptsByProblem(problemId)
|
||||||
suspend fun insertAttempt(attempt: Attempt) = attemptDao.insertAttempt(attempt)
|
suspend fun insertAttempt(attempt: Attempt) = attemptDao.insertAttempt(attempt)
|
||||||
@@ -69,7 +64,7 @@ class ClimbRepository(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// JSON Export functionality
|
// JSON Export
|
||||||
suspend fun exportAllDataToJson(directory: File? = null): File {
|
suspend fun exportAllDataToJson(directory: File? = null): File {
|
||||||
val exportDir = directory ?: File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "OpenClimb")
|
val exportDir = directory ?: File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "OpenClimb")
|
||||||
if (!exportDir.exists()) {
|
if (!exportDir.exists()) {
|
||||||
@@ -124,12 +119,12 @@ class ClimbRepository(
|
|||||||
val jsonContent = file.readText()
|
val jsonContent = file.readText()
|
||||||
val importData = json.decodeFromString<ClimbDataExport>(jsonContent)
|
val importData = json.decodeFromString<ClimbDataExport>(jsonContent)
|
||||||
|
|
||||||
// Import gyms (replace if exists due to primary key constraint)
|
// Import gyms
|
||||||
importData.gyms.forEach { gym ->
|
importData.gyms.forEach { gym ->
|
||||||
try {
|
try {
|
||||||
gymDao.insertGym(gym)
|
gymDao.insertGym(gym)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// If insertion fails due to primary key conflict, update instead
|
// If insertion fails, update instead
|
||||||
gymDao.updateGym(gym)
|
gymDao.updateGym(gym)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +133,7 @@ class ClimbRepository(
|
|||||||
importData.problems.forEach { problem ->
|
importData.problems.forEach { problem ->
|
||||||
try {
|
try {
|
||||||
problemDao.insertProblem(problem)
|
problemDao.insertProblem(problem)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
problemDao.updateProblem(problem)
|
problemDao.updateProblem(problem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +142,7 @@ class ClimbRepository(
|
|||||||
importData.sessions.forEach { session ->
|
importData.sessions.forEach { session ->
|
||||||
try {
|
try {
|
||||||
sessionDao.insertSession(session)
|
sessionDao.insertSession(session)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
sessionDao.updateSession(session)
|
sessionDao.updateSession(session)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +151,7 @@ class ClimbRepository(
|
|||||||
importData.attempts.forEach { attempt ->
|
importData.attempts.forEach { attempt ->
|
||||||
try {
|
try {
|
||||||
attemptDao.insertAttempt(attempt)
|
attemptDao.insertAttempt(attempt)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
attemptDao.updateAttempt(attempt)
|
attemptDao.updateAttempt(attempt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +161,7 @@ class ClimbRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ZIP Export functionality with images
|
// ZIP Export with images
|
||||||
suspend fun exportAllDataToZip(directory: File? = null): File {
|
suspend fun exportAllDataToZip(directory: File? = null): File {
|
||||||
val allGyms = gymDao.getAllGyms().first()
|
val allGyms = gymDao.getAllGyms().first()
|
||||||
val allProblems = problemDao.getAllProblems().first()
|
val allProblems = problemDao.getAllProblems().first()
|
||||||
@@ -206,7 +201,7 @@ class ClimbRepository(
|
|||||||
attempts = attempts
|
attempts = attempts
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collect all referenced image paths
|
// Collect all image paths
|
||||||
val referencedImagePaths = problems.flatMap { it.imagePaths }.toSet()
|
val referencedImagePaths = problems.flatMap { it.imagePaths }.toSet()
|
||||||
|
|
||||||
ZipExportImportUtils.createExportZipToUri(
|
ZipExportImportUtils.createExportZipToUri(
|
||||||
@@ -228,12 +223,12 @@ class ClimbRepository(
|
|||||||
importResult.importedImagePaths
|
importResult.importedImagePaths
|
||||||
)
|
)
|
||||||
|
|
||||||
// Import gyms (replace if exists due to primary key constraint)
|
// Import gyms
|
||||||
importData.gyms.forEach { gym ->
|
importData.gyms.forEach { gym ->
|
||||||
try {
|
try {
|
||||||
gymDao.insertGym(gym)
|
gymDao.insertGym(gym)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// If insertion fails due to primary key conflict, update instead
|
// If insertion fails update instead
|
||||||
gymDao.updateGym(gym)
|
gymDao.updateGym(gym)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.app.PendingIntent
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.atridad.openclimb.MainActivity
|
import com.atridad.openclimb.MainActivity
|
||||||
@@ -16,7 +15,6 @@ import com.atridad.openclimb.data.repository.ClimbRepository
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
class SessionTrackingService : Service() {
|
class SessionTrackingService : Service() {
|
||||||
@@ -40,9 +38,10 @@ class SessionTrackingService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createStopIntent(context: Context): Intent {
|
fun createStopIntent(context: Context, sessionId: String): Intent {
|
||||||
return Intent(context, SessionTrackingService::class.java).apply {
|
return Intent(context, SessionTrackingService::class.java).apply {
|
||||||
action = ACTION_STOP_SESSION
|
action = ACTION_STOP_SESSION
|
||||||
|
putExtra(EXTRA_SESSION_ID, sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +64,21 @@ class SessionTrackingService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTION_STOP_SESSION -> {
|
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_STICKY
|
||||||
@@ -76,9 +89,13 @@ class SessionTrackingService : Service() {
|
|||||||
private fun startSessionTracking(sessionId: String) {
|
private fun startSessionTracking(sessionId: String) {
|
||||||
notificationJob?.cancel()
|
notificationJob?.cancel()
|
||||||
notificationJob = serviceScope.launch {
|
notificationJob = serviceScope.launch {
|
||||||
|
// Initial notification update
|
||||||
|
updateNotification(sessionId)
|
||||||
|
|
||||||
|
// Then update every second
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
|
delay(1000L)
|
||||||
updateNotification(sessionId)
|
updateNotification(sessionId)
|
||||||
delay(1000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,40 +121,46 @@ class SessionTrackingService : Service() {
|
|||||||
try {
|
try {
|
||||||
val start = LocalDateTime.parse(startTime)
|
val start = LocalDateTime.parse(startTime)
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val minutes = ChronoUnit.MINUTES.between(start, now)
|
val totalSeconds = ChronoUnit.SECONDS.between(start, now)
|
||||||
val hours = minutes / 60
|
val hours = totalSeconds / 3600
|
||||||
val remainingMinutes = minutes % 60
|
val minutes = (totalSeconds % 3600) / 60
|
||||||
|
val seconds = totalSeconds % 60
|
||||||
|
|
||||||
when {
|
when {
|
||||||
hours > 0 -> "${hours}h ${remainingMinutes}m"
|
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
|
||||||
remainingMinutes > 0 -> "${remainingMinutes}m"
|
minutes > 0 -> "${minutes}m ${seconds}s"
|
||||||
else -> "< 1m"
|
else -> "${totalSeconds}s"
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
"Active"
|
"Active"
|
||||||
}
|
}
|
||||||
} ?: "Active"
|
} ?: "Active"
|
||||||
|
|
||||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setContentTitle("OpenClimb Session Active")
|
.setContentTitle("Climbing Session Active")
|
||||||
.setContentText("${gym?.name ?: "Gym"} • $duration • ${attempts.size} attempts")
|
.setContentText("${gym?.name ?: "Gym"} • $duration • ${attempts.size} attempts")
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
.setSmallIcon(R.drawable.ic_mountains)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setContentIntent(createOpenAppIntent())
|
.setContentIntent(createOpenAppIntent())
|
||||||
.addAction(
|
.addAction(
|
||||||
R.drawable.ic_launcher_foreground,
|
R.drawable.ic_mountains,
|
||||||
"Open Session",
|
"Open Session",
|
||||||
createOpenAppIntent()
|
createOpenAppIntent()
|
||||||
)
|
)
|
||||||
.addAction(
|
.addAction(
|
||||||
R.drawable.ic_launcher_foreground,
|
android.R.drawable.ic_menu_close_clear_cancel,
|
||||||
"End Session",
|
"End Session",
|
||||||
createStopIntent()
|
createStopPendingIntent(sessionId)
|
||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
// Force update the notification every second
|
||||||
|
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, notification)
|
||||||
|
|
||||||
startForeground(NOTIFICATION_ID, notification)
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
// Handle errors gracefully
|
// Handle errors gracefully
|
||||||
stopSessionTracking()
|
stopSessionTracking()
|
||||||
}
|
}
|
||||||
@@ -155,8 +178,8 @@ class SessionTrackingService : Service() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createStopIntent(): PendingIntent {
|
private fun createStopPendingIntent(sessionId: String): PendingIntent {
|
||||||
val intent = createStopIntent(this)
|
val intent = createStopIntent(this, sessionId)
|
||||||
return PendingIntent.getService(
|
return PendingIntent.getService(
|
||||||
this,
|
this,
|
||||||
1,
|
1,
|
||||||
@@ -166,19 +189,17 @@ class SessionTrackingService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
val channel = NotificationChannel(
|
||||||
val channel = NotificationChannel(
|
CHANNEL_ID,
|
||||||
CHANNEL_ID,
|
"Session Tracking",
|
||||||
"Session Tracking",
|
NotificationManager.IMPORTANCE_LOW
|
||||||
NotificationManager.IMPORTANCE_LOW
|
).apply {
|
||||||
).apply {
|
description = "Shows active climbing session information"
|
||||||
description = "Shows active climbing session information"
|
setShowBadge(false)
|
||||||
setShowBadge(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
notificationManager.createNotificationChannel(channel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.atridad.openclimb.ui
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -27,8 +28,6 @@ import com.atridad.openclimb.ui.viewmodel.ClimbViewModelFactory
|
|||||||
fun OpenClimbApp() {
|
fun OpenClimbApp() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val currentBackStackEntry by navController.currentBackStackEntryAsState()
|
|
||||||
val currentDestination = currentBackStackEntry?.destination?.route
|
|
||||||
|
|
||||||
val database = remember { OpenClimbDatabase.getDatabase(context) }
|
val database = remember { OpenClimbDatabase.getDatabase(context) }
|
||||||
val repository = remember { ClimbRepository(database, context) }
|
val repository = remember { ClimbRepository(database, context) }
|
||||||
@@ -69,7 +68,7 @@ fun OpenClimbApp() {
|
|||||||
LaunchedEffect(gyms, activeSession) {
|
LaunchedEffect(gyms, activeSession) {
|
||||||
fabConfig = if (gyms.isNotEmpty() && activeSession == null) {
|
fabConfig = if (gyms.isNotEmpty() && activeSession == null) {
|
||||||
FabConfig(
|
FabConfig(
|
||||||
icon = Icons.Default.Add,
|
icon = Icons.Default.PlayArrow,
|
||||||
contentDescription = "Start Session",
|
contentDescription = "Start Session",
|
||||||
onClick = {
|
onClick = {
|
||||||
if (gyms.size == 1) {
|
if (gyms.size == 1) {
|
||||||
@@ -87,9 +86,6 @@ fun OpenClimbApp() {
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateToSessionDetail = { sessionId ->
|
onNavigateToSessionDetail = { sessionId ->
|
||||||
navController.navigate(Screen.SessionDetail(sessionId))
|
navController.navigate(Screen.SessionDetail(sessionId))
|
||||||
},
|
|
||||||
onNavigateToAddSession = { gymId ->
|
|
||||||
navController.navigate(Screen.AddEditSession(gymId = gymId))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -113,9 +109,6 @@ fun OpenClimbApp() {
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateToProblemDetail = { problemId ->
|
onNavigateToProblemDetail = { problemId ->
|
||||||
navController.navigate(Screen.ProblemDetail(problemId))
|
navController.navigate(Screen.ProblemDetail(problemId))
|
||||||
},
|
|
||||||
onNavigateToAddProblem = { gymId ->
|
|
||||||
navController.navigate(Screen.AddEditProblem(gymId = gymId))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -141,9 +134,6 @@ fun OpenClimbApp() {
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateToGymDetail = { gymId ->
|
onNavigateToGymDetail = { gymId ->
|
||||||
navController.navigate(Screen.GymDetail(gymId))
|
navController.navigate(Screen.GymDetail(gymId))
|
||||||
},
|
|
||||||
onNavigateToAddGym = {
|
|
||||||
navController.navigate(Screen.AddEditGym())
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -158,18 +148,17 @@ fun OpenClimbApp() {
|
|||||||
// Detail screens
|
// Detail screens
|
||||||
composable<Screen.SessionDetail> { backStackEntry ->
|
composable<Screen.SessionDetail> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.SessionDetail>()
|
val args = backStackEntry.toRoute<Screen.SessionDetail>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
SessionDetailScreen(
|
SessionDetailScreen(
|
||||||
sessionId = args.sessionId,
|
sessionId = args.sessionId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() }
|
||||||
onNavigateToEdit = { sessionId ->
|
|
||||||
navController.navigate(Screen.AddEditSession(sessionId = sessionId))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
composable<Screen.ProblemDetail> { backStackEntry ->
|
composable<Screen.ProblemDetail> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.ProblemDetail>()
|
val args = backStackEntry.toRoute<Screen.ProblemDetail>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
ProblemDetailScreen(
|
ProblemDetailScreen(
|
||||||
problemId = args.problemId,
|
problemId = args.problemId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -182,6 +171,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.GymDetail> { backStackEntry ->
|
composable<Screen.GymDetail> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.GymDetail>()
|
val args = backStackEntry.toRoute<Screen.GymDetail>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
GymDetailScreen(
|
GymDetailScreen(
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -195,6 +185,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.AddEditGym> { backStackEntry ->
|
composable<Screen.AddEditGym> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.AddEditGym>()
|
val args = backStackEntry.toRoute<Screen.AddEditGym>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
AddEditGymScreen(
|
AddEditGymScreen(
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
@@ -204,6 +195,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.AddEditProblem> { backStackEntry ->
|
composable<Screen.AddEditProblem> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.AddEditProblem>()
|
val args = backStackEntry.toRoute<Screen.AddEditProblem>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
AddEditProblemScreen(
|
AddEditProblemScreen(
|
||||||
problemId = args.problemId,
|
problemId = args.problemId,
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
@@ -214,6 +206,7 @@ fun OpenClimbApp() {
|
|||||||
|
|
||||||
composable<Screen.AddEditSession> { backStackEntry ->
|
composable<Screen.AddEditSession> { backStackEntry ->
|
||||||
val args = backStackEntry.toRoute<Screen.AddEditSession>()
|
val args = backStackEntry.toRoute<Screen.AddEditSession>()
|
||||||
|
LaunchedEffect(Unit) { fabConfig = null }
|
||||||
AddEditSessionScreen(
|
AddEditSessionScreen(
|
||||||
sessionId = args.sessionId,
|
sessionId = args.sessionId,
|
||||||
gymId = args.gymId,
|
gymId = args.gymId,
|
||||||
@@ -247,17 +240,15 @@ fun OpenClimbBottomNavigation(navController: NavHostController) {
|
|||||||
selected = isSelected,
|
selected = isSelected,
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate(item.screen) {
|
navController.navigate(item.screen) {
|
||||||
// Pop up to the start destination of the graph to
|
// Clear the entire back stack and go to the selected tab's root screen
|
||||||
// avoid building up a large stack of destinations
|
popUpTo(0) {
|
||||||
// on the back stack as users select items
|
inclusive = true
|
||||||
popUpTo(Screen.Sessions) {
|
|
||||||
saveState = true
|
|
||||||
}
|
}
|
||||||
// Avoid multiple copies of the same destination when
|
// Avoid multiple copies of the same destination when
|
||||||
// reselecting the same item
|
// reselecting the same item
|
||||||
launchSingleTop = true
|
launchSingleTop = true
|
||||||
// Restore state when reselecting a previously selected item
|
// Don't restore state - always start fresh when switching tabs
|
||||||
restoreState = true
|
restoreState = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ package com.atridad.openclimb.ui.components
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.rounded.Close
|
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.atridad.openclimb.data.model.ClimbSession
|
import com.atridad.openclimb.data.model.ClimbSession
|
||||||
import com.atridad.openclimb.data.model.Gym
|
import com.atridad.openclimb.data.model.Gym
|
||||||
|
import com.atridad.openclimb.ui.theme.CustomIcons
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
|
|
||||||
@@ -26,6 +25,16 @@ fun ActiveSessionBanner(
|
|||||||
onEndSession: () -> Unit
|
onEndSession: () -> Unit
|
||||||
) {
|
) {
|
||||||
if (activeSession != null) {
|
if (activeSession != null) {
|
||||||
|
// Add a timer that updates every second for real-time duration counting
|
||||||
|
var currentTime by remember { mutableStateOf(LocalDateTime.now()) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
delay(1000) // Update every second
|
||||||
|
currentTime = LocalDateTime.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -69,7 +78,7 @@ fun ActiveSessionBanner(
|
|||||||
)
|
)
|
||||||
|
|
||||||
activeSession.startTime?.let { startTime ->
|
activeSession.startTime?.let { startTime ->
|
||||||
val duration = calculateDuration(startTime)
|
val duration = calculateDuration(startTime, currentTime)
|
||||||
Text(
|
Text(
|
||||||
text = duration,
|
text = duration,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
@@ -86,7 +95,7 @@ fun ActiveSessionBanner(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Close,
|
imageVector = CustomIcons.Stop(MaterialTheme.colorScheme.onError),
|
||||||
contentDescription = "End session"
|
contentDescription = "End session"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -95,94 +104,20 @@ fun ActiveSessionBanner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
private fun calculateDuration(startTimeString: String, currentTime: LocalDateTime): String {
|
||||||
fun StartSessionButton(
|
|
||||||
gyms: List<Gym>,
|
|
||||||
onStartSession: (String) -> Unit
|
|
||||||
) {
|
|
||||||
var showGymSelection by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
if (gyms.isEmpty()) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "No gyms available",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "Add a gym first to start a session",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button(
|
|
||||||
onClick = { showGymSelection = true },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.PlayArrow, contentDescription = null)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text("Start Session")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showGymSelection) {
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = { showGymSelection = false },
|
|
||||||
title = { Text("Select Gym") },
|
|
||||||
text = {
|
|
||||||
Column {
|
|
||||||
gyms.forEach { gym ->
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
onStartSession(gym.id)
|
|
||||||
showGymSelection = false
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = gym.name,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { showGymSelection = false }) {
|
|
||||||
Text("Cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateDuration(startTimeString: String): String {
|
|
||||||
return try {
|
return try {
|
||||||
val startTime = LocalDateTime.parse(startTimeString)
|
val startTime = LocalDateTime.parse(startTimeString)
|
||||||
val now = LocalDateTime.now()
|
val totalSeconds = ChronoUnit.SECONDS.between(startTime, currentTime)
|
||||||
val minutes = ChronoUnit.MINUTES.between(startTime, now)
|
val hours = totalSeconds / 3600
|
||||||
val hours = minutes / 60
|
val minutes = (totalSeconds % 3600) / 60
|
||||||
val remainingMinutes = minutes % 60
|
val seconds = totalSeconds % 60
|
||||||
|
|
||||||
when {
|
when {
|
||||||
hours > 0 -> "${hours}h ${remainingMinutes}m"
|
hours > 0 -> "${hours}h ${minutes}m ${seconds}s"
|
||||||
remainingMinutes > 0 -> "${remainingMinutes}m"
|
minutes > 0 -> "${minutes}m ${seconds}s"
|
||||||
else -> "< 1m"
|
else -> "${totalSeconds}s"
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
"Active"
|
"Active"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ fun FullscreenImageViewer(
|
|||||||
LaunchedEffect(pagerState.currentPage) {
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
thumbnailListState.animateScrollToItem(
|
thumbnailListState.animateScrollToItem(
|
||||||
index = pagerState.currentPage,
|
index = pagerState.currentPage,
|
||||||
scrollOffset = -200 // Center the item
|
scrollOffset = -200
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.atridad.openclimb.ui.components
|
package com.atridad.openclimb.ui.components
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -20,7 +19,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.atridad.openclimb.utils.ImageUtils
|
import com.atridad.openclimb.utils.ImageUtils
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ImagePicker(
|
fun ImagePicker(
|
||||||
@@ -41,7 +39,7 @@ fun ImagePicker(
|
|||||||
val remainingSlots = maxImages - currentCount
|
val remainingSlots = maxImages - currentCount
|
||||||
val urisToProcess = uris.take(remainingSlots)
|
val urisToProcess = uris.take(remainingSlots)
|
||||||
|
|
||||||
// Process each selected image
|
// Process images
|
||||||
val newImagePaths = mutableListOf<String>()
|
val newImagePaths = mutableListOf<String>()
|
||||||
urisToProcess.forEach { uri ->
|
urisToProcess.forEach { uri ->
|
||||||
val imagePath = ImageUtils.saveImageFromUri(context, uri)
|
val imagePath = ImageUtils.saveImageFromUri(context, uri)
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.foundation.selection.selectableGroup
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -17,21 +16,12 @@ import androidx.compose.ui.semantics.Role
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import com.atridad.openclimb.data.model.*
|
import com.atridad.openclimb.data.model.*
|
||||||
import com.atridad.openclimb.ui.components.ImagePicker
|
import com.atridad.openclimb.ui.components.ImagePicker
|
||||||
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
|
import com.atridad.openclimb.ui.viewmodel.ClimbViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
// Data class for attempt input
|
|
||||||
data class AttemptInput(
|
|
||||||
val problemId: String,
|
|
||||||
val result: AttemptResult,
|
|
||||||
val highestHold: String = "",
|
|
||||||
val notes: String = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AddEditGymScreen(
|
fun AddEditGymScreen(
|
||||||
@@ -47,32 +37,56 @@ fun AddEditGymScreen(
|
|||||||
|
|
||||||
val isEditing = gymId != null
|
val isEditing = gymId != null
|
||||||
|
|
||||||
|
// Calculate available difficulty systems based on selected climb types
|
||||||
|
val availableDifficultySystems = if (selectedClimbTypes.isEmpty()) {
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
selectedClimbTypes.flatMap { climbType ->
|
||||||
|
DifficultySystem.getSystemsForClimbType(climbType)
|
||||||
|
}.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset selected difficulty systems when available systems change
|
||||||
|
LaunchedEffect(availableDifficultySystems) {
|
||||||
|
selectedDifficultySystems = selectedDifficultySystems.filter { it in availableDifficultySystems }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load existing gym data for editing
|
||||||
|
LaunchedEffect(gymId) {
|
||||||
|
if (gymId != null) {
|
||||||
|
val existingGym = viewModel.getGymById(gymId).first()
|
||||||
|
existingGym?.let { gym ->
|
||||||
|
name = gym.name
|
||||||
|
location = gym.location ?: ""
|
||||||
|
notes = gym.notes ?: ""
|
||||||
|
selectedClimbTypes = gym.supportedClimbTypes.toSet()
|
||||||
|
selectedDifficultySystems = gym.difficultySystems.toSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(if (isEditing) "Edit Gym" else "Add Gym") },
|
title = { Text(if (isEditing) "Edit Gym" else "Add Gym") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val gym = if (isEditing) {
|
val gym = Gym.create(name, location, selectedClimbTypes.toList(), selectedDifficultySystems.toList(), notes = notes)
|
||||||
Gym.create(name, location, selectedClimbTypes.toList(), selectedDifficultySystems.toList(), notes = notes)
|
|
||||||
} else {
|
|
||||||
Gym.create(name, location, selectedClimbTypes.toList(), selectedDifficultySystems.toList(), notes = notes)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
viewModel.updateGym(gym)
|
viewModel.updateGym(gym.copy(id = gymId))
|
||||||
} else {
|
} else {
|
||||||
viewModel.addGym(gym)
|
viewModel.addGym(gym)
|
||||||
}
|
}
|
||||||
onNavigateBack()
|
onNavigateBack()
|
||||||
},
|
},
|
||||||
enabled = name.isNotBlank() && selectedClimbTypes.isNotEmpty()
|
enabled = name.isNotBlank() && selectedClimbTypes.isNotEmpty() && selectedDifficultySystems.isNotEmpty()
|
||||||
) {
|
) {
|
||||||
Text("Save")
|
Text("Save")
|
||||||
}
|
}
|
||||||
@@ -142,7 +156,7 @@ fun AddEditGymScreen(
|
|||||||
onCheckedChange = null
|
onCheckedChange = null
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Text(climbType.name.lowercase().replaceFirstChar { it.uppercase() })
|
Text(climbType.getDisplayName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,29 +177,38 @@ fun AddEditGymScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
DifficultySystem.entries.forEach { system ->
|
if (selectedClimbTypes.isEmpty()) {
|
||||||
Row(
|
Text(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
text = "Select climb types first to see available difficulty systems",
|
||||||
modifier = Modifier
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
.fillMaxWidth()
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
.selectable(
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
selected = system in selectedDifficultySystems,
|
)
|
||||||
onClick = {
|
} else {
|
||||||
selectedDifficultySystems = if (system in selectedDifficultySystems) {
|
availableDifficultySystems.forEach { system ->
|
||||||
selectedDifficultySystems - system
|
Row(
|
||||||
} else {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
selectedDifficultySystems + system
|
modifier = Modifier
|
||||||
}
|
.fillMaxWidth()
|
||||||
},
|
.selectable(
|
||||||
role = Role.Checkbox
|
selected = system in selectedDifficultySystems,
|
||||||
|
onClick = {
|
||||||
|
selectedDifficultySystems = if (system in selectedDifficultySystems) {
|
||||||
|
selectedDifficultySystems - system
|
||||||
|
} else {
|
||||||
|
selectedDifficultySystems + system
|
||||||
|
}
|
||||||
|
},
|
||||||
|
role = Role.Checkbox
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = system in selectedDifficultySystems,
|
||||||
|
onCheckedChange = null
|
||||||
)
|
)
|
||||||
) {
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
Checkbox(
|
Text(system.getDisplayName())
|
||||||
checked = system in selectedDifficultySystems,
|
}
|
||||||
onCheckedChange = null
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(system.name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +267,7 @@ fun AddEditProblemScreen(
|
|||||||
notes = p.notes ?: ""
|
notes = p.notes ?: ""
|
||||||
isActive = p.isActive
|
isActive = p.isActive
|
||||||
imagePaths = p.imagePaths
|
imagePaths = p.imagePaths
|
||||||
|
selectedGym = gyms.find { it.id == p.gymId }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,8 +278,39 @@ fun AddEditProblemScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val availableDifficultySystems = selectedGym?.difficultySystems ?: DifficultySystem.entries.toList()
|
|
||||||
val availableClimbTypes = selectedGym?.supportedClimbTypes ?: ClimbType.entries.toList()
|
val availableClimbTypes = selectedGym?.supportedClimbTypes ?: ClimbType.entries.toList()
|
||||||
|
val availableDifficultySystems = DifficultySystem.getSystemsForClimbType(selectedClimbType).filter { system ->
|
||||||
|
selectedGym?.difficultySystems?.contains(system) != false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-select climb type if there's only one available
|
||||||
|
LaunchedEffect(availableClimbTypes) {
|
||||||
|
if (availableClimbTypes.size == 1 && selectedClimbType != availableClimbTypes.first()) {
|
||||||
|
selectedClimbType = availableClimbTypes.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-select or reset difficulty system based on climb type
|
||||||
|
LaunchedEffect(selectedClimbType, availableDifficultySystems) {
|
||||||
|
when {
|
||||||
|
// If current system is not compatible, select the first available one
|
||||||
|
selectedDifficultySystem !in availableDifficultySystems -> {
|
||||||
|
selectedDifficultySystem = availableDifficultySystems.firstOrNull() ?: DifficultySystem.CUSTOM
|
||||||
|
}
|
||||||
|
// If there's only one available system and nothing is selected, auto-select it
|
||||||
|
availableDifficultySystems.size == 1 && selectedDifficultySystem != availableDifficultySystems.first() -> {
|
||||||
|
selectedDifficultySystem = availableDifficultySystems.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset grade when difficulty system changes (unless it's a valid grade for the new system)
|
||||||
|
LaunchedEffect(selectedDifficultySystem) {
|
||||||
|
val availableGrades = selectedDifficultySystem.getAvailableGrades()
|
||||||
|
if (availableGrades.isNotEmpty() && difficultyGrade !in availableGrades) {
|
||||||
|
difficultyGrade = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -263,7 +318,7 @@ fun AddEditProblemScreen(
|
|||||||
title = { Text(if (isEditing) "Edit Problem" else "Add Problem") },
|
title = { Text(if (isEditing) "Edit Problem" else "Add Problem") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
@@ -293,7 +348,7 @@ fun AddEditProblemScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
viewModel.updateProblem(problem.copy(id = problemId!!))
|
viewModel.updateProblem(problem.copy(id = problemId))
|
||||||
} else {
|
} else {
|
||||||
viewModel.addProblem(problem)
|
viewModel.addProblem(problem)
|
||||||
}
|
}
|
||||||
@@ -437,7 +492,7 @@ fun AddEditProblemScreen(
|
|||||||
availableClimbTypes.forEach { climbType ->
|
availableClimbTypes.forEach { climbType ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
onClick = { selectedClimbType = climbType },
|
onClick = { selectedClimbType = climbType },
|
||||||
label = { Text(climbType.name.lowercase().replaceFirstChar { it.uppercase() }) },
|
label = { Text(climbType.getDisplayName()) },
|
||||||
selected = selectedClimbType == climbType
|
selected = selectedClimbType == climbType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -476,7 +531,7 @@ fun AddEditProblemScreen(
|
|||||||
items(availableDifficultySystems) { system ->
|
items(availableDifficultySystems) { system ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
onClick = { selectedDifficultySystem = system },
|
onClick = { selectedDifficultySystem = system },
|
||||||
label = { Text(system.name) },
|
label = { Text(system.getDisplayName()) },
|
||||||
selected = selectedDifficultySystem == system
|
selected = selectedDifficultySystem == system
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -484,23 +539,51 @@ fun AddEditProblemScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
OutlinedTextField(
|
if (selectedDifficultySystem == DifficultySystem.CUSTOM) {
|
||||||
value = difficultyGrade,
|
OutlinedTextField(
|
||||||
onValueChange = { difficultyGrade = it },
|
value = difficultyGrade,
|
||||||
label = { Text("Grade *") },
|
onValueChange = { difficultyGrade = it },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
label = { Text("Grade *") },
|
||||||
singleLine = true,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
placeholder = {
|
singleLine = true,
|
||||||
Text(when (selectedDifficultySystem) {
|
placeholder = { Text("Enter custom grade") }
|
||||||
DifficultySystem.V_SCALE -> "e.g., V0, V4, V10"
|
)
|
||||||
DifficultySystem.FONT -> "e.g., 3, 6A+, 8B"
|
} else {
|
||||||
DifficultySystem.YDS -> "e.g., 5.8, 5.12a"
|
var expanded by remember { mutableStateOf(false) }
|
||||||
DifficultySystem.FRENCH -> "e.g., 6a, 7c+"
|
val availableGrades = selectedDifficultySystem.getAvailableGrades()
|
||||||
DifficultySystem.CUSTOM -> "Custom grade"
|
|
||||||
else -> "Enter grade"
|
ExposedDropdownMenuBox(
|
||||||
})
|
expanded = expanded,
|
||||||
|
onExpandedChange = { expanded = !expanded },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = difficultyGrade,
|
||||||
|
onValueChange = { },
|
||||||
|
readOnly = true,
|
||||||
|
label = { Text("Grade *") },
|
||||||
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||||
|
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
||||||
|
modifier = Modifier
|
||||||
|
.menuAnchor()
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
availableGrades.forEach { grade ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(grade) },
|
||||||
|
onClick = {
|
||||||
|
difficultyGrade = grade
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -552,7 +635,7 @@ fun AddEditProblemScreen(
|
|||||||
label = { Text("Tags (Optional)") },
|
label = { Text("Tags (Optional)") },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
placeholder = { Text("e.g., overhang, crimpy, dynamic (comma-separated)") }
|
placeholder = { Text("e.g., crimpy, dynamic (comma-separated)") }
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -605,17 +688,25 @@ fun AddEditSessionScreen(
|
|||||||
) {
|
) {
|
||||||
val isEditing = sessionId != null
|
val isEditing = sessionId != null
|
||||||
val gyms by viewModel.gyms.collectAsState()
|
val gyms by viewModel.gyms.collectAsState()
|
||||||
val problems by viewModel.problems.collectAsState()
|
|
||||||
|
|
||||||
// Session form state
|
// Session form state
|
||||||
var selectedGym by remember { mutableStateOf<Gym?>(gymId?.let { id -> gyms.find { it.id == id } }) }
|
var selectedGym by remember { mutableStateOf<Gym?>(gymId?.let { id -> gyms.find { it.id == id } }) }
|
||||||
var sessionDate by remember { mutableStateOf(LocalDateTime.now().toLocalDate().toString()) }
|
var sessionDate by remember { mutableStateOf(LocalDateTime.now().toLocalDate().toString()) }
|
||||||
var duration by remember { mutableStateOf("") }
|
var duration by remember { mutableStateOf("") }
|
||||||
var sessionNotes by remember { mutableStateOf("") }
|
var sessionNotes by remember { mutableStateOf("") }
|
||||||
|
|
||||||
// Attempt tracking state
|
// Load existing session data for editing
|
||||||
var attempts by remember { mutableStateOf(listOf<AttemptInput>()) }
|
LaunchedEffect(sessionId) {
|
||||||
var showAddAttemptDialog by remember { mutableStateOf(false) }
|
if (sessionId != null) {
|
||||||
|
val existingSession = viewModel.getSessionById(sessionId).first()
|
||||||
|
existingSession?.let { session ->
|
||||||
|
selectedGym = gyms.find { it.id == session.gymId }
|
||||||
|
sessionDate = session.date.split("T")[0] // Extract date part
|
||||||
|
duration = session.duration?.toString() ?: ""
|
||||||
|
sessionNotes = session.notes ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(gymId, gyms) {
|
LaunchedEffect(gymId, gyms) {
|
||||||
if (gymId != null && selectedGym == null) {
|
if (gymId != null && selectedGym == null) {
|
||||||
@@ -629,7 +720,7 @@ fun AddEditSessionScreen(
|
|||||||
title = { Text(if (isEditing) "Edit Session" else "Add Session") },
|
title = { Text(if (isEditing) "Edit Session" else "Add Session") },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
@@ -642,20 +733,9 @@ fun AddEditSessionScreen(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
viewModel.updateSession(session.copy(id = sessionId!!))
|
viewModel.updateSession(session.copy(id = sessionId))
|
||||||
} else {
|
} else {
|
||||||
viewModel.addSession(session)
|
viewModel.addSession(session)
|
||||||
|
|
||||||
attempts.forEach { attemptInput ->
|
|
||||||
val attempt = Attempt.create(
|
|
||||||
sessionId = session.id,
|
|
||||||
problemId = attemptInput.problemId,
|
|
||||||
result = attemptInput.result,
|
|
||||||
highestHold = attemptInput.highestHold.ifBlank { null },
|
|
||||||
notes = attemptInput.notes.ifBlank { null }
|
|
||||||
)
|
|
||||||
viewModel.addAttempt(attempt)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onNavigateBack()
|
onNavigateBack()
|
||||||
}
|
}
|
||||||
@@ -666,15 +746,6 @@ fun AddEditSessionScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
if (selectedGym != null) {
|
|
||||||
FloatingActionButton(
|
|
||||||
onClick = { showAddAttemptDialog = true }
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Add, contentDescription = "Add Attempt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -770,286 +841,9 @@ fun AddEditSessionScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempts Section
|
|
||||||
item {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Attempts (${attempts.size})",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempts.isEmpty()) {
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(
|
|
||||||
text = "No attempts recorded yet. Add an attempt to track your progress.",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempts List
|
|
||||||
items(attempts.size) { index ->
|
|
||||||
val attempt = attempts[index]
|
|
||||||
val problem = problems.find { it.id == attempt.problemId }
|
|
||||||
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
Text(
|
|
||||||
text = problem?.name ?: "Unknown Problem",
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
problem?.difficulty?.let { difficulty ->
|
|
||||||
Text(
|
|
||||||
text = "${difficulty.system.name}: ${difficulty.grade}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "Result: ${attempt.result.name.lowercase().replaceFirstChar { it.uppercase() }}",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = when (attempt.result) {
|
|
||||||
AttemptResult.SUCCESS, AttemptResult.FLASH,
|
|
||||||
AttemptResult.REDPOINT, AttemptResult.ONSIGHT -> MaterialTheme.colorScheme.primary
|
|
||||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (attempt.highestHold.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
text = "Highest hold: ${attempt.highestHold}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempt.notes.isNotBlank()) {
|
|
||||||
Text(
|
|
||||||
text = attempt.notes,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = {
|
|
||||||
attempts = attempts.toMutableList().apply { removeAt(index) }
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(Icons.Default.Delete, contentDescription = "Remove attempt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showAddAttemptDialog && selectedGym != null) {
|
|
||||||
AddAttemptDialog(
|
|
||||||
problems = problems.filter { it.gymId == selectedGym!!.id && it.isActive },
|
|
||||||
onDismiss = { showAddAttemptDialog = false },
|
|
||||||
onAddAttempt = { attemptInput ->
|
|
||||||
attempts = attempts + attemptInput
|
|
||||||
showAddAttemptDialog = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun AddAttemptDialog(
|
|
||||||
problems: List<Problem>,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onAddAttempt: (AttemptInput) -> Unit
|
|
||||||
) {
|
|
||||||
var selectedProblem by remember { mutableStateOf<Problem?>(null) }
|
|
||||||
var selectedResult by remember { mutableStateOf(AttemptResult.FALL) }
|
|
||||||
var highestHold by remember { mutableStateOf("") }
|
|
||||||
var notes by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
Dialog(onDismissRequest = onDismiss) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(24.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Add Attempt",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
// Problem Selection
|
|
||||||
Text(
|
|
||||||
text = "Problem",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
|
|
||||||
if (problems.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = "No active problems in this gym. Add some problems first.",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.height(120.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
items(problems) { problem ->
|
|
||||||
Card(
|
|
||||||
onClick = { selectedProblem = problem },
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = if (selectedProblem?.id == problem.id)
|
|
||||||
MaterialTheme.colorScheme.primaryContainer
|
|
||||||
else MaterialTheme.colorScheme.surface
|
|
||||||
),
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(12.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = problem.name ?: "Unnamed Problem",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "${problem.difficulty.system.name}: ${problem.difficulty.grade}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result Selection
|
|
||||||
Text(
|
|
||||||
text = "Result",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(modifier = Modifier.selectableGroup()) {
|
|
||||||
AttemptResult.entries.forEach { result ->
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.selectable(
|
|
||||||
selected = selectedResult == result,
|
|
||||||
onClick = { selectedResult = result },
|
|
||||||
role = Role.RadioButton
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
RadioButton(
|
|
||||||
selected = selectedResult == result,
|
|
||||||
onClick = null
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = result.name.lowercase().replaceFirstChar { it.uppercase() },
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Highest Hold
|
|
||||||
OutlinedTextField(
|
|
||||||
value = highestHold,
|
|
||||||
onValueChange = { highestHold = it },
|
|
||||||
label = { Text("Highest Hold (Optional)") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
singleLine = true,
|
|
||||||
placeholder = { Text("e.g., 'jugs near the top', 'crux move'") }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Notes
|
|
||||||
OutlinedTextField(
|
|
||||||
value = notes,
|
|
||||||
onValueChange = { notes = it },
|
|
||||||
label = { Text("Notes (Optional)") },
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
minLines = 2,
|
|
||||||
placeholder = { Text("e.g., 'need to work on heel hooks', 'pumped out'") }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
TextButton(
|
|
||||||
onClick = onDismiss,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Text("Cancel")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
selectedProblem?.let { problem ->
|
|
||||||
onAddAttempt(
|
|
||||||
AttemptInput(
|
|
||||||
problemId = problem.id,
|
|
||||||
result = selectedResult,
|
|
||||||
highestHold = highestHold,
|
|
||||||
notes = notes
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = selectedProblem != null,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Text("Add Attempt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -93,41 +93,6 @@ fun AnalyticsScreen(
|
|||||||
val recentSessions = sessions.take(5)
|
val recentSessions = sessions.take(5)
|
||||||
RecentActivityCard(recentSessions = recentSessions.size)
|
RecentActivityCard(recentSessions = recentSessions.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
item {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Progress Charts",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "Detailed charts and analytics coming soon!",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = "📊",
|
|
||||||
style = MaterialTheme.typography.displaySmall
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user