diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml
new file mode 100644
index 0000000..447aaa1
--- /dev/null
+++ b/.github/actionlint.yaml
@@ -0,0 +1,6 @@
+paths:
+ .github/workflows/**/*.{yml,yaml}:
+ ignore:
+ - '"inputs" section is alias node but mapping node is expected'
+ - '"paths" section must be sequence node but got alias node with "" tag'
+ - '"paths-ignore" section must be sequence node but got alias node with "" tag'
diff --git a/.github/workflows/draft.yaml b/.github/workflows/draft.yaml
index a82f5c3..309c3b9 100644
--- a/.github/workflows/draft.yaml
+++ b/.github/workflows/draft.yaml
@@ -19,7 +19,7 @@ jobs:
steps:
- name: "Checkout"
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: "Draft Release Action"
id: draft
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index c7f5bf3..35b316a 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -20,13 +20,19 @@ jobs:
steps:
- name: "Checkout"
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: "Debug event.json"
- if: ${{ !cancelled() }}
continue-on-error: true
- run: |
- cat "${GITHUB_EVENT_PATH}"
+ run: cat "${GITHUB_EVENT_PATH}"
+ - name: "Debug CTX github"
+ continue-on-error: true
+ env:
+ GITHUB_CTX: ${{ toJSON(github) }}
+ run: echo "$GITHUB_CTX"
+ - name: "Debug Environment"
+ continue-on-error: true
+ run: env
- name: "Kotlin Lint"
if: ${{ !cancelled() }}
@@ -49,28 +55,26 @@ jobs:
echo "::endgroup::"
npx prettier --check .
- - name: "Yamllint"
+ - name: "yamllint"
if: ${{ !cancelled() }}
env:
- CONFIG: "{extends: relaxed, ignore: [node_modules/,.github/disabled/], rules: {line-length: {max: 119}}}"
+ CONFIG: .github/yamllint.yaml
run: |
echo "::group::List Files"
- yamllint -d '${{ env.CONFIG }}' --list-files .
+ yamllint -c "${{ env.CONFIG }}" --list-files .
echo "::endgroup::"
- yamllint -d '${{ env.CONFIG }}' .
+ yamllint -c "${{ env.CONFIG }}" .
- - name: "Actionlint"
+ - name: "actionlint"
if: ${{ !cancelled() }}
- run: |
- echo "::group::Download"
- loc=$(curl -sI https://github.com/rhysd/actionlint/releases/latest | grep -i '^location:')
- echo "loc: ${loc}"
- tag=$(echo "${loc}" | sed -E 's|.*/tag/v?(.*)|\1|' | tr -d '\t\r\n')
- echo "tag: ${tag}"
- url="https://github.com/rhysd/actionlint/releases/latest/download/actionlint_${tag}_linux_amd64.tar.gz"
- echo "url: ${url}"
- curl -sL "${url}" | tar xz -C "${RUNNER_TEMP}" actionlint
- file "${RUNNER_TEMP}/actionlint"
- "${RUNNER_TEMP}/actionlint" --version
- echo "::endgroup::"
- "${RUNNER_TEMP}/actionlint" -color -verbose -shellcheck= -pyflakes=
+ uses: cssnr/actionlint-action@v1
+ with:
+ shellcheck_opts: -e SC2129
+
+ - name: "ShellCheck"
+ if: ${{ !cancelled() }}
+ uses: ludeeus/action-shellcheck@master
+ env:
+ SHELLCHECK_OPTS: -x
+ with:
+ scandir: .github/scripts
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index b42aa1b..0d8cf8b 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -35,7 +35,7 @@ jobs:
steps:
- name: "Checkout"
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: "Debug Event"
continue-on-error: true
@@ -68,7 +68,7 @@ jobs:
echo "::endgroup::"
- name: "Update Version"
- uses: chkfung/android-version-actions@v1.2.2
+ uses: chkfung/android-version-actions@fcf89abef1c7afba2083146dcca0c6da4705ba4b # v1.2.3
id: version
with:
gradlePath: ${{ env.gradle_file }}
@@ -98,7 +98,7 @@ jobs:
echo ${{ secrets.ANDROID_KEYSTORE_PASS }} | keytool -list -keystore ${{ env.key_file }}
- name: "Setup Node 22"
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v6
with:
node-version: 22
@@ -114,7 +114,7 @@ jobs:
ls -lAh app
- name: "Setup Java"
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: "zulu"
java-version: "17"
@@ -159,12 +159,10 @@ jobs:
- name: "Upload APK to Release"
if: ${{ github.event_name == 'release' }}
- uses: svenstaro/upload-release-action@v2
+ uses: cssnr/upload-release-action@v1
with:
- file: ${{ env.apk_path }}/${{ env.signed_apk }}
- tag: ${{ github.ref }}
+ files: ${{ env.apk_path }}/${{ env.signed_apk }}
overwrite: true
- file_glob: true
- name: "Gradle Bundle AAB"
if: ${{ !github.event.release.prerelease }}
@@ -177,11 +175,11 @@ jobs:
continue-on-error: true
run: |
echo "env.aab_path: ${{ env.aab_path }}"
- ls -lAh ${{ env.aab_path }} ||:
+ ls -lAh "${{ env.aab_path }}" ||:
echo "env.debug_symbols: ${{ env.debug_symbols }}"
- ls -lAh ${{ env.debug_symbols }} ||:
+ ls -lAh "${{ env.debug_symbols }}" ||:
echo "env.mapping_file: ${{ env.mapping_file }}"
- ls -lAh $(dirname ${{ env.debug_symbols }}) ||:
+ ls -lAh "$(dirname "${{ env.debug_symbols }}")" ||:
- name: "Sign Bundle"
if: ${{ !github.event.release.prerelease }}
@@ -196,7 +194,7 @@ jobs:
- name: "Upload Bundle to Artifacts"
if: ${{ !github.event.release.prerelease }}
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: bundle
path: ${{ env.aab_path }}
@@ -219,7 +217,7 @@ jobs:
- name: "Upload Google Play"
if: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
- uses: r0adkll/upload-google-play@v1
+ uses: r0adkll/upload-google-play@935ef9c68bb393a8e6116b1575626a7f5be3a7fb # v1.1.3
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
packageName: ${{ env.package_name }}
@@ -248,24 +246,24 @@ jobs:
if: ${{ !github.event.act }}
continue-on-error: true
run: |
- echo -e "## Android Release\n\n" >> $GITHUB_STEP_SUMMARY
+ echo -e "## Android Release\n\n" >> "$GITHUB_STEP_SUMMARY"
- echo -e "Final APK: \`${{ env.signed_apk }}\`\n\n" >> $GITHUB_STEP_SUMMARY
+ echo -e "Final APK: \`${{ env.signed_apk }}\`\n\n" >> "$GITHUB_STEP_SUMMARY"
- echo -e "Build Artifacts
\n\n" >> $GITHUB_STEP_SUMMARY
- echo -e "\`\`\`text\n$(ls -lAh ${{ env.apk_path }})\n\`\`\`\n\n" >> $GITHUB_STEP_SUMMARY
- echo -e " \n\n" >> $GITHUB_STEP_SUMMARY
+ echo -e "Build Artifacts
\n\n" >> "$GITHUB_STEP_SUMMARY"
+ echo -e "\`\`\`text\n$(ls -lAh ${{ env.apk_path }})\n\`\`\`\n\n" >> "$GITHUB_STEP_SUMMARY"
+ echo -e " \n\n" >> "$GITHUB_STEP_SUMMARY"
if [ -f "${{ env.apk_path }}/output-metadata.json" ];then
- echo -e "File: output-metadata.json
\n\n" >> $GITHUB_STEP_SUMMARY
- echo -e "\`\`\`json\n$(cat ${{ env.apk_path }}/output-metadata.json)\n\`\`\`\n\n" >> $GITHUB_STEP_SUMMARY
- echo -e " \n\n" >> $GITHUB_STEP_SUMMARY
+ echo -e "File: output-metadata.json
\n\n" >> "$GITHUB_STEP_SUMMARY"
+ echo -e "\`\`\`json\n$(cat ${{ env.apk_path }}/output-metadata.json)\n\`\`\`\n\n" >> "$GITHUB_STEP_SUMMARY"
+ echo -e " \n\n" >> "$GITHUB_STEP_SUMMARY"
fi
- echo -e "\n\n---" >> $GITHUB_STEP_SUMMARY
+ echo -e "\n\n---" >> "$GITHUB_STEP_SUMMARY"
- name: "Send Failure Notification"
if: ${{ failure() && github.event_name == 'release' }}
- uses: sarisia/actions-status-discord@v1
+ uses: sarisia/actions-status-discord@eb045afee445dc055c18d3d90bd0f244fd062708 # v1.16.0
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
diff --git a/.github/yamllint.yaml b/.github/yamllint.yaml
new file mode 100644
index 0000000..38237e5
--- /dev/null
+++ b/.github/yamllint.yaml
@@ -0,0 +1,7 @@
+extends: relaxed
+
+ignore-from-file: .gitignore
+
+rules:
+ line-length:
+ max: 119
diff --git a/.gitignore b/.gitignore
index 67e4e53..a828ca2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,19 +1,17 @@
+# Generic
.DS_Store
-
.idea/
*.iml
-
**/build/
+**/dist/
.gradle/
.kotlin/
captures/
app/debug
app/release
-
+# App
local.properties
*.keystore
*.logcat
**/google-services.json
**/firebase-adminsdk.json
-
-**/dist/
diff --git a/.prettierignore b/.prettierignore
index ac11c51..e9257f7 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,11 +1,4 @@
-# IDE
-.idea/
-.vscode/
-
-# Build
-**/dist/
-**/node_modules/
-.github/assets/
-
-# App
-app/src/main/assets/preview/**
+.github/assets
+.github/disabled
+.github/pull_request_template.md
+app/src/main/assets/preview
diff --git a/README.md b/README.md
index 1cfe6af..009eec3 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,18 @@
[](https://github.com/cssnr/zipline-android/releases/latest/download/app-release.apk)
[](https://github.com/cssnr/zipline-android/releases/latest)
-[](https://zipline-android.cssnr.com/)
-[](https://github.com/cssnr/zipline-android/pulse)
-[](https://github.com/cssnr/zipline-android/actions/workflows/lint.yaml)
-[](https://github.com/cssnr/zipline-android/actions/workflows/release.yaml)
+[](https://github.com/cssnr/zipline-android/actions/workflows/lint.yaml)
+[](https://github.com/cssnr/zipline-android/actions/workflows/release.yaml)
[](https://github.com/cssnr/zipline-android/blob/master/gradle/libs.versions.toml#L2)
-[](https://github.com/cssnr/zipline-android)
-[](https://github.com/cssnr/zipline-android)
-[](https://github.com/cssnr/zipline-android/discussions)
-[](https://github.com/cssnr/zipline-android/forks)
-[](https://github.com/cssnr/zipline-android/stargazers)
-[](https://cssnr.com/)
+[](https://zipline-android.cssnr.com/)
+[](https://github.com/cssnr/zipline-android/pulse)
+[](https://github.com/cssnr/zipline-android?tab=readme-ov-file#readme)
+[](https://github.com/cssnr/zipline-android?tab=readme-ov-file#readme)
+[](https://github.com/cssnr/zipline-android/graphs/contributors)
+[](https://github.com/cssnr/zipline-android/issues)
+[](https://github.com/cssnr/zipline-android/discussions)
+[](https://github.com/cssnr/zipline-android/forks)
+[](https://github.com/cssnr/zipline-android/stargazers)
+[](https://cssnr.github.io/)
[](https://discord.gg/wXy6m2X8wY)
[](https://ko-fi.com/cssnr)
[](https://zipline-android.cssnr.com/)
@@ -38,11 +40,11 @@ Native Kotlin Android Application with a Mobile First Design.
Everything is cached and images are not downloaded over metered connections unless enabled.
User profile and stats widget are updated in the background with a user configurable task.
-**More information available on the website:** https://zipline-android.cssnr.com/
-
_We are also developing a browser addon for all major browsers including Firefox Android:
[Zipline Web Extension](https://github.com/cssnr/zipline-extension?tab=readme-ov-file#readme)_
+[](https://zipline-android.cssnr.com/)
+
## Install
> [!NOTE]
@@ -85,9 +87,9 @@ should take you to the settings area to allow installation if not already enable
-### Setup
+[](https://zipline-android.cssnr.com/)
-Setup guides available [on the website](https://zipline-android.cssnr.com/guides/get-started).
+### Setup
1. [Install](#Install) and open the app on your device.
2. Log in as you normally would on the website.
@@ -149,6 +151,8 @@ Features are documented [on the website](https://zipline-android.cssnr.com/guide
If you are having trouble using the app, support is available via [GitHub](#support) or [Discord](https://discord.gg/wXy6m2X8wY).
+[](https://zipline-android.cssnr.com/)
+
## Screenshots
A slideshow is available [on the website](https://zipline-android.cssnr.com/guides/features#screenshots).
@@ -192,7 +196,7 @@ A slideshow is available [on the website](https://zipline-android.cssnr.com/guid
## Support
-Documentation Site: https://zipline-android.cssnr.com/
+[](https://zipline-android.cssnr.com/)
For general help or to request a feature, see:
diff --git a/Taskfile.yml b/Taskfile.yml
new file mode 100644
index 0000000..29a9b50
--- /dev/null
+++ b/Taskfile.yml
@@ -0,0 +1,24 @@
+# yaml-language-server: $schema=https://taskfile.dev/schema.json
+version: "3"
+
+tasks:
+ lint:
+ desc: Lint
+ cmd: |
+ prettier --check .
+ yamllint -c .github/yamllint.yaml .
+ actionlint -shellcheck="-e SC2129"
+ shellcheck .github/scripts/prepare.sh
+
+ format:
+ desc: Format
+ aliases: [fmt]
+ cmd: |
+ prettier --write .
+
+ build:
+ desc: Build (recommended android studio)
+ vars:
+ GRADLEW: 'gradlew{{if eq OS "windows"}}.bat{{end}}'
+ cmd: |
+ ./{{.GRADLEW}} assemble {{.CLI_ARGS}}
diff --git a/app/src/main/java/org/cssnr/zipline/api/ServerApi.kt b/app/src/main/java/org/cssnr/zipline/api/ServerApi.kt
index 390e2de..5586a3f 100644
--- a/app/src/main/java/org/cssnr/zipline/api/ServerApi.kt
+++ b/app/src/main/java/org/cssnr/zipline/api/ServerApi.kt
@@ -8,7 +8,10 @@ import android.webkit.CookieManager
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.squareup.moshi.Json
+import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonClass
+import com.squareup.moshi.JsonReader
+import com.squareup.moshi.JsonWriter
import com.squareup.moshi.Moshi
import okhttp3.Cookie
import okhttp3.CookieJar
@@ -39,6 +42,7 @@ import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import java.io.InputStream
+import java.lang.reflect.Type
import java.net.URLConnection
class ServerApi(private val context: Context, url: String? = null) {
@@ -69,7 +73,9 @@ class ServerApi(private val context: Context, url: String? = null) {
Log.d("Api[login]", "$user - $pass - $code")
return try {
- val loginResponse = api.postAuthLogin(LoginRequest(user, pass, code))
+ // NOTE: Added for nullable adapter - SkipNullsAdapterFactory
+ val codeValue = if (code.isNullOrBlank()) null else code
+ val loginResponse = api.postAuthLogin(LoginRequest(user, pass, codeValue))
Log.i("Api[login]", "loginResponse.code(): ${loginResponse.code()}")
if (loginResponse.isSuccessful) {
val rawJson = loginResponse.body()?.string()
@@ -843,7 +849,9 @@ data class ErrorResponse(val error: String)
fun Response<*>.parseErrorBody(context: Context): String? {
val errorBody = errorBody() ?: return null
- val moshi = Moshi.Builder().build()
+ val moshi = Moshi.Builder()
+ .add(SkipNullsAdapterFactory()) // NOTE: Added for nullable adapter
+ .build()
val adapter = moshi.adapter(ErrorResponse::class.java)
return errorBody.source().use { source ->
try {
@@ -856,6 +864,20 @@ fun Response<*>.parseErrorBody(context: Context): String? {
}
}
+class SkipNullsAdapterFactory : JsonAdapter.Factory {
+ override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter<*> {
+ val delegate = moshi.nextAdapter(this, type, annotations)
+ return object : JsonAdapter() {
+ override fun fromJson(reader: JsonReader): Any? = delegate.fromJson(reader)
+
+ override fun toJson(writer: JsonWriter, value: Any?) {
+ writer.serializeNulls = false
+ delegate.toJson(writer, value)
+ }
+ }
+ }
+}
+
class InputStreamRequestBody(
private val contentType: MediaType,
diff --git a/app/src/main/java/org/cssnr/zipline/ui/dialogs/UploadOptionsDialog.kt b/app/src/main/java/org/cssnr/zipline/ui/dialogs/UploadOptionsDialog.kt
index 9709bb0..ccbc283 100644
--- a/app/src/main/java/org/cssnr/zipline/ui/dialogs/UploadOptionsDialog.kt
+++ b/app/src/main/java/org/cssnr/zipline/ui/dialogs/UploadOptionsDialog.kt
@@ -34,6 +34,7 @@ class UploadOptionsDialog : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ @Suppress("DEPRECATION")
uploadOptions = arguments?.getParcelable("upload_options")
}
diff --git a/app/src/main/java/org/cssnr/zipline/ui/settings/SettingsFragment.kt b/app/src/main/java/org/cssnr/zipline/ui/settings/SettingsFragment.kt
index 13173d3..9630457 100644
--- a/app/src/main/java/org/cssnr/zipline/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/org/cssnr/zipline/ui/settings/SettingsFragment.kt
@@ -14,6 +14,7 @@ import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
+import androidx.core.content.pm.PackageInfoCompat
import androidx.core.net.toUri
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
@@ -386,7 +387,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
val formattedVersion = getString(
R.string.version_code_string,
packageInfo.versionName,
- packageInfo.versionCode.toString()
+ PackageInfoCompat.getLongVersionCode(packageInfo).toString()
)
Log.d("showAppInfoDialog", "formattedVersion: $formattedVersion")
diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml
index f375f8b..1df0ec4 100644
--- a/app/src/main/res/layout/fragment_login.xml
+++ b/app/src/main/res/layout/fragment_login.xml
@@ -108,12 +108,12 @@
android:layout_height="48dp"
android:padding="10dp"
android:autofillHints=""
- android:hint="Two-Factor Code"
+ android:hint="Optional 2Factor Code"
android:inputType="number"
android:maxLength="6"
android:drawableEnd="@drawable/md_password_2_24px"
android:drawableTint="?android:attr/textColorTertiary"
- android:visibility="gone"
+ android:visibility="visible"
tools:visibility="visible" />