Compare commits

...

19 commits

Author SHA1 Message Date
Jonathan Klee
3fb7e6427f Merge branch '2585-u-change-package-name' into 'main'
Change Ntfy package name

See merge request e/os/ntfy-android!14
2024-10-24 11:26:08 +00:00
Jonathan Klee
afeebcc4d1 Change package name
So that in case the user had ntfy already installed, we don't
override it. Indeed, the Ntfy icon (which is actually a BlissLauncher3 cache)
would appear after the OTA and the user would not be able to remove it
without cleaning BlissLauncher3 cache.
2024-10-24 10:14:11 +02:00
Jonathan Klee
9add68ed1a Change preference wording 2024-10-10 14:42:34 +02:00
Jonathan Klee
18fb197507 Merge branch '0000-u-keep-same-process' into 'main'
Don't execute MainSettingsActivity in a different process

See merge request e/os/ntfy-android!13
2024-09-26 08:55:37 +00:00
Jonathan Klee
7c00376ae1 Don't execute MainSettingsActivity in a different process
so that share preferences work properly
2024-09-26 08:20:17 +02:00
Jonathan Klee
db299383c8 Use fdroid target 2024-09-25 09:04:23 +02:00
Jonathan Klee
e480230f27 Add gitlab yaml 2024-09-25 08:48:44 +02:00
Jonathan Klee
92611d1e81 Merge branch 'simpleran' into 'main'
Integrate ntfy in /e/OS

See merge request e/os/ntfy-android!12
2024-09-20 16:05:59 +00:00
Jonathan Klee
3b1581a2ba Set status bar theme programatically 2024-09-20 14:57:10 +02:00
Jonathan Klee
78bf83acf4 Extract isEnabled key as resource 2024-09-20 13:42:47 +02:00
Jonathan Klee
5684aba36d Implement NavigationBar theme 2024-09-20 13:31:20 +02:00
Jonathan Klee
61dadcba85 Add translations 2024-09-20 13:31:20 +02:00
Jonathan Klee
35711e7c1b Implement collapsing toolbar 2024-09-20 13:31:20 +02:00
Jonathan Klee
4f97fee55c Adjust styles 2024-09-20 13:31:15 +02:00
Jonathan Klee
fe86329af5 Add Preferences to enable Ntfy in the Settings app 2024-09-19 14:25:02 +02:00
Jonathan Klee
160c4cfd88 Don't show app on the launcher 2024-09-04 17:01:20 +02:00
Jonathan Klee
a4eb268aa0 Exclude app from Android recents app view 2024-09-04 17:00:31 +02:00
Jonathan Klee
95ab609d94 Make the app persistent so that it does not get killed 2024-09-04 16:59:53 +02:00
Jonathan Klee
5aceba6474 Start SubscriberService as background service 2024-09-04 16:59:36 +02:00
17 changed files with 284 additions and 30 deletions

21
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,21 @@
image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest"
stages:
- build
before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
buildRelease:
stage: build
script:
- ./gradlew assembleFdroid
artifacts:
paths:
- app/build/outputs/apk/fdroid/release

View file

