Loading app/build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ android { targetSdkVersion 31 versionCode 21 versionName "1.9.0" versionName "1.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" Loading app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +8 −5 Original line number Diff line number Diff line Loading @@ -118,7 +118,7 @@ class ApiService { return call } fun authTopicRead(baseUrl: String, topic: String, user: User?): Boolean { fun checkAuth(baseUrl: String, topic: String, user: User?): Boolean { if (user == null) { Log.d(TAG, "Checking anonymous read against ${topicUrl(baseUrl, topic)}") } else { Loading @@ -127,11 +127,14 @@ class ApiService { val url = topicUrlAuth(baseUrl, topic) val request = requestBuilder(url, user).build() client.newCall(request).execute().use { response -> return if (user == null) { response.isSuccessful || response.code == 404 // Treat 404 as success (old server; to be removed in future versions) } else { response.isSuccessful } if (response.isSuccessful) { return true } else if (user == null && response.code == 404) { return true // Special case: Anonymous login to old servers return 404 since /<topic>/auth doesn't exist } else if (response.code == 401 || response.code == 403) { // See server/server.go return false } throw Exception("Unexpected server response ${response.code}") } } Loading app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +15 −4 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.util.TypedValue import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager Loading Loading @@ -88,7 +89,9 @@ class AddFragment : DialogFragment() { // Fields for "subscribe page" subscribeTopicText = view.findViewById(R.id.add_dialog_subscribe_topic_text) subscribeBaseUrlLayout = view.findViewById(R.id.add_dialog_subscribe_base_url_layout) subscribeBaseUrlLayout.background = view.background subscribeBaseUrlText = view.findViewById(R.id.add_dialog_subscribe_base_url_text) subscribeBaseUrlText.background = view.background subscribeInstantDeliveryBox = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_box) subscribeInstantDeliveryCheckbox = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_checkbox) subscribeInstantDeliveryDescription = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_description) Loading @@ -100,6 +103,14 @@ class AddFragment : DialogFragment() { subscribeErrorTextImage = view.findViewById(R.id.add_dialog_subscribe_error_text_image) subscribeErrorTextImage.visibility = View.GONE // Hack: Make end icon smaller, see https://stackoverflow.com/a/57098715/1440785 val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics) val endIconImageView = subscribeBaseUrlLayout.findViewById<ImageView>(R.id.text_input_end_icon) endIconImageView.minimumHeight = dimension.toInt() endIconImageView.minimumWidth = dimension.toInt() subscribeBaseUrlLayout.requestLayout() // Fields for "login page" loginUsernameText = view.findViewById(R.id.add_dialog_login_username) loginPasswordText = view.findViewById(R.id.add_dialog_login_password) Loading Loading @@ -280,14 +291,14 @@ class AddFragment : DialogFragment() { lifecycleScope.launch(Dispatchers.IO) { try { val user = repository.getUser(baseUrl) // May be null val authorized = api.authTopicRead(baseUrl, topic, user) val authorized = api.checkAuth(baseUrl, topic, user) if (authorized) { Log.d(TAG, "Access granted to topic ${topicUrl(baseUrl, topic)}") dismissDialog() } else { if (user != null) { Log.w(TAG, "Access not allowed to topic ${topicUrl(baseUrl, topic)}, but user already exists") showErrorAndReenableSubscribeView(getString(R.string.add_dialog_login_error_not_authorized)) showErrorAndReenableSubscribeView(getString(R.string.add_dialog_login_error_not_authorized, user.username)) } else { Log.w(TAG, "Access not allowed to topic ${topicUrl(baseUrl, topic)}, showing login dialog") val activity = activity ?: return@launch // We may have pressed "Cancel" Loading Loading @@ -327,14 +338,14 @@ class AddFragment : DialogFragment() { lifecycleScope.launch(Dispatchers.IO) { Log.d(TAG, "Checking read access for user ${user.username} to topic ${topicUrl(baseUrl, topic)}") try { val authorized = api.authTopicRead(baseUrl, topic, user) val authorized = api.checkAuth(baseUrl, topic, user) if (authorized) { Log.d(TAG, "Access granted for user ${user.username} to topic ${topicUrl(baseUrl, topic)}, adding to database") repository.addUser(user) dismissDialog() } else { Log.w(TAG, "Access not allowed for user ${user.username} to topic ${topicUrl(baseUrl, topic)}") showErrorAndReenableLoginView(getString(R.string.add_dialog_login_error_not_authorized)) showErrorAndReenableLoginView(getString(R.string.add_dialog_login_error_not_authorized, user.username)) } } catch (e: Exception) { Log.w(TAG, "Connection to topic failed during login: ${e.message}", e) Loading app/src/main/res/layout/fragment_add_dialog.xml +60 −56 Original line number Diff line number Diff line Loading @@ -6,13 +6,14 @@ android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp"> <androidx.constraintlayout.widget.ConstraintLayout <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:id="@+id/add_dialog_subscribe_view" android:visibility="visible"> android:id="@+id/add_dialog_subscribe_view"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/add_dialog_subscribe_title_text" android:layout_width="0dp" Loading Loading @@ -70,14 +71,13 @@ style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu" android:id="@+id/add_dialog_subscribe_base_url_layout" android:layout_width="match_parent" android:layout_height="40dp" android:layout_height="wrap_content" android:layout_margin="0dp" android:background="@android:color/transparent" android:padding="0dp" android:visibility="gone" app:endIconMode="custom" app:hintEnabled="false" app:boxBackgroundColor="@android:color/transparent" app:layout_constraintStart_toStartOf="parent" app:boxBackgroundColor="@null" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_description"> <AutoCompleteTextView Loading @@ -89,13 +89,14 @@ android:layout_marginTop="0dp" android:layout_marginBottom="0dp" android:inputType="textNoSuggestions" android:textAppearance="?android:attr/textAppearanceMedium" android:paddingStart="0dp" android:paddingEnd="0dp" android:paddingTop="0dp" android:paddingBottom="0dp" android:paddingTop="5dp" android:paddingBottom="5dp" android:layout_marginStart="4dp" android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp" android:textAppearance="?android:attr/textAppearanceMedium" /> </com.google.android.material.textfield.TextInputLayout> <LinearLayout Loading Loading @@ -140,13 +141,15 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description" android:paddingEnd="4dp" android:textColor="@color/primaryDangerButtonColor" app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image" android:layout_marginTop="5dp" tools:visibility="gone"/> </androidx.constraintlayout.widget.ConstraintLayout> <androidx.constraintlayout.widget.ConstraintLayout </ScrollView> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:id="@+id/add_dialog_login_view" android:visibility="gone" > android:id="@+id/add_dialog_login_view"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/add_dialog_login_title" android:layout_width="0dp" Loading Loading @@ -204,4 +207,5 @@ app:layout_constraintBottom_toTopOf="@+id/add_dialog_login_description" android:indeterminate="true" android:layout_marginBottom="5dp"/> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> </LinearLayout> app/src/main/res/values/strings.xml +4 −5 Original line number Diff line number Diff line Loading @@ -77,7 +77,7 @@ <string name="add_dialog_title">Subscribe to topic</string> <string name="add_dialog_description_below"> Topics may not be password-protected, so choose a name that\'s not easy to guess. Once subscribed, you can PUT/POST to receive notifications. guess. Once subscribed, you can PUT/POST notifications. </string> <string name="add_dialog_topic_name_hint">Topic name, e.g. phils_alerts</string> <string name="add_dialog_use_another_server">Use another server</string> Loading @@ -85,12 +85,11 @@ You can subscribe to topics from your own server. This option requires a foreground service. </string> <string name="add_dialog_use_another_server_description_noinstant"> You can subscribe to topics from your own server. Simply type in the base URL of your server. You can subscribe to topics from your own server. Type the server URL below. </string> <string name="add_dialog_instant_delivery">Instant delivery in doze mode</string> <string name="add_dialog_instant_delivery_description"> Ensures that messages are immediately delivered, even if the device is inactive or in doze mode. Ensures that messages are immediately delivered, even if the device is inactive. This requires a foreground service. </string> <string name="add_dialog_button_cancel">Cancel</string> Loading @@ -102,7 +101,7 @@ <string name="add_dialog_login_description">This topic requires you to login. Please type in a username and password.</string> <string name="add_dialog_login_username_hint">Username</string> <string name="add_dialog_login_password_hint">Password</string> <string name="add_dialog_login_error_not_authorized">Login failed. User not authorized.</string> <string name="add_dialog_login_error_not_authorized">Login failed. User %1$s not authorized.</string> <string name="add_dialog_login_new_user">New user</string> <!-- Detail activity --> Loading Loading
app/build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ android { targetSdkVersion 31 versionCode 21 versionName "1.9.0" versionName "1.8.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" Loading
app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +8 −5 Original line number Diff line number Diff line Loading @@ -118,7 +118,7 @@ class ApiService { return call } fun authTopicRead(baseUrl: String, topic: String, user: User?): Boolean { fun checkAuth(baseUrl: String, topic: String, user: User?): Boolean { if (user == null) { Log.d(TAG, "Checking anonymous read against ${topicUrl(baseUrl, topic)}") } else { Loading @@ -127,11 +127,14 @@ class ApiService { val url = topicUrlAuth(baseUrl, topic) val request = requestBuilder(url, user).build() client.newCall(request).execute().use { response -> return if (user == null) { response.isSuccessful || response.code == 404 // Treat 404 as success (old server; to be removed in future versions) } else { response.isSuccessful } if (response.isSuccessful) { return true } else if (user == null && response.code == 404) { return true // Special case: Anonymous login to old servers return 404 since /<topic>/auth doesn't exist } else if (response.code == 401 || response.code == 403) { // See server/server.go return false } throw Exception("Unexpected server response ${response.code}") } } Loading
app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +15 −4 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ import android.content.Context import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.util.TypedValue import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager Loading Loading @@ -88,7 +89,9 @@ class AddFragment : DialogFragment() { // Fields for "subscribe page" subscribeTopicText = view.findViewById(R.id.add_dialog_subscribe_topic_text) subscribeBaseUrlLayout = view.findViewById(R.id.add_dialog_subscribe_base_url_layout) subscribeBaseUrlLayout.background = view.background subscribeBaseUrlText = view.findViewById(R.id.add_dialog_subscribe_base_url_text) subscribeBaseUrlText.background = view.background subscribeInstantDeliveryBox = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_box) subscribeInstantDeliveryCheckbox = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_checkbox) subscribeInstantDeliveryDescription = view.findViewById(R.id.add_dialog_subscribe_instant_delivery_description) Loading @@ -100,6 +103,14 @@ class AddFragment : DialogFragment() { subscribeErrorTextImage = view.findViewById(R.id.add_dialog_subscribe_error_text_image) subscribeErrorTextImage.visibility = View.GONE // Hack: Make end icon smaller, see https://stackoverflow.com/a/57098715/1440785 val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, resources.displayMetrics) val endIconImageView = subscribeBaseUrlLayout.findViewById<ImageView>(R.id.text_input_end_icon) endIconImageView.minimumHeight = dimension.toInt() endIconImageView.minimumWidth = dimension.toInt() subscribeBaseUrlLayout.requestLayout() // Fields for "login page" loginUsernameText = view.findViewById(R.id.add_dialog_login_username) loginPasswordText = view.findViewById(R.id.add_dialog_login_password) Loading Loading @@ -280,14 +291,14 @@ class AddFragment : DialogFragment() { lifecycleScope.launch(Dispatchers.IO) { try { val user = repository.getUser(baseUrl) // May be null val authorized = api.authTopicRead(baseUrl, topic, user) val authorized = api.checkAuth(baseUrl, topic, user) if (authorized) { Log.d(TAG, "Access granted to topic ${topicUrl(baseUrl, topic)}") dismissDialog() } else { if (user != null) { Log.w(TAG, "Access not allowed to topic ${topicUrl(baseUrl, topic)}, but user already exists") showErrorAndReenableSubscribeView(getString(R.string.add_dialog_login_error_not_authorized)) showErrorAndReenableSubscribeView(getString(R.string.add_dialog_login_error_not_authorized, user.username)) } else { Log.w(TAG, "Access not allowed to topic ${topicUrl(baseUrl, topic)}, showing login dialog") val activity = activity ?: return@launch // We may have pressed "Cancel" Loading Loading @@ -327,14 +338,14 @@ class AddFragment : DialogFragment() { lifecycleScope.launch(Dispatchers.IO) { Log.d(TAG, "Checking read access for user ${user.username} to topic ${topicUrl(baseUrl, topic)}") try { val authorized = api.authTopicRead(baseUrl, topic, user) val authorized = api.checkAuth(baseUrl, topic, user) if (authorized) { Log.d(TAG, "Access granted for user ${user.username} to topic ${topicUrl(baseUrl, topic)}, adding to database") repository.addUser(user) dismissDialog() } else { Log.w(TAG, "Access not allowed for user ${user.username} to topic ${topicUrl(baseUrl, topic)}") showErrorAndReenableLoginView(getString(R.string.add_dialog_login_error_not_authorized)) showErrorAndReenableLoginView(getString(R.string.add_dialog_login_error_not_authorized, user.username)) } } catch (e: Exception) { Log.w(TAG, "Connection to topic failed during login: ${e.message}", e) Loading
app/src/main/res/layout/fragment_add_dialog.xml +60 −56 Original line number Diff line number Diff line Loading @@ -6,13 +6,14 @@ android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp"> <androidx.constraintlayout.widget.ConstraintLayout <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:id="@+id/add_dialog_subscribe_view" android:visibility="visible"> android:id="@+id/add_dialog_subscribe_view"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/add_dialog_subscribe_title_text" android:layout_width="0dp" Loading Loading @@ -70,14 +71,13 @@ style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.ExposedDropdownMenu" android:id="@+id/add_dialog_subscribe_base_url_layout" android:layout_width="match_parent" android:layout_height="40dp" android:layout_height="wrap_content" android:layout_margin="0dp" android:background="@android:color/transparent" android:padding="0dp" android:visibility="gone" app:endIconMode="custom" app:hintEnabled="false" app:boxBackgroundColor="@android:color/transparent" app:layout_constraintStart_toStartOf="parent" app:boxBackgroundColor="@null" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_use_another_server_description"> <AutoCompleteTextView Loading @@ -89,13 +89,14 @@ android:layout_marginTop="0dp" android:layout_marginBottom="0dp" android:inputType="textNoSuggestions" android:textAppearance="?android:attr/textAppearanceMedium" android:paddingStart="0dp" android:paddingEnd="0dp" android:paddingTop="0dp" android:paddingBottom="0dp" android:paddingTop="5dp" android:paddingBottom="5dp" android:layout_marginStart="4dp" android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp" android:textAppearance="?android:attr/textAppearanceMedium" /> </com.google.android.material.textfield.TextInputLayout> <LinearLayout Loading Loading @@ -140,13 +141,15 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/add_dialog_subscribe_instant_delivery_description" android:paddingEnd="4dp" android:textColor="@color/primaryDangerButtonColor" app:layout_constraintStart_toEndOf="@id/add_dialog_subscribe_error_text_image" android:layout_marginTop="5dp" tools:visibility="gone"/> </androidx.constraintlayout.widget.ConstraintLayout> <androidx.constraintlayout.widget.ConstraintLayout </ScrollView> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:id="@+id/add_dialog_login_view" android:visibility="gone" > android:id="@+id/add_dialog_login_view"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/add_dialog_login_title" android:layout_width="0dp" Loading Loading @@ -204,4 +207,5 @@ app:layout_constraintBottom_toTopOf="@+id/add_dialog_login_description" android:indeterminate="true" android:layout_marginBottom="5dp"/> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> </LinearLayout>
app/src/main/res/values/strings.xml +4 −5 Original line number Diff line number Diff line Loading @@ -77,7 +77,7 @@ <string name="add_dialog_title">Subscribe to topic</string> <string name="add_dialog_description_below"> Topics may not be password-protected, so choose a name that\'s not easy to guess. Once subscribed, you can PUT/POST to receive notifications. guess. Once subscribed, you can PUT/POST notifications. </string> <string name="add_dialog_topic_name_hint">Topic name, e.g. phils_alerts</string> <string name="add_dialog_use_another_server">Use another server</string> Loading @@ -85,12 +85,11 @@ You can subscribe to topics from your own server. This option requires a foreground service. </string> <string name="add_dialog_use_another_server_description_noinstant"> You can subscribe to topics from your own server. Simply type in the base URL of your server. You can subscribe to topics from your own server. Type the server URL below. </string> <string name="add_dialog_instant_delivery">Instant delivery in doze mode</string> <string name="add_dialog_instant_delivery_description"> Ensures that messages are immediately delivered, even if the device is inactive or in doze mode. Ensures that messages are immediately delivered, even if the device is inactive. This requires a foreground service. </string> <string name="add_dialog_button_cancel">Cancel</string> Loading @@ -102,7 +101,7 @@ <string name="add_dialog_login_description">This topic requires you to login. Please type in a username and password.</string> <string name="add_dialog_login_username_hint">Username</string> <string name="add_dialog_login_password_hint">Password</string> <string name="add_dialog_login_error_not_authorized">Login failed. User not authorized.</string> <string name="add_dialog_login_error_not_authorized">Login failed. User %1$s not authorized.</string> <string name="add_dialog_login_new_user">New user</string> <!-- Detail activity --> Loading