Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,28 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Run tests
run: ./gradlew ktfmtCheck
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Retrieve keystore for apk signing
env:
ENCODED_KEYSTORE: ${{ secrets.KEYSTORE }}
Expand All @@ -54,12 +58,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
- name: Retrieve keystore for apk signing
env:
ENCODED_KEYSTORE: ${{ secrets.KEYSTORE }}
Expand Down
3 changes: 2 additions & 1 deletion app/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ ktfmt {
kotlinLangStyle()
}

kotlin { jvmToolchain(11) }

android {
namespace = "com.github.livingwithhippos.unchained"
compileSdk = 36
Expand Down Expand Up @@ -134,7 +136,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions { jvmTarget = "11" }
buildFeatures {
viewBinding = true
buildConfig = true
Expand Down
1 change: 1 addition & 0 deletions app/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keep class com.google.re2j.** { *; }

# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
Expand Down
5 changes: 1 addition & 4 deletions app/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@

<data android:host="down.mdiaload.com" />
<data android:host="mediafire.com" />
<data android:host="mega.nz" />
<data android:host="mega.co.nz" />
<data android:host="mixdrop.co" />
<data android:host="mixloads.com" />
Expand Down Expand Up @@ -428,10 +429,6 @@
<data android:host="wushare.com" />
<data android:host="xubster.com" />

<data android:host="www.youtube.com" />
<data android:host="youtu.be" />
<data android:host="youtube.com" />

<data android:host="zippyshare.com" />
</intent-filter>
</activity>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,12 @@ class AuthenticationFragment : UnchainedFragment() {
val action = AuthenticationFragmentDirections.actionAuthenticationToUser()
findNavController().navigate(action)
}

FSMAuthenticationState.AuthenticatedPrivateToken -> {
val action = AuthenticationFragmentDirections.actionAuthenticationToUser()
findNavController().navigate(action)
}

FSMAuthenticationState.StartNewLogin -> {
// reset the current data
// token == null
Expand All @@ -125,21 +127,26 @@ class AuthenticationFragment : UnchainedFragment() {
// get the authentication link to start the process
viewModel.fetchAuthenticationInfo()
}

FSMAuthenticationState.WaitingUserConfirmation -> {
// start the next auth step
viewModel.fetchSecrets()
}

FSMAuthenticationState.WaitingToken -> {
viewModel.fetchToken()
}

FSMAuthenticationState.CheckCredentials,
FSMAuthenticationState.RefreshingOpenToken -> {
// managed by activity
}

is FSMAuthenticationState.WaitingUserAction -> {
// todo: depending on the action required show an error or restart the
// process
}

FSMAuthenticationState.Start -> {
// this shouldn't happen
}
Expand All @@ -148,28 +155,30 @@ class AuthenticationFragment : UnchainedFragment() {
}

// 1. start checking for the auth link
viewModel.authLiveData.observe(
viewLifecycleOwner,
EventObserver { auth ->
if (auth != null) {
binding.tvAuthenticationLink.text = auth.verificationUrl
binding.tvAuthenticationLink.visibility = View.VISIBLE
binding.cbLink.isChecked = true
binding.cbLink.text = getString(R.string.link_loaded)
// let the user copy the user code to enter in the website
binding.tvUserCodeValue.text = auth.userCode
binding.bCopyLink.isEnabled = true
// update the currently saved credentials
activityViewModel.updateCredentialsDeviceCode(auth.deviceCode)
// transition state machine
viewModel.authLiveData.observe(viewLifecycleOwner) { event ->
event?.peekContent()?.let { auth ->
binding.tvAuthenticationLink.text = auth.verificationUrl
binding.tvAuthenticationLink.visibility = View.VISIBLE
binding.cbLink.isChecked = true
binding.cbLink.text = getString(R.string.link_loaded)
// let the user copy the user code to enter in the website
binding.tvUserCodeValue.text = auth.userCode
binding.bCopyLink.isEnabled = true
// update the currently saved credentials
activityViewModel.updateCredentialsDeviceCode(auth.deviceCode)
// transition state machine
if (
activityViewModel.getAuthenticationMachineState()
is FSMAuthenticationState.StartNewLogin
) {
activityViewModel.transitionAuthenticationMachine(
FSMAuthenticationEvent.OnAuthLoaded
)
// set up values for calling the secrets endpoint
viewModel.setupSecretLoop(auth.expiresIn)
}
},
)
}
}

// 2. start checking for user confirmation
viewModel.secretLiveData.observe(
Expand All @@ -186,12 +195,14 @@ class AuthenticationFragment : UnchainedFragment() {
FSMAuthenticationEvent.OnUserConfirmationMissing
)
}

SecretResult.Expired -> {
// will restart the authentication process
activityViewModel.transitionAuthenticationMachine(
FSMAuthenticationEvent.OnUserConfirmationExpired
)
}

is SecretResult.Retrieved -> {
if (
activityViewModel.getAuthenticationMachineState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,8 @@ constructor(
* secrets endpoint
*/
fun setupSecretLoop(expiresIn: Int) {
// this is just an estimate, keeping track of time would be more precise. As of now this
// value
// should be 120
// this is just an estimate, keeping track of time would be more precise.
// As of now this value should be 120
var calls = (expiresIn * 1000 / SECRET_CALLS_DELAY).toInt() - 10
// remove 10% of the calls to account for the api calls
calls -= calls / 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,33 @@ package com.github.livingwithhippos.unchained.base

import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import com.github.livingwithhippos.unchained.start.viewmodel.MainActivityViewModel
import timber.log.Timber

/** Base [Fragment] class, giving simple access to the activity ViewModel to its subclasses */
abstract class UnchainedFragment : Fragment() {

// activity viewModel. To be used for alerting of expired token or missing network
val activityViewModel: MainActivityViewModel by activityViewModels()

fun safeNavigate(action: NavDirections): Boolean {
val nav = findNavController()
val current = nav.currentDestination
if (current != null && current.getAction(action.actionId) != null) {
try {
nav.navigate(action)
return true
} catch (e: IllegalArgumentException) {
Timber.w(e, "Safe navigate failed for actionId=${action.actionId}")
return false
}
} else {
Timber.w(
"Navigation action not found from destination ${current?.id} for actionId=${action.actionId}"
)
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ class DownloadsListFragment : UnchainedFragment(), DownloadListListener {
viewModel.downloadItemLiveData.observe(
viewLifecycleOwner,
EventObserver { links ->
if (_binding == null) return@EventObserver
// todo: if it gets emptied null/empty should be processed too
if (links.isNotEmpty()) {
// simulate list refresh
Expand All @@ -567,6 +568,7 @@ class DownloadsListFragment : UnchainedFragment(), DownloadListListener {
activityViewModel.listStateLiveData.observe(
viewLifecycleOwner,
EventObserver {
if (_binding == null) return@EventObserver
when (it) {
ListState.UpdateDownload -> {
lifecycleScope.launch {
Expand Down Expand Up @@ -808,6 +810,7 @@ class TorrentsListFragment : UnchainedFragment(), TorrentListListener {
activityViewModel.listStateLiveData.observe(
viewLifecycleOwner,
EventObserver {
if (_binding == null) return@EventObserver
when (it) {
ListState.UpdateTorrent -> {
lifecycleScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import com.github.livingwithhippos.unchained.R
import com.github.livingwithhippos.unchained.base.UnchainedFragment
import com.github.livingwithhippos.unchained.data.model.UserAction
Expand Down Expand Up @@ -104,25 +102,6 @@ class StartFragment : UnchainedFragment() {
return binding.root
}

private fun safeNavigate(action: NavDirections): Boolean {
val nav = findNavController()
val current = nav.currentDestination
if (current != null && current.getAction(action.actionId) != null) {
try {
nav.navigate(action)
return true
} catch (e: IllegalArgumentException) {
Timber.w(e, "Safe navigate failed for actionId=${action.actionId}")
return false
}
} else {
Timber.w(
"Navigation action not found from destination ${current?.id} for actionId=${action.actionId}"
)
return false
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.content.PermissionChecker
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import coil.load
import com.github.livingwithhippos.unchained.R
import com.github.livingwithhippos.unchained.base.UnchainedFragment
Expand Down Expand Up @@ -115,14 +114,14 @@ class UserProfileFragment : UnchainedFragment() {
is FSMAuthenticationState.WaitingUserAction -> {
// an error occurred, check it and eventually go back to the start fragment
val action = UserProfileFragmentDirections.actionUserToStartFragment()
findNavController().navigate(action)
safeNavigate(action)
}

FSMAuthenticationState.StartNewLogin -> {
// the user reset the login, go to the auth fragment
val action =
UserProfileFragmentDirections.actionUserToAuthenticationFragment()
findNavController().navigate(action)
safeNavigate(action)
}

FSMAuthenticationState.AuthenticatedOpenToken,
Expand Down
5 changes: 3 additions & 2 deletions app/app/src/main/res/layout/fragment_authentication.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fitsSystemWindows="true"
android:orientation="vertical">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical">
android:layout_height="wrap_content">

<TextView
android:id="@+id/tvLoginMessage"
Expand Down
16 changes: 8 additions & 8 deletions app/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[versions]
android_gradle_plugin = "8.13.1"
android_gradle_plugin = "8.13.2"
appcompat = "1.7.1"
constraint_layout = "2.2.1"
coil = "2.7.0"
core_ktx = "1.17.0"
coroutines = "1.10.2"
# non compliant with new Play Store but it's not used in production
countly = "25.4.7"
countly = "25.4.9"
dagger = "2.57.2"
datetime = "0.7.1"
espresso = "3.7.0"
Expand All @@ -16,19 +16,19 @@ fragment = "1.8.9"
hilt_navigation = "1.3.0"
jackson = "2.20.1"
jakarta_xml = "4.0.4"
jsoup = "1.21.2"
jsoup = "1.22.1"
junit = "4.13.2"
kotlin = "2.2.21"
kotlin = "2.3.0"
ktfmt = "0.25.0"
ksp = "2.3.3"
ksp = "2.3.4"
lifecycle = "2.10.0"
material_three = "1.13.0"
moshi = "1.15.2"
navigation = "2.9.6"
okhttp = "5.3.2"
paging = "3.3.6"
protobuf = "4.33.1"
protobuf_plugin = "0.9.5"
protobuf = "4.33.3"
protobuf_plugin = "0.9.6"
preference = "1.2.1"
recyclerview = "1.4.0"
recyclerview_selection = "1.2.0"
Expand All @@ -37,7 +37,7 @@ robolectric = "4.16"
room = "2.8.4"
statemachine = "0.2.0"
stax = "1.0-2"
swiperefresh = "1.1.0"
swiperefresh = "1.2.0"
test = "1.7.0"
test_truth = "1.7.0"
test_runner = "1.7.0"
Expand Down
Loading
Loading