@ -10,8 +10,8 @@ android {
compileSdkVersion 33
defaultConfig {
applicationId "io.heckel.ntfy"
minSdkVersion 21
applicationId "foundation.e.ntfy"
minSdkVersion 23
targetSdkVersion 33
versionCode 33
@ -27,6 +27,10 @@ android {
}
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
minifyEnabled true
@ -64,6 +68,7 @@ android {
'-Xjvm-default=all-compatibility' // https://stackoverflow.com/a/71234042/1440785
]
}
namespace "io.heckel.ntfy"
}
// Disables GoogleServices tasks for F-Droid variant
@ -128,4 +133,6 @@ dependencies {
// Image viewer
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
implementation 'foundation.e:elib:0.0.1-alpha11'
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.heckel.ntfy">
xmlns:tools="http://schemas.android.com/tools">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
@ -25,6 +25,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:persistent="true"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
@ -35,10 +36,10 @@
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:exported="true">
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@ -175,5 +176,25 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity
android:name=".ui.MainSettingsActivity"
android:theme="@style/PreferenceTheme"/>
<activity-alias
android:name=".ui.SettingsActivityLink"
android:exported="true"
android:label="@string/eos_settings_title"
android:targetActivity=".ui.MainSettingsActivity">
<intent-filter>
<action android:name="com.android.settings.action.EXTRA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.device" />
<meta-data
android:name="com.android.settings.icon"
android:resource="@drawable/ic_notification" />
</activity-alias>
</application>
</manifest>

View file

@ -10,6 +10,7 @@ import android.os.PowerManager
import android.os.SystemClock
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
@ -88,23 +89,16 @@ class SubscriberService : Service() {
Log.init(this) // Init logs in all entry points
Log.d(TAG, "Subscriber service has been created")
val title = getString(R.string.channel_subscriber_notification_title)
val text = if (BuildConfig.FIREBASE_AVAILABLE) {
getString(R.string.channel_subscriber_notification_instant_text)
} else {
getString(R.string.channel_subscriber_notification_noinstant_text)
}
notificationManager = createNotificationChannel()
serviceNotification = createNotification(title, text)
startForeground(NOTIFICATION_SERVICE_ID, serviceNotification)
}
override fun onDestroy() {
Log.d(TAG, "Subscriber service has been destroyed")
stopService()
sendBroadcast(Intent(this, AutoRestartReceiver::class.java)) // Restart it if necessary!
val preferenceKey = getString(R.string.eos_preference_key_is_enabled)
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(preferenceKey, false)) {
sendBroadcast(Intent(this, AutoRestartReceiver::class.java))
}
super.onDestroy()
}
@ -138,7 +132,6 @@ class SubscriberService : Service() {
}
}
wakeLock = null
stopForeground(true)
stopSelf()
} catch (e: Exception) {
Log.d(TAG, "Service stopped without being started: ${e.message}")

View file

@ -2,10 +2,11 @@ package io.heckel.ntfy.service
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.work.*
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -43,11 +44,17 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Failed, no application found (work ID: ${id})")
return Result.failure()
}
withContext(Dispatchers.IO) {
val app = context.applicationContext as Application
val subscriptionIdsWithInstantStatus = app.repository.getSubscriptionIdsWithInstantStatus()
val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size
val action = if (instantSubscriptions > 0) SubscriberService.Action.START else SubscriberService.Action.STOP
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(app)
val preferenceKey = context.getString(R.string.eos_preference_key_is_enabled)
val action = if (sharedPreferences.getBoolean(preferenceKey, false)) {
SubscriberService.Action.START
} else {
SubscriberService.Action.STOP
}
val serviceState = SubscriberService.readServiceState(context)
if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Action.STOP) {
return@withContext Result.success()
@ -55,7 +62,7 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Starting foreground service with action $action (work ID: ${id})")
Intent(context, SubscriberService::class.java).also {
it.action = action.name
ContextCompat.startForegroundService(context, it)
context.startService(it)
}
}
return Result.success()

View file

@ -0,0 +1,71 @@
package io.heckel.ntfy.ui
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.WindowInsetsController
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import io.heckel.ntfy.R
import io.heckel.ntfy.databinding.MainSettingsActivityBinding
class MainSettingsActivity : AppCompatActivity() {
private lateinit var mBinding: MainSettingsActivityBinding
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = MainSettingsActivityBinding.inflate(layoutInflater)
setContentView(mBinding.root)
setupToolbar()
setSystemBarsAppearance()
showPreferencesFragment()
}
private fun setupToolbar() {
mBinding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun setSystemBarsAppearance() {
val insetsController = window.insetsController ?: return
val isLightMode = isSystemInLightMode()
if (isLightMode) {
insetsController.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
insetsController.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
} else {
insetsController.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
insetsController.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
}
}
private fun isSystemInLightMode(): Boolean {
val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return nightModeFlags != Configuration.UI_MODE_NIGHT_YES
}
private fun showPreferencesFragment() {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, PreferencesFragment())
.commit()
}
}

View file

