Skip to content
Merged
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
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[![GitHub Downloads](https://img.shields.io/github/downloads/django-files/android-client/total?logo=github)](https://github.com/django-files/android-client/releases/latest/download/app-release.apk)
[![GitHub Downloads](https://img.shields.io/github/downloads/django-files/android-client/total?logo=android)](https://github.com/django-files/android-client/releases/latest/download/app-release.apk)
[![GitHub Release Version](https://img.shields.io/github/v/release/django-files/android-client?logo=github)](https://github.com/django-files/android-client/releases/latest)
[![Lint](https://img.shields.io/github/actions/workflow/status/django-files/android-client/lint.yaml?logo=github&logoColor=white&label=lint)](https://github.com/django-files/android-client/actions/workflows/lint.yaml)
[![GitHub Top Language](https://img.shields.io/github/languages/top/django-files/android-client?logo=htmx)](https://github.com/django-files/android-client)
[![GitHub Last Commit](https://img.shields.io/github/last-commit/django-files/android-client?logo=github&label=updated)](https://github.com/django-files/android-client/graphs/commit-activity)
[![GitHub Repo Size](https://img.shields.io/github/repo-size/django-files/android-client?logo=bookstack&logoColor=white&label=repo%20size)](https://github.com/django-files/android-client)
[![GitHub Top Language](https://img.shields.io/github/languages/top/django-files/android-client?logo=htmx)](https://github.com/django-files/android-client)
[![GitHub Discussions](https://img.shields.io/github/discussions/django-files/android-client)](https://github.com/django-files/android-client/discussions)
[![GitHub Forks](https://img.shields.io/github/forks/django-files/android-client?style=flat&logo=github)](https://github.com/django-files/android-client/forks)
[![GitHub Repo Stars](https://img.shields.io/github/stars/django-files/android-client?style=flat&logo=github)](https://github.com/django-files/android-client/stargazers)
Expand Down Expand Up @@ -59,16 +59,14 @@ _If you are unsure how to install, [Obtainium](https://github.com/ImranR98/Obtai

</details>

_Note: Until published on the play store, you may need to allow installation of apps from unknown sources._

- Supports Android 8 (API 26) 2017 +

Downloading and Installing the [apk](https://github.com/django-files/android-client/releases/latest/download/app-release.apk)
should take you to the settings area to allow installation if not already enabled.
For more information, see [Release through a website](https://developer.android.com/studio/publish#publishing-website).
_Note: If installing directly, you may need to allow installation of apps from unknown sources.
For more information, see [Release through a website](https://developer.android.com/studio/publish#publishing-website)._

<details><summary>View Manual Steps to Install from Unknown Sources</summary>

Note: Downloading and Installing the [apk](https://github.com/django-files/android-client/releases/latest/download/app-release.apk)
should take you to the settings area to allow installation if not already enabled. Otherwise:

1. Go to your device settings.
2. Search for "Install unknown apps" or similar.
3. Choose the app you will install the apk file from.
Expand Down
63 changes: 26 additions & 37 deletions app/src/main/java/com/djangofiles/djangofiles/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,20 @@ import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.djangofiles.djangofiles.databinding.ActivityMainBinding
import com.djangofiles.djangofiles.db.Server
import com.djangofiles.djangofiles.db.ServerDao
import com.djangofiles.djangofiles.db.ServerDatabase
import com.djangofiles.djangofiles.ui.home.HomeViewModel
import com.djangofiles.djangofiles.widget.WidgetProvider
import com.djangofiles.djangofiles.work.DAILY_WORKER_CONSTRAINTS
import com.djangofiles.djangofiles.work.DailyWorker
import com.djangofiles.djangofiles.work.enqueueWorkRequest
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.io.File
import java.net.URL
import java.util.UUID
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity() {

Expand Down Expand Up @@ -202,40 +199,34 @@ class MainActivity : AppCompatActivity() {
window.setNavigationBarContrastEnforced(false)
}

// Set Nav Header Top Padding
// Update Header Padding
val headerView = binding.navView.getHeaderView(0)
ViewCompat.setOnApplyWindowInsetsListener(headerView) { v, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.statusBars())
Log.d("ViewCompat", "top: ${bars.top}")
v.updatePadding(top = bars.top)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
Log.d("ViewCompat", "binding.root: top: ${bars.top}")
if (bars.top > 0) {
headerView.updatePadding(top = bars.top)
}
insets
}

// Update Header Text
val packageInfo = packageManager.getPackageInfo(this.packageName, 0)
val packageInfo = packageManager.getPackageInfo(packageName, 0)
val versionName = packageInfo.versionName
Log.d("Main[onCreate]", "versionName: $versionName")
val versionTextView = headerView.findViewById<TextView>(R.id.header_version)
versionTextView.text = "v${versionName}"

// TODO: Improve initialization of the WorkRequest
// Work Manager
val workInterval = preferences.getString("work_interval", null) ?: "0"
Log.i("Main[onCreate]", "workInterval: $workInterval")
Log.d("Main[onCreate]", "workInterval: $workInterval")
// NOTE: This just ensures work manager is enabled or disabled based on preference
if (workInterval != "0") {
val workRequest =
PeriodicWorkRequestBuilder<DailyWorker>(workInterval.toLong(), TimeUnit.MINUTES)
.setConstraints(DAILY_WORKER_CONSTRAINTS)
.build()
Log.i("Main[onCreate]", "workRequest: $workRequest")
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"daily_worker",
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
enqueueWorkRequest(workInterval, ExistingPeriodicWorkPolicy.KEEP)
} else {
// TODO: Confirm this is necessary...
Log.i("Main[onCreate]", "Ensuring Work is Disabled")
WorkManager.getInstance(this).cancelUniqueWork("app_worker")
WorkManager.getInstance(this).cancelUniqueWork("daily_worker")
}

// Handle Custom Navigation Items
Expand Down Expand Up @@ -418,8 +409,8 @@ class MainActivity : AppCompatActivity() {
Log.d("onNewIntent", "SEND TEXT DETECTED")
//if (extraText.lowercase().startsWith("http")) {
//if (Patterns.WEB_URL.matcher(extraText).matches()) {
if (isURL(extraText)) {
Log.d("onNewIntent", "URL DETECTED: $extraText")
if (isTextUrl(extraText)) {
Log.i("onNewIntent", "URL DETECTED: $extraText")
val bundle = Bundle().apply { putString("url", extraText) }
navController.navigate(
R.id.nav_item_short, bundle, NavOptions.Builder()
Expand Down Expand Up @@ -662,6 +653,15 @@ class MainActivity : AppCompatActivity() {
}
}

private fun isTextUrl(input: String): Boolean {
val url = input.toHttpUrlOrNull() ?: return false
if (input != url.toString()) return false
if (url.scheme !in listOf("http", "https")) return false
if (url.host.isBlank()) return false
if (url.toString().length > 2048) return false
return true
}

fun setDrawerLockMode(enabled: Boolean) {
Log.d("setDrawerLockMode", "enabled: $enabled")
val lockMode =
Expand Down Expand Up @@ -709,14 +709,3 @@ fun copyToClipboard(context: Context, text: String, msg: String? = null) {
clipboard.setPrimaryClip(clip)
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}

fun isURL(url: String): Boolean {
return try {
URL(url)
Log.d("isURL", "TRUE")
true
} catch (_: Exception) {
Log.d("isURL", "FALSE")
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ class FeedbackApi(val context: Context) {
}

suspend fun sendFeedback(messageText: String): Response<Unit> {
val versionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName
Log.d("sendFeedback", "messageText: $messageText")
val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
val feedbackText = context.getString(
R.string.feedback_message,
context.getString(R.string.app_name),
versionName,
packageInfo.versionName,
Build.VERSION.SDK_INT.toString(),
messageText
)
Expand All @@ -51,8 +51,7 @@ class FeedbackApi(val context: Context) {

@JsonClass(generateAdapter = true)
data class Message(
@Json(name = "content")
val content: String
@param:Json(name = "content") val content: String
)

interface ApiService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import androidx.navigation.fragment.findNavController
import com.djangofiles.djangofiles.R
import com.djangofiles.djangofiles.ServerApi
import com.djangofiles.djangofiles.databinding.FragmentLoginBinding
import com.djangofiles.djangofiles.isURL
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull

class LoginFragment : Fragment() {

Expand Down Expand Up @@ -92,7 +92,10 @@ class LoginFragment : Fragment() {
binding.hostnameText.setSelection(binding.hostnameText.text.length)
}
Log.d("loginFunction", "host: $host")
if (!isURL(host)) {
// TODO: Look into the usage of (host.toHttpUrlOrNull() == null) here.
// NOTE: This seems to be less restrictive so should work and can be improved...
//if (!isURL(host)) {
if (host.toHttpUrlOrNull() == null) {
binding.hostnameText.error = "Invalid Hostname"
return@OnClickListener
}
Expand Down Expand Up @@ -190,16 +193,63 @@ class LoginFragment : Fragment() {
}

private fun parseHost(urlString: String): String {
var url = urlString.trim()
if (url.isEmpty()) {
try {
var url = urlString.trim()
if (url.isEmpty()) {
return ""
}
if (!url.lowercase().startsWith("http")) {
url = "https://$url"
}
if (url.toHttpUrlOrNull() == null) {
return url
}
val uri = url.toUri()
Log.d("parseHost", "uri: $uri")
Log.d("parseHost", "uri.scheme: ${uri.scheme}")
if (uri.scheme.isNullOrEmpty()) {
return "https://"
}
Log.d("parseHost", "uri.host: ${uri.host}")
if (uri.host.isNullOrEmpty()) {
return "${uri.scheme}://"
}
Log.d("parseHost", "uri.path: ${uri.path}")
val result = "${uri.scheme}://${uri.host}${uri.path}"
Log.i("parseHost", "result: $result")
return if (result.endsWith("/")) {
result.dropLast(1)
} else {
result
}
} catch (e: Throwable) {
Log.d("parseHost", "Exception: $e")
return ""
}
if (!url.lowercase().startsWith("http")) {
url = "https://$url"
}
if (url.endsWith("/")) {
url = url.substring(0, url.length - 1)
}
return url
}

//private fun parseHost(urlString: String): String {
// var url = urlString.trim()
// if (url.isEmpty()) {
// return ""
// }
// if (!url.lowercase().startsWith("http")) {
// url = "https://$url"
// }
// if (url.endsWith("/")) {
// url = url.substring(0, url.length - 1)
// }
// return url
//}

//private fun isURL(url: String): Boolean {
// return try {
// URL(url)
// Log.d("isURL", "TRUE")
// true
// } catch (_: Exception) {
// Log.d("isURL", "FALSE")
// false
// }
//}
}
Loading
Loading