diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 000000000..454c4b74b --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,119 @@ +name: Android CI + +on: + push: + branches: + - master + - test/** + pull_request: + types: [opened, synchronize, reopened] + +env: + ANDROID_API: 36.1 + ANDROID_BUILD_TOOLS: 36.1.0 + ADB_INSTALL_TIMEOUT: 5 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + + - name: Cache Gradle Build Cache + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches/build-cache-* + key: ${{ runner.os }}-gradle-build-cache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-gradle-build-cache- + + - name: Restore cache for Git LFS Objects + id: lfs-cache + uses: actions/cache@v4 + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.git/lfs/objects/**') }} + restore-keys: ${{ runner.os }}-lfs- + + - name: Git LFS Pull + run: git lfs pull + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: Install Android SDK components + run: | + sdkmanager "tools" + sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" + sdkmanager "platforms;android-${ANDROID_API}" + sdkmanager "extras;android;m2repository" + sdkmanager "extras;google;m2repository" + sdkmanager "extras;google;google_play_services" + + - name: Prepare builddir + run: | + # secrets are only available for collabrators, other will build with OsmDroid (but a few more options than F-Droid still) + echo "${{ secrets.MAPBOX }}" > $GITHUB_WORKSPACE/mapbox.properties + echo "${{ secrets.DROPBOX }}" > $GITHUB_WORKSPACE/dropbox.properties + echo "${{ secrets.RUNALYZE }}" > $GITHUB_WORKSPACE/runalyze.properties + chmod +x gradlew + + - name: Build bundle + run: ./gradlew :app:bundleLatestRelease :wear:bundleRelease + + - name: Upload build logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-bundle + # possibly include: app/build/outputs/bundle/latestRelease/ wear/build/outputs/bundle/release/ + path: | + **/build/outputs/logs/ + + - name: Test + run: ./gradlew testBuildTypesUnitTest :app:testLatestDebugUnitTest testDebugUnitTest + + - name: Upload test logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-logs + path: | + **/build/reports/tests/ + + - name: Lint latest release + run: ./gradlew :app:lintLatestRelease :wear:lintRelease :hrdevice:lintRelease :common:lintRelease + + - name: Upload lint logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: lint-logs + path: | + **/build/reports/lint-results-release.html + **/build/reports/lint-results-latestRelease.html + + - name: Build F-Droid + run: | + rm $GITHUB_WORKSPACE/mapbox.properties + # For special build steps, see https://gitlab.com/fdroid/fdroiddata.git metadata/org.runnerup.free.yml + rm -rf wear ANT-Android-SDKs $GITHUB_WORKSPACE/dropbox.properties $GITHUB_WORKSPACE/runalyze.properties + sed -i -e '/play-services/d' -e '/com.mapbox.maps/d' -e '/api.mapbox.com/d' app/build.gradle + sed -i -e '/wearable/d' common/build.gradle + ./gradlew clean :app:assembleLatestRelease + + - name: Upload F-Droid logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: fdroid-logs + path: | + build/reports/problems/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9fa069636..000000000 --- a/.travis.yml +++ /dev/null @@ -1,35 +0,0 @@ -env: - global: - - ANDROID_API=35 - - ANDROID_BUILD_TOOLS=36.0.0 - - ADB_INSTALL_TIMEOUT=5 -language: android -jdk: -- oraclejdk8 -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ -before_cache: -- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock -- rm -f $HOME/.gradle/caches/*/classAnalysis/cache.properties.lock -- rm -f $HOME/.gradle/caches/*/jarSnapshots/cache.properties.lock -- rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -android: - components: - - tools #latest for "builtin" sdk tools (24.4.1 in Android-25) - #To update SDK Tools to latest, another update is required - #- tools #latest, 26.1.1 as of 2018-10-07 - #- platform-tools #latest, 28.0.1 as of 2018-10-07 - - build-tools-$ANDROID_BUILD_TOOLS - - android-$ANDROID_API - - extra-android-m2repository - - extra-google-m2repository - - extra-google-google_play_services -notifications: - email: false -script: -- ./gradlew wear:lintRelease -- ./gradlew app:lintLatestRelease -- ./gradlew app:assembleLatestRelease -- ./gradlew app:test diff --git a/app/build.gradle b/app/build.gradle index 57999fc0e..1180ba57c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,11 +6,11 @@ def getGitHash = providers.exec { android { buildToolsVersion = rootProject.ext.buildToolsVersion - namespace 'org.runnerup' + namespace = 'org.runnerup' compileOptions { - sourceCompatibility JavaVersion.toVersion("17") - targetCompatibility JavaVersion.toVersion("17") + sourceCompatibility = JavaVersion.toVersion("17") + targetCompatibility = JavaVersion.toVersion("17") } sourceSets { @@ -29,7 +29,7 @@ android { } if (rootProject.ext.noMap) { java.srcDirs += ['src/nomap'] - } else if (rootProject.ext.useMapBox) { + } else if (rootProject.ext.mapboxEnabled) { java.srcDirs += ['src/mapbox'] } else { java.srcDirs += ['src/osmdroid'] @@ -44,23 +44,13 @@ android { flavorDimensions = [ "all" ] productFlavors { latest { - dimension "all" + dimension = "all" // multidexing support, min play support - minSdk rootProject.ext.minSdk - compileSdk rootProject.ext.compileSdk - targetSdk rootProject.ext.targetSdk - versionName rootProject.ext.versionName - versionCode rootProject.ext.latestBaseVersionCode + rootProject.ext.versionCode - } - } - - splits { - abi { - // Disable, app bundles used in play - // enable rootProject.ext.allowNonFree && gradle.startParameter.taskNames.contains("assembleLatestRelease") - // relevant archs only - these are the only available anyway for newer NDK - include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' - universalApk true + minSdk = rootProject.ext.minSdk + compileSdk = rootProject.ext.compileSdk + targetSdk = rootProject.ext.targetSdk + versionName = rootProject.ext.versionName + versionCode = rootProject.ext.latestBaseVersionCode + rootProject.ext.versionCode } } @@ -74,7 +64,6 @@ android { } signingConfigs { - //noinspection GroovyMissingReturnStatement release { } } @@ -86,22 +75,30 @@ android { versionNameSuffix = "-${getGitHash}" } release { - debuggable false - applicationIdSuffix "" + debuggable = false + applicationIdSuffix = "" - minifyEnabled rootProject.ext.allowNonFree - shrinkResources rootProject.ext.allowNonFree + minifyEnabled = rootProject.ext.allowNonFree + shrinkResources = rootProject.ext.allowNonFree proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard.txt' - signingConfig signingConfigs.release + signingConfig = signingConfigs.release } } lint { - baseline file('lint-baseline.xml') - checkReleaseBuilds true - lintConfig file('lint.xml') - showAll true - //textOutput 'stdout' - textReport true + baseline = file('lint-baseline.xml') + checkReleaseBuilds = true + if (mapboxEnabled) { + lintConfig = file('lint.xml') + } else { + lintConfig = file('lint-osmdroid.xml') + } + showAll = true + //textOutput = 'stdout' + textReport = true + // Treat all warnings as errors + warningsAsErrors = true + // Halt the build if any errors are found + abortOnError = true } bundle { language { @@ -110,21 +107,21 @@ android { } } buildFeatures { - aidl true - buildConfig true + aidl = true + buildConfig = true } androidResources { - generateLocaleConfig true + generateLocaleConfig = true } } repositories { google() mavenCentral() //MapBox GraphView - maven { url "https://oss.sonatype.org/content/groups/public/" } //pebblekit - if (rootProject.ext.useMapBox) { + maven { url = "https://oss.sonatype.org/content/groups/public/" } //pebblekit + if (rootProject.ext.mapboxEnabled) { maven { - url 'https://api.mapbox.com/downloads/v2/releases/maven' + url = 'https://api.mapbox.com/downloads/v2/releases/maven' authentication { basic(BasicAuthentication) } @@ -141,18 +138,6 @@ repositories { } } -// Duplicate class kotlin.collections.jdk8 (from MapBox?) -dependencies { - constraints { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { - because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") - } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { - because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") - } - } -} - dependencies { implementation project(':common') implementation project(':hrdevice') @@ -163,28 +148,21 @@ dependencies { implementation "androidx.viewpager2:viewpager2:1.1.0" implementation "androidx.constraintlayout:constraintlayout:2.2.1" - latestImplementation "com.google.android.material:material:1.12.0" + latestImplementation "com.google.android.material:material:1.13.0" if (rootProject.ext.enableWear) { - // Build Wear separately, do not include in phone apk - // latestWearApp project(':wear') - - //noinspection GradleDependency latestImplementation "com.google.android.gms:play-services-wearable:${rootProject.ext.googlePlayServicesWearableVersion}" } - implementation "com.squareup.okhttp3:okhttp:5.1.0" + implementation "com.squareup.okhttp3:okhttp:5.3.2" latestImplementation 'com.getpebble:pebblekit:4.0.1' if (rootProject.ext.allowNonFree) { // MapBox uses telemetry, without Play there may be exceptions from mapbox (OK to ignore) latestImplementation "com.google.android.gms:play-services-location:${rootProject.ext.googlePlayServicesVersion}" } - if (rootProject.ext.useMapBox) { - //noinspection GradleDependency - latestImplementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:9.6.2' - latestImplementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v9:0.9.0' - latestImplementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v8:0.2.0' + if (rootProject.ext.mapboxEnabled) { + latestImplementation 'com.mapbox.maps:android-ndk27:11.17.1' } else { - implementation 'org.osmdroid:osmdroid-android:6.1.11' + implementation 'org.osmdroid:osmdroid-android:6.1.20' } latestImplementation 'com.jjoe64:graphview:4.2.2' @@ -202,6 +180,7 @@ allprojects { } def props = new Properties() +// For locally signed builds only, Google Play signs the bundle if (rootProject.file("release.properties").exists()) { props.load(new FileInputStream(rootProject.file("release.properties"))) @@ -215,74 +194,15 @@ if (rootProject.file("release.properties").exists()) { } android.applicationVariants.configureEach { - // Note: As a minimum extra security at least obfuscate the strings with Proguard - if (rootProject.ext.noMap) { - buildConfigField 'boolean', 'USING_OSMDROID', "false" - buildConfigField 'int', 'MAPBOX_ENABLED', "0" - buildConfigField 'String', 'MAPBOX_ACCESS_TOKEN', '""' - } else if (rootProject.ext.useMapBox) { - // https://www.mapbox.com/account/ - props.load(new FileInputStream(rootProject.file("mapbox.properties"))) - buildConfigField 'int', 'MAPBOX_ENABLED', "1" - buildConfigField 'String', 'MAPBOX_ACCESS_TOKEN', props.mapboxAccessToken - - buildConfigField 'boolean', 'USING_OSMDROID', "false" - } else { - buildConfigField 'int', 'MAPBOX_ENABLED', "0" - buildConfigField 'String', 'MAPBOX_ACCESS_TOKEN', '""' - buildConfigField 'boolean', 'USING_OSMDROID', "true" - } - - if (rootProject.file("runalyze.properties").exists()) { - // Contact Runalyze team at https://forum.runalyze.com/ - props.load(new FileInputStream(rootProject.file("runalyze.properties"))) - buildConfigField 'int', 'RUNALYZE_ENABLED', "1" - buildConfigField 'String', 'RUNALYZE_ID', props.CLIENT_ID - buildConfigField 'String', 'RUNALYZE_SECRET', props.CLIENT_SECRET - } else { - // Demo, connect to testing.runalyze.com - buildConfigField 'int', 'RUNALYZE_ENABLED', "0" - buildConfigField 'String', 'RUNALYZE_ID', '"8_2jx5jt9r39ic40ooc80c8c0884okgk0owsowg808c4csg8ko8g"' - buildConfigField 'String', 'RUNALYZE_SECRET', '"1v7d6nwe1v9c8skok44g0gc8cc04cc0wwwo8swwgckoogwsww4"' - } + buildConfigField 'boolean', 'MAPBOX_ENABLED', "${rootProject.ext.mapboxEnabled}" + buildConfigField 'String', 'MAPBOX_ACCESS_TOKEN', "\"${rootProject.ext.mapboxAccessToken}\"" + buildConfigField 'boolean', 'OSMDROID_ENABLED', "${rootProject.ext.osmdroidEnabled}" - if (rootProject.file("dropbox.properties").exists()) { - // Create an app at https://www.dropbox.com/developers/apps/ - // Dropbox API, App folder -> create app - // Enable additional users, Redirect URI:http://localhost:8080/runnerup/dropbox, Disallow implicit grant - // Set branding icon - // Create dropbox.properties with two lines: - // CLIENT_ID="replace_dropbox_id" - // CLIENT_SECRET="replace_dropbox_secret" - props.load(new FileInputStream(rootProject.file("dropbox.properties"))) - buildConfigField 'int', 'DROPBOX_ENABLED', "1" - buildConfigField 'String', 'DROPBOX_ID', props.CLIENT_ID - buildConfigField 'String', 'DROPBOX_SECRET', props.CLIENT_SECRET - } else { - buildConfigField 'int', 'DROPBOX_ENABLED', "0" - buildConfigField 'String', 'DROPBOX_ID', "null" - buildConfigField 'String', 'DROPBOX_SECRET', "null" - } -} + buildConfigField 'boolean', 'RUNALYZE_ENABLED', "${rootProject.ext.runalyzeEnabled}" + buildConfigField 'String', 'RUNALYZE_ID', "\"${rootProject.ext.runalyzeId}\"" + buildConfigField 'String', 'RUNALYZE_SECRET', "\"${rootProject.ext.runalyzeSecret}\"" -//Based on an example from https://developer.android.com/studio/build/configure-apk-splits.html -//Most comments from there removed - -// Map for the version code that gives each ABI a value (generic has lower value) -ext.abiCodes = ['arm64-v8a': 8, 'armeabi': 2, 'armeabi-v7a': 3, 'mips': 4, 'mips64': 5, 'x86': 6, 'x86_64': 7] - -import com.android.build.OutputFile - -// For each APK output variant, override versionCode with a combination of -// ext.abiCodes * 10000 + variant.versionCode. (universal gets no offset) -android.applicationVariants.configureEach { variant -> - variant.outputs.each { output -> - def baseAbiVersionCode = - project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) - - if (baseAbiVersionCode != null) { - output.versionCodeOverride = - baseAbiVersionCode * 10000 + variant.versionCode - } - } + buildConfigField 'boolean', 'DROPBOX_ENABLED', "${rootProject.ext.dropboxEnabled}" + buildConfigField 'String', 'DROPBOX_ID', "\"${rootProject.ext.dropboxId}\"" + buildConfigField 'String', 'DROPBOX_SECRET', "\"${rootProject.ext.dropboxSecret}\"" } diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index ce9768f92..9cb0b5202 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -1,5 +1,5 @@ - + @@ -52,24 +52,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/lint.xml b/app/lint.xml index 4f935c9b0..0f23394ae 100644 --- a/app/lint.xml +++ b/app/lint.xml @@ -1,29 +1,21 @@ + + + - - - - + + + - - - - - - - - - - @@ -35,12 +27,6 @@ - - - - - - @@ -48,25 +34,12 @@ - - - - - - - - - - - - - - - + - + + diff --git a/app/res/layout/account_list.xml b/app/res/layout/account_list.xml index cda247c1b..ad27c4b41 100644 --- a/app/res/layout/account_list.xml +++ b/app/res/layout/account_list.xml @@ -26,7 +26,6 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_alignParentTop="true" > diff --git a/app/res/layout/account_row.xml b/app/res/layout/account_row.xml index 2c7654aaf..fba2b8c55 100644 --- a/app/res/layout/account_row.xml +++ b/app/res/layout/account_row.xml @@ -65,7 +65,6 @@ style="@style/AccountListText" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:layout_weight="1" android:minWidth="48dp" @@ -75,7 +74,6 @@ android:id="@+id/account_row_upload" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:minWidth="48dp" android:minHeight="48dp" diff --git a/app/res/layout/actionbar_dropdown_spinner.xml b/app/res/layout/actionbar_dropdown_spinner.xml index 3fd319e3b..754c50b77 100644 --- a/app/res/layout/actionbar_dropdown_spinner.xml +++ b/app/res/layout/actionbar_dropdown_spinner.xml @@ -4,4 +4,4 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" - android:gravity="left" /> \ No newline at end of file + android:gravity="start" /> \ No newline at end of file diff --git a/app/res/layout/actionbar_spinner.xml b/app/res/layout/actionbar_spinner.xml index cd5cd040b..20fa7d330 100644 --- a/app/res/layout/actionbar_spinner.xml +++ b/app/res/layout/actionbar_spinner.xml @@ -3,4 +3,4 @@ style="@style/TextAppearance.AppCompat.Title" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="left" /> \ No newline at end of file + android:gravity="start" /> \ No newline at end of file diff --git a/app/res/layout/detail.xml b/app/res/layout/detail.xml index 3a13046a9..4f887ed77 100644 --- a/app/res/layout/detail.xml +++ b/app/res/layout/detail.xml @@ -140,7 +140,7 @@ android:id="@+id/notes_text" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="top|left" + android:gravity="top|start" android:hint="@string/Notes_about_your_workout" android:inputType="textCapSentences|textMultiLine" android:minLines="4" @@ -229,7 +229,6 @@ android:background="@drawable/btn_blue" android:drawablePadding="-32dp" android:drawableEnd="@drawable/ic_av_play_arrow" - android:drawableRight="@drawable/ic_av_play_arrow" android:text="@string/Resume" />