@ -0,0 +1,37 @@
package io.heckel.ntfy.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.Toolbar
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.util.Log
class PreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_preferences, rootKey)
val preference: SwitchPreferenceCompat? =
findPreference(getString(R.string.eos_preference_key_is_enabled))
preference?.setOnPreferenceChangeListener { _, newValue ->
val isChecked = newValue as Boolean
val intent = Intent(context, SubscriberService::class.java)
intent.action = if (isChecked) {
SubscriberService.Action.START.name
} else {
SubscriberService.Action.STOP.name
}
requireContext().startService(intent)
true
}
}
}

View file

@ -1,31 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="50dp"
android:width="22dp"
android:height="22dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:pathData="m7.8399,6.35c-3.58,0 -6.6469,2.817 -6.6469,6.3983v0.003l0.0351,27.8668 -0.8991,6.6347 12.2261,-3.248L42.9487,44.0049c3.58,0 6.6469,-2.8208 6.6469,-6.4022v-24.8545c0,-3.5803 -3.0652,-6.3967 -6.6438,-6.3983h-0.0031zM7.8399,10.8662h35.1088,0.0031c1.2579,0.0013 2.1277,0.9164 2.1277,1.8821v24.8544c0,0.9666 -0.8714,1.8821 -2.1307,1.8821L11.8924,39.4849l-6.2114,1.8768 0.0633,-0.366 -0.0343,-28.2473c0,-0.9665 0.8706,-1.8821 2.13,-1.8821z"
android:strokeWidth="0.754022"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m11.5278,32.0849l0,-3.346l7.0363,-3.721q0.3397,-0.1732 0.6551,-0.2596 0.3397,-0.1153 0.6066,-0.1732 0.2912,-0.0288 0.5823,-0.0576l0,-0.2308q-0.2912,-0.0288 -0.5823,-0.1153 -0.2669,-0.0576 -0.6066,-0.1443 -0.3154,-0.1153 -0.6551,-0.2884l-7.0363,-3.721l0,-3.3749l10.8699,5.9132l0,3.6056z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m10.9661,15.6112l0,4.8516l7.3742,3.9002c0.0157,0.0077 0.0305,0.0128 0.0461,0.0204 -0.0157,0.0077 -0.0305,0.0128 -0.0461,0.0204l-7.3742,3.9002l0,4.8267l0.7961,-0.4333 11.1995,-6.0969l0,-4.463zM12.0931,17.6933 L21.8346,22.9981l0,2.7446l-9.7414,5.2999l0,-1.8679l6.6912,-3.5416 0.0084,-0.0051c0.1961,-0.0992 0.3826,-0.1724 0.5531,-0.2191l0.0127,0l0.0167,-0.0051c0.2034,-0.0691 0.3777,-0.1209 0.5279,-0.1545l1.0684,-0.1046l0,-1.4644l-0.5154,-0.0497c-0.1632,-0.0153 -0.3288,-0.0505 -0.4944,-0.0997l-0.0167,-0.0051 -0.0167,-0.0051c-0.1632,-0.0352 -0.3552,-0.0811 -0.5656,-0.1344 -0.1802,-0.0668 -0.3706,-0.1479 -0.5698,-0.2492l-0.0084,-0.0051 -6.6912,-3.5416z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m26.7503,30.9206l11.6118,0l0,3.1388L26.7503,34.0594Z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
<path
android:pathData="m26.1875,30.2775l0,0.6427 0,3.7845l12.7371,0l0,-4.4272zM27.3113,31.563l10.4896,0l0,1.8515l-10.4896,0z"
android:strokeWidth="0.525121"
android:fillColor="#FFFFFFFF"
android:fillColor="?android:attr/colorControlNormal"
android:strokeColor="#00000000"/>
</vector>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
style="?attr/collapsingToolbarLayoutLargeStyle"
android:background="@color/e_background"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
<com.google.android.material.appbar.MaterialToolbar
app:navigationIcon="@drawable/e_ic_back"
app:title="@string/eos_settings_title"
android:id="@+id/toolbar"
android:background="@color/e_background"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View file

