pull/4008/merge
acrodriguezs1 2 weeks ago committed by GitHub
commit 544c274e8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,3 +1,15 @@
# EVIDENCE OF CHANGES IN THE APP REPOSITORY MOVILE APP DEVELOPMENT COURSE
## ISSUE
<img width="1726" height="933" alt="image" src="https://github.com/user-attachments/assets/afd51f5f-b5a4-4020-85db-476d20b71d36" />
## PULL REQUEST
<img width="1715" height="918" alt="image" src="https://github.com/user-attachments/assets/6430abe7-b326-4f83-b16d-5dd7e823e296" />
Astrid was a popular cross-platform productivity service that was [acquired](https://web.archive.org/web/20130811052500/http://blog.astrid.com/blog/2013/05/01/yahoo-acquires-astrid/) and [discontinued](https://techcrunch.com/2013/07/06/astrid-goes-dark-august-5-goodnight-sweet-squid/) in 2013. The source code from Astrid's open source Android app serves as the basis of Tasks. Astrid was a popular cross-platform productivity service that was [acquired](https://web.archive.org/web/20130811052500/http://blog.astrid.com/blog/2013/05/01/yahoo-acquires-astrid/) and [discontinued](https://techcrunch.com/2013/07/06/astrid-goes-dark-august-5-goodnight-sweet-squid/) in 2013. The source code from Astrid's open source Android app serves as the basis of Tasks.
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" [<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"

@ -7,6 +7,7 @@ import androidx.activity.compose.LocalActivity
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity.RESULT_OK import androidx.appcompat.app.AppCompatActivity.RESULT_OK
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -20,8 +21,6 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerState
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -44,6 +43,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.IntentCompat.getParcelableExtra import androidx.core.content.IntentCompat.getParcelableExtra
import androidx.fragment.compose.AndroidFragment import androidx.fragment.compose.AndroidFragment
@ -123,194 +123,183 @@ fun HomeScreen(
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
Box(modifier = Modifier.fillMaxSize()) { TaskListDrawer(
TaskListDrawer( arrangement = if (state.menuQuery.isBlank()) Arrangement.Top else Arrangement.Bottom,
arrangement = if (state.menuQuery.isBlank()) Arrangement.Top else Arrangement.Bottom, filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems,
filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems, onClick = {
onClick = { when (it) {
when (it) { is DrawerItem.Filter -> {
is DrawerItem.Filter -> { viewModel.setFilter(it.filter)
viewModel.setFilter(it.filter) scope.launch {
scope.launch { drawerState.close()
drawerState.close() keyboard?.hide()
keyboard?.hide()
}
} }
}
is DrawerItem.Header -> { is DrawerItem.Header -> {
viewModel.toggleCollapsed(it.header) viewModel.toggleCollapsed(it.header)
}
} }
}, }
onAddClick = { },
scope.launch { onAddClick = {
drawerState.close() scope.launch {
when (it.header.addIntentRc) { drawerState.close()
FilterProvider.REQUEST_NEW_FILTER -> when (it.header.addIntentRc) {
showNewFilterDialog() FilterProvider.REQUEST_NEW_FILTER ->
showNewFilterDialog()
REQUEST_NEW_PLACE -> REQUEST_NEW_PLACE ->
newList.launch(Intent(context, LocationPickerActivity::class.java)) newList.launch(Intent(context, LocationPickerActivity::class.java))
REQUEST_NEW_TAGS -> REQUEST_NEW_TAGS ->
newList.launch(Intent(context, TagSettingsActivity::class.java)) newList.launch(Intent(context, TagSettingsActivity::class.java))
REQUEST_NEW_LIST -> REQUEST_NEW_LIST ->
when (it.header.subheaderType) { when (it.header.subheaderType) {
NavigationDrawerSubheader.SubheaderType.CALDAV, NavigationDrawerSubheader.SubheaderType.CALDAV,
NavigationDrawerSubheader.SubheaderType.TASKS -> NavigationDrawerSubheader.SubheaderType.TASKS ->
viewModel viewModel
.getAccount(it.header.id.toLong()) .getAccount(it.header.id.toLong())
?.let { ?.let {
newList.launch( newList.launch(
Intent(context, it.listSettingsClass()) Intent(context, it.listSettingsClass())
.putExtra(EXTRA_CALDAV_ACCOUNT, it) .putExtra(EXTRA_CALDAV_ACCOUNT, it)
) )
} }
else -> {} else -> {}
} }
else -> Timber.e("Unhandled request code: $it") else -> Timber.e("Unhandled request code: $it")
}
} }
}, }
onErrorClick = { },
context.startActivity(Intent(context, MainPreferences::class.java)) onErrorClick = {
}, context.startActivity(Intent(context, MainPreferences::class.java))
searchBar = { },
MenuSearchBar( searchBar = {
begForMoney = state.begForMoney, MenuSearchBar(
onDrawerAction = { begForMoney = state.begForMoney,
scope.launch { onDrawerAction = {
drawerState.close() scope.launch {
when (it) { drawerState.close()
DrawerAction.PURCHASE -> when (it) {
if (TasksApplication.IS_GENERIC) DrawerAction.PURCHASE ->
context.openUri(R.string.url_donate) if (TasksApplication.IS_GENERIC)
else context.openUri(R.string.url_donate)
context.startActivity( else
Intent(
context,
PurchaseActivity::class.java
)
)
DrawerAction.HELP_AND_FEEDBACK ->
context.startActivity( context.startActivity(
Intent( Intent(
context, context,
HelpAndFeedback::class.java PurchaseActivity::class.java
) )
) )
}
}
},
query = state.menuQuery,
onQueryChange = { viewModel.queryMenu(it) },
)
},
)
SystemBarScrim( DrawerAction.HELP_AND_FEEDBACK ->
modifier = Modifier context.startActivity(
.windowInsetsTopHeight(WindowInsets.systemBars) Intent(
.align(Alignment.TopCenter) context,
) HelpAndFeedback::class.java
SystemBarScrim( )
modifier = Modifier )
.windowInsetsBottomHeight(WindowInsets.systemBars) }
.align(Alignment.BottomCenter), }
) },
} query = state.menuQuery,
onQueryChange = { viewModel.queryMenu(it) },
)
},
)
} }
} }
) { ) {
Box(modifier = Modifier.fillMaxSize()) { val scope = rememberCoroutineScope()
val scope = rememberCoroutineScope()
ListDetailPaneScaffold(
directive = navigator.scaffoldDirective, ListDetailPaneScaffold(
value = navigator.scaffoldValue, directive = navigator.scaffoldDirective,
listPane = { value = navigator.scaffoldValue,
key (state.filter) { listPane = {
val fragment = remember { mutableStateOf<TaskListFragment?>(null) } key (state.filter) {
val keyboardOpen = rememberImeState() val fragment = remember { mutableStateOf<TaskListFragment?>(null) }
AndroidFragment<TaskListFragment>( val keyboardOpen = rememberImeState()
fragmentState = rememberFragmentState(), AndroidFragment<TaskListFragment>(
arguments = remember(state.filter) { fragmentState = rememberFragmentState(),
Bundle() arguments = remember(state.filter) {
.apply { putParcelable(EXTRA_FILTER, state.filter) } Bundle()
}, .apply { putParcelable(EXTRA_FILTER, state.filter) }
modifier = Modifier },
.fillMaxSize()
.imePadding(),
) { tlf ->
fragment.value = tlf
tlf.applyInsets(windowInsets.value)
tlf.setNavigationClickListener {
scope.launch { drawerState.open() }
}
}
LaunchedEffect(fragment, windowInsets, keyboardOpen.value) {
fragment.value?.applyInsets(
if (keyboardOpen.value) {
PaddingValues(
top = windowInsets.value.calculateTopPadding(),
)
} else {
windowInsets.value
}
)
}
}
},
detailPane = {
val direction = LocalLayoutDirection.current
Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding( .imePadding(),
top = windowInsets.value.calculateTopPadding(), ) { tlf ->
start = windowInsets.value.calculateStartPadding(direction), fragment.value = tlf
end = windowInsets.value.calculateEndPadding(direction), tlf.applyInsets(windowInsets.value)
bottom = if (rememberImeState().value) tlf.setNavigationClickListener {
WindowInsets.ime.asPaddingValues().calculateBottomPadding() scope.launch { drawerState.open() }
else }
windowInsets.value.calculateBottomPadding() }
), LaunchedEffect(fragment, windowInsets, keyboardOpen.value) {
contentAlignment = Alignment.Center, fragment.value?.applyInsets(
) { if (keyboardOpen.value) {
if (state.task == null) { PaddingValues(
if (isListVisible && isDetailVisible) { top = windowInsets.value.calculateTopPadding(),
Icon(
painter = painterResource(org.tasks.kmp.R.drawable.ic_launcher_no_shadow_foreground),
contentDescription = null,
modifier = Modifier.size(192.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
} else {
key(state.task) {
AndroidFragment<TaskEditFragment>(
fragmentState = rememberFragmentState(),
arguments = remember(state.task) {
Bundle()
.apply { putParcelable(EXTRA_TASK, state.task) }
},
modifier = Modifier.fillMaxSize(),
) )
} else {
windowInsets.value
} }
} )
} }
}, }
) },
detailPane = {
val direction = LocalLayoutDirection.current
val imeOpen = rememberImeState().value
val insets = windowInsets.value
val topPad = insets.calculateTopPadding()
val startPad = insets.calculateStartPadding(direction)
val endPad = insets.calculateEndPadding(direction)
val bottomPad = if (imeOpen)
WindowInsets.ime.asPaddingValues().calculateBottomPadding()
else
insets.calculateBottomPadding()
SystemBarScrim( Box(
modifier = Modifier modifier = Modifier
.windowInsetsTopHeight(WindowInsets.systemBars) .fillMaxSize()
.align(Alignment.TopCenter),
) .padding(
} top = topPad,
start = startPad,
end = endPad,
bottom = bottomPad
),
contentAlignment = Alignment.Center,
) {
if (state.task == null) {
if (isListVisible && isDetailVisible) {
Icon(
painter = painterResource(org.tasks.kmp.R.drawable.ic_launcher_no_shadow_foreground),
contentDescription = null,
modifier = Modifier.size(192.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
} else {
key(state.task) {
AndroidFragment<TaskEditFragment>(
fragmentState = rememberFragmentState(),
arguments = remember(state.task) {
Bundle()
.apply { putParcelable(EXTRA_TASK, state.task) }
},
modifier = Modifier.fillMaxSize(),
)
}
}
}
},
)
} }
} }
} }

