Skip to content

Commit

Permalink
feat: Add launcher shortcuts for quick access to some functionalities (
Browse files Browse the repository at this point in the history
…#139)

Also added website link in about page & optimized miui check method
---------
Signed-off-by: starry-shivam <[email protected]>
  • Loading branch information
starry-shivam authored May 25, 2024
1 parent 0fe3a0a commit 4bb254f
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 15 deletions.
4 changes: 2 additions & 2 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="greenstash_lc_shortcut" />
</intent-filter>
</activity>

<activity
Expand Down
53 changes: 53 additions & 0 deletions app/src/main/java/com/starry/greenstash/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@

package com.starry.greenstash

import android.content.pm.ShortcutManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
Expand All @@ -36,15 +39,20 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.starry.greenstash.ui.navigation.NavGraph
import com.starry.greenstash.ui.navigation.Screens
import com.starry.greenstash.ui.screens.other.AppLockedScreen
import com.starry.greenstash.ui.screens.settings.SettingsViewModel
import com.starry.greenstash.ui.theme.AdjustEdgeToEdge
Expand Down Expand Up @@ -134,6 +142,8 @@ class MainActivity : AppCompatActivity() {

// set app contents based on the value of showAppContents.
setAppContents(showAppContents)
// Set launcher shortcuts on Android 7.1 and above.
setAppShortcuts()
}

private fun setAppContents(showAppContents: State<Boolean>) {
Expand All @@ -160,6 +170,15 @@ class MainActivity : AppCompatActivity() {
// show app contents only if user has authenticated.
if (showAppContents.value) {
NavGraph(navController = navController, startDestination)
// Handle and navigate to shortcut destinations after the UI is
// properly loaded.
val shouldHandleShortCut = remember { mutableStateOf(false) }
LaunchedEffect(key1 = true) {
shouldHandleShortCut.value = true
}
if (shouldHandleShortCut.value) {
HandleShortcutIntent(navController)
}
} else {
// show app locked screen if user has not authenticated.
AppLockedScreen(onAuthRequest = {
Expand All @@ -171,4 +190,38 @@ class MainActivity : AppCompatActivity() {
}
}
}

@Composable
private fun HandleShortcutIntent(navController: NavController) {
val data = intent.data
if (data != null && data.scheme == MainViewModel.LAUNCHER_SHORTCUT_SCHEME) {
val goalId = intent.getLongExtra(MainViewModel.LC_SHORTCUT_GOAL_ID, -100)
if (goalId != -100L) {
navController.navigate(Screens.GoalInfoScreen.withGoalId(goalId.toString()))
return
}
if (intent.getBooleanExtra(MainViewModel.LC_SHORTCUT_NEW_GOAL, false)) {
navController.navigate(Screens.InputScreen.route)
}
}
}

private fun setAppShortcuts() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
return // shortcuts are not supported on this device.
}

val shortcutManager = getSystemService(ShortcutManager::class.java)
mainViewModel.buildDynamicShortcuts(
context = this,
limit = shortcutManager.maxShortcutCountPerActivity,
onComplete = { shortcuts ->
try {
shortcutManager.dynamicShortcuts = shortcuts
} catch (e: IllegalArgumentException) {
Log.e("MainActivity", "setAppShortcuts: ${e.message}")
}
}
)
}
}
71 changes: 66 additions & 5 deletions app/src/main/java/com/starry/greenstash/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@

package com.starry.greenstash

import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
Expand All @@ -39,6 +46,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject


Expand All @@ -62,6 +70,17 @@ class MainViewModel @Inject constructor(
mutableStateOf(Screens.WelcomeScreen.route)
val startDestination: State<String> = _startDestination

companion object {
// Must be same as the one in AndroidManifest.xml
const val LAUNCHER_SHORTCUT_SCHEME = "greenstash_lc_shortcut"

// Key to get goalId from intent.
const val LC_SHORTCUT_GOAL_ID = "lc_shortcut_goal_id"

// Key to detect new goal shortcut.
const val LC_SHORTCUT_NEW_GOAL = "lc_shortcut_new_goal"
}

init {
viewModelScope.launch {
welcomeDataStore.readOnBoardingState().collect { completed ->
Expand All @@ -71,21 +90,63 @@ class MainViewModel @Inject constructor(
_startDestination.value = Screens.WelcomeScreen.route
}

delay(150)
delay(100)
_isLoading.value = false
}
}
}

fun isAppUnlocked(): Boolean = _appUnlocked

fun setAppUnlocked(value: Boolean) {
_appUnlocked = value
}

fun refreshReminders() {
viewModelScope.launch(Dispatchers.IO) {
reminderManager.checkAndScheduleReminders(goalDao.getAllGoals())
}
}

fun isAppUnlocked(): Boolean = _appUnlocked
@RequiresApi(Build.VERSION_CODES.N_MR1)
fun buildDynamicShortcuts(
context: Context,
limit: Int,
onComplete: (List<ShortcutInfo>) -> Unit
) {
viewModelScope.launch(Dispatchers.IO) {
// get all goals and filter top goals up to limit by priority
val goals = goalDao.getAllGoals()
val topGoals = goals.sortedByDescending { goalItem ->
goalItem.goal.priority.value
}.take(limit - 1).map { it.goal } // -1 for new goal shortcut

val newGoalShortcut = ShortcutInfo.Builder(context, "new_goal").apply {
setShortLabel(context.getString(R.string.new_goal_fab))
setIcon(Icon.createWithResource(context, R.drawable.ic_shortcut_new_goal))
setIntent(Intent().apply {
action = Intent.ACTION_VIEW
data = Uri.parse("$LAUNCHER_SHORTCUT_SCHEME://newGoal")
putExtra(LC_SHORTCUT_NEW_GOAL, true)
})
}.build()

val shortCuts = listOf(newGoalShortcut) + topGoals.map { goal ->
val intent = Intent().apply {
action = Intent.ACTION_VIEW
data = Uri.parse("$LAUNCHER_SHORTCUT_SCHEME://goalId")
putExtra(LC_SHORTCUT_GOAL_ID, goal.goalId)
}

ShortcutInfo.Builder(context, goal.goalId.toString()).apply {
setShortLabel(goal.title)
setIcon(Icon.createWithResource(context, R.drawable.ic_widget_config_item))
setIntent(intent)
}.build()
}

withContext(Dispatchers.Main) { onComplete(shortCuts) }
}

fun setAppUnlocked(value: Boolean) {
_appUnlocked = value
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.compose.material.icons.automirrored.filled.Notes
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.PrivacyTip
import androidx.compose.material.icons.filled.Web
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
Expand All @@ -54,7 +55,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.starry.greenstash.BuildConfig
import com.starry.greenstash.R
import com.starry.greenstash.ui.theme.greenstashFont
Expand All @@ -63,6 +66,7 @@ import com.starry.greenstash.utils.weakHapticFeedback

sealed class AboutLinks(val url: String) {
data object ReadMe : AboutLinks("https://github.com/Pool-Of-Tears/GreenStash")
data object Website : AboutLinks("https://pooloftears.in")
data object PrivacyPolicy :
AboutLinks("https://github.com/Pool-Of-Tears/GreenStash/blob/main/legal/PRIVACY-POLICY.md")

Expand Down Expand Up @@ -118,6 +122,13 @@ fun AboutScreen(navController: NavController) {
onClick = { Utils.openWebLink(context, AboutLinks.ReadMe.url) }
)
}
item {
SettingsItem(title = stringResource(id = R.string.about_website_title),
description = stringResource(id = R.string.about_website_desc),
icon = Icons.Filled.Web,
onClick = { Utils.openWebLink(context, AboutLinks.Website.url)}
)
}
item {
SettingsItem(title = stringResource(id = R.string.about_privacy_title),
description = stringResource(id = R.string.about_privacy_desc),
Expand Down Expand Up @@ -174,3 +185,9 @@ fun getVersionReport(): String {
.append("Supported ABIs: ${Build.SUPPORTED_ABIS.contentToString()}\n")
.toString()
}

@Preview
@Composable
fun AboutScreenPV() {
AboutScreen(navController = rememberNavController())
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/starry/greenstash/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ object Utils {
* @return True if the device is running on MIUI, false otherwise
*/
fun isMiui(excludeHyperOS: Boolean = true): Boolean {
// Check if the device is manufactured by Xiaomi, Redmi, or POCO.
val brand = Build.BRAND.lowercase()
if (!setOf("xiaomi", "redmi", "poco").contains(brand)) return false
// Check if the device is running on MIUI.
val isMiui = !getProperty("ro.miui.ui.version.name").isNullOrBlank()
val isHyperOS = !getProperty("ro.mi.os.version.name").isNullOrBlank()
return isMiui && (!excludeHyperOS || !isHyperOS)
Expand Down
15 changes: 7 additions & 8 deletions app/src/main/java/com/starry/greenstash/widget/GoalWidget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,22 @@ class GoalWidget : AppWidgetProvider() {
context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray
) {
initialiseVm(context)
for (appWidgetId in appWidgetIds) {
appWidgetIds.forEach { appWidgetId ->
val widgetOptions = appWidgetManager.getAppWidgetOptions(appWidgetId)
val minHeight = widgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 0)
viewModel.getGoalFromWidgetId(appWidgetId) { goalItem ->
updateWidgetContents(context, appWidgetId, goalItem)
updateWidgetContents(context, appWidgetId, goalItem, minHeight)
}
}
}

override fun onReceive(context: Context, intent: Intent?) {
super.onReceive(context, intent)
// Update widget when the screen is turned on.
if (intent?.action.equals(Intent.ACTION_SCREEN_ON)) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val ids = appWidgetManager.getAppWidgetIds(
ComponentName(
context, GoalWidget::class.java
)
ComponentName(context, GoalWidget::class.java)
)
if (ids.isNotEmpty()) {
onUpdate(context, appWidgetManager, ids)
Expand Down Expand Up @@ -148,9 +149,7 @@ class GoalWidget : AppWidgetProvider() {
}

val pendingIntent = PendingIntent.getBroadcast(
context,
appWidgetId,
intent,
context, appWidgetId, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widgetLayout, pendingIntent)
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_shortcut_new_goal.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="#434343"
android:fillType="evenOdd"
android:pathData="M12,22C17.523,22 22,17.523 22,12C22,6.477 17.523,2 12,2C6.477,2 2,6.477 2,12C2,17.523 6.477,22 12,22ZM12.75,9C12.75,8.586 12.414,8.25 12,8.25C11.586,8.25 11.25,8.586 11.25,9L11.25,11.25H9C8.586,11.25 8.25,11.586 8.25,12C8.25,12.414 8.586,12.75 9,12.75H11.25V15C11.25,15.414 11.586,15.75 12,15.75C12.414,15.75 12.75,15.414 12.75,15L12.75,12.75H15C15.414,12.75 15.75,12.414 15.75,12C15.75,11.586 15.414,11.25 15,11.25H12.75V9Z" />

</vector>
2 changes: 2 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
<string name="about_screen_header">Acerca de</string>
<string name="about_readme_title">LÉEME</string>
<string name="about_readme_desc">Mira el repositorio de Github y el README de la aplicación.</string>
<string name="about_website_title">Sitio web</string>
<string name="about_website_desc">¡Visita nuestro sitio web para obtener más información y explorar nuestras otras aplicaciones y proyectos!</string>
<string name="about_privacy_title">Política de Seguridad</string>
<string name="about_privacy_desc">Lee nuestra última política de seguridad y términos de servicio en Github.</string>
<string name="about_gh_issue_title">Solicitudes de Github</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
<string name="about_screen_header">Informazioni</string>
<string name="about_readme_title">README</string>
<string name="about_readme_desc">Vai a vedere il repository di GitHub e il README dell\'app.</string>
<string name="about_website_title">Sito Web</string>
<string name="about_website_desc">Visita il nostro sito web per ulteriori informazioni ed esplora le nostre altre app e progetti!</string>
<string name="about_privacy_title">Informativa sulla Privacy</string>
<string name="about_privacy_desc">Leggi l\'informativa sulla privacy e i termini di servizio su GitHub.</string>
<string name="about_gh_issue_title">Issues di GitHub</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@
<string name="about_screen_header">Sobre</string>
<string name="about_readme_title">README</string>
<string name="about_readme_desc">Confira o repositório GitHub e o README do aplicativo.</string>
<string name="about_website_title">Website</string>
<string name="about_website_desc">Visite nosso site para mais informações e explore nossos outros aplicativos e projetos!</string>
<string name="about_privacy_title">Política de Privacidade</string>
<string name="about_privacy_desc">Leia nossa política de privacidade mais recente e termos de serviço no GitHub.</string>
<string name="about_gh_issue_title">Problemas no GitHub</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
<string name="about_screen_header">О приложении</string>
<string name="about_readme_title">ПРОЧТИ МЕНЯ</string>
<string name="about_readme_desc">Ознакомьтесь с репозиторием на GitHub и README файл приложения.</string>
<string name="about_website_title">Веб-сайт</string>
<string name="about_website_desc">Посетите наш веб-сайт, чтобы получить больше информации и изучить наши другие приложения и проекты!</string>
<string name="about_privacy_title">Политика конфиденциальности</string>
<string name="about_privacy_desc">Ознакомьтесь с нашей последней политикой конфиденциальности и условиями обслуживания на GitHub.</string>
<string name="about_gh_issue_title">Багтрекер GitHub</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-tr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@
<string name="about_screen_header">Hakkında</string>
<string name="about_readme_title">README</string>
<string name="about_readme_desc">Github deposuna ve uygulamanın README\'sine göz at.</string>
<string name="about_website_title">Web Sitesi</string>
<string name="about_website_desc">Daha fazla bilgi için web sitemizi ziyaret edin ve diğer uygulamalarımızı ve projelerimizi keşfedin!</string>
<string name="about_privacy_title">Gizlilik Sözleşmesi</string>
<string name="about_privacy_desc">Github\'da güncel gizlilik sözleşmemizi ve kullanım koşullarını oku.</string>
<string name="about_gh_issue_title">Github Sorunları</string>
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
<string name="about_screen_header">关于</string>
<string name="about_readme_title">README</string>
<string name="about_readme_desc">在Github上查看应用及其README文档。</string>
<string name="about_website_title">网站</string>
<string name="about_website_desc">查看我们的网站获取更多信息,探索我们的其他应用和项目!</string>
<string name="about_privacy_title">隐私政策</string>
<string name="about_privacy_desc">在Github上阅读我们最新的隐私政策和服务条款。</string>
<string name="about_gh_issue_title">Github问题</string>
Expand Down
Loading

0 comments on commit 4bb254f

Please sign in to comment.