@ -343,4 +343,6 @@
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy arbeitet nicht als UnifiedPush-Distributor</string>
<string name="settings_advanced_unifiedpush_title">UnifiedPush aktivieren</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy arbeitet als UnifiedPush-Distributor</string>
<string name="eos_settings_enable_title">Den Verteiler aktivieren</string>
<string name="eos_settings_enable_description">Es ermöglicht Drittanbieteranwendungen, UnifiedPush-Benachrichtigungen zu empfangen</string>
</resources>

View file

@ -343,4 +343,6 @@
<string name="settings_advanced_unifiedpush_title">Activar UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy actuará como distribuidor UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy no actuará como distribuidor UnifiedPush</string>
<string name="eos_settings_enable_title">Habilitar el distribuidor</string>
<string name="eos_settings_enable_description">Permite a las aplicaciones de terceros recibir notificaciones de UnifiedPush</string>
</resources>

View file

@ -343,4 +343,6 @@
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy agira comme un distributeur UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy n\'agira pas comme un distributeur UnifiedPush</string>
<string name="settings_advanced_unifiedpush_title">Activer le \"UnifiedPush\"</string>
<string name="eos_settings_enable_title">Activer le distributeur</string>
<string name="eos_settings_enable_description">Cela permet aux applications tierces de recevoir des notifications UnifiedPush</string>
</resources>

View file

@ -330,4 +330,6 @@
<string name="channel_notifications_group_default_name">Default</string>
<string name="main_menu_donate_title">Dona 💸</string>
<string name="detail_item_cannot_open_apk">Le app non possono più essere installate: devono essere scaricate via browser. Vedi l\'issue #531 per dettagli.</string>
<string name="eos_settings_enable_title">Abilitare il distributore</string>
<string name="eos_settings_enable_description">Consente alle applicazioni di terze parti di ricevere notifiche UnifiedPush</string>
</resources>

View file

@ -391,4 +391,10 @@
<string name="user_dialog_button_cancel">Cancel</string>
<string name="user_dialog_button_delete">Delete user</string>
<string name="user_dialog_button_save">Save</string>
<!-- /e/OS integration preferences -->
<string name="eos_preference_key_is_enabled" translatable="false">isEnabled</string>
<string name="eos_settings_title" translatable="false">UnifiedPush</string>
<string name="eos_settings_enable_title" translatable="true">Enable the distributor</string>
<string name="eos_settings_enable_description" translatable="true">It allows 3rd party applications to receive UnifiedPush notifications</string>
</resources>

View file

@ -30,4 +30,26 @@
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5dp</item>
</style>
<style name="PreferenceTheme" parent="PreferenceTheme.Base"/>
<style name="PreferenceTheme.Base" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/e_action_bar</item>
<item name="colorPrimaryDark">@color/e_action_bar</item>
<item name="colorAccent">@color/e_accent</item>
<item name="android:textColorPrimary">@color/e_primary_text_color</item>
<item name="android:textColorSecondary">@color/e_secondary_text_color</item>
<item name="android:textColorPrimaryInverse">@color/e_background</item>
<item name="android:windowBackground">@color/e_background</item>
<item name="colorControlActivated">@color/e_accent</item>
<item name="colorButtonNormal">@color/e_icon_color</item>
<item name="colorControlHighlight">@color/e_icon_color</item>
<item name="homeAsUpIndicator">@drawable/e_ic_back</item>
<item name="android:homeAsUpIndicator">@drawable/e_ic_back</item>
<item name="android:popupBackground">@color/e_floating_background</item>
<item name="android:divider">@color/e_divider_color</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@color/e_background</item>
<item name="switchStyle">@style/ETheme.Switch</item>
</style>
</resources>

View file

@ -0,0 +1,11 @@
<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:icon="@drawable/ic_notification"
app:key="@string/eos_preference_key_is_enabled"
app:defaultValue="false"
app:title="@string/eos_settings_enable_title"
app:summary="@string/eos_settings_enable_description" />
</PreferenceScreen>

View file

@ -18,6 +18,7 @@ allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven'}
maven { url "https://jitpack.io" } // For StfalconImageViewer
}
}