@ -1,11 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="match_parent"
android:layout_height="fill_parent" android:layout_height="match_parent"
android:orientation="vertical">
android:background="?attr/colorSurface"
android:orientation="vertical">
<include <include
android:id="@+id/toolbar" android:id="@+id/toolbar"
layout="@layout/toolbar" /> layout="@layout/toolbar" />
</LinearLayout> </LinearLayout>

@ -4,15 +4,15 @@
** See the file "LICENSE" for the full license governing this code. ** See the file "LICENSE" for the full license governing this code.
--> -->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/task_list_coordinator" android:id="@+id/task_list_coordinator"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:descendantFocusability="beforeDescendants" android:descendantFocusability="beforeDescendants"
android:focusable="true" android:focusable="true"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbarlayout" android:id="@+id/appbarlayout"
@ -32,10 +32,10 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.compose.ui.platform.ComposeView <androidx.compose.ui.platform.ComposeView
android:id="@+id/banner" android:id="@+id/banner"
@ -43,14 +43,14 @@
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="horizontal">
<FrameLayout <FrameLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="100"> android:layout_weight="100">
<include <include
android:id="@+id/body_standard" android:id="@+id/body_standard"

@ -27,12 +27,15 @@
tools:padding="@dimen/widget_padding"/> tools:padding="@dimen/widget_padding"/>
<ImageButton <ImageButton
android:id="@+id/widget_reconfigure" android:id="@+id/widget_reconfigure"
style="@style/WidgetButton" style="@style/WidgetButton"
android:layout_toStartOf="@id/widget_button" android:layout_toStartOf="@id/widget_button"
android:src="@drawable/ic_outline_settings_24px" android:src="@drawable/ic_outline_settings_24px"
android:contentDescription="@string/widget_settings" android:contentDescription="@string/widget_settings"
tools:padding="@dimen/widget_padding"/> android:background="@android:color/transparent"
android:padding="@dimen/widget_padding"
/>
<TextView <TextView
android:id="@+id/widget_title" android:id="@+id/widget_title"

@ -30,6 +30,7 @@
</style> </style>
<style name="Tasks" parent="TasksBase"> <style name="Tasks" parent="TasksBase">
<item name="android:windowBackground">@null</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLightStatusBar">@bool/light_status_bar</item> <item name="android:windowLightStatusBar">@bool/light_status_bar</item>
<item name="android:statusBarColor">@color/content_background</item> <item name="android:statusBarColor">@color/content_background</item>

Loading…
Cancel
Save