Skip to content

Commit

Permalink
feat(androidApp): create widget to see user profile.
Browse files Browse the repository at this point in the history
  • Loading branch information
GerardPaligot committed May 3, 2024
1 parent 46dad40 commit 74ac77a
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 19 deletions.
13 changes: 12 additions & 1 deletion androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,25 @@
</receiver>

<receiver android:name=".widgets.AppWidgetReceiver"
android:exported="true">
android:exported="true"
android:label="@string/widget_label_agenda">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/agenda_widget_info" />
</receiver>
<receiver android:name=".widgets.NetworkingWidgetReceiver"
android:exported="true"
android:label="@string/widget_label_networking">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/networking_widget_info" />
</receiver>

<provider
android:name="androidx.core.content.FileProvider"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import androidx.glance.appwidget.updateAll
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import androidx.work.Constraints
Expand All @@ -21,11 +23,13 @@ import androidx.work.WorkManager
import com.russhwolf.settings.ExperimentalSettingsApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.launch
import org.gdglille.devfest.android.shared.resources.Resource
import org.gdglille.devfest.android.shared.resources.text_export_subject
import org.gdglille.devfest.android.shared.resources.text_report_app_target
import org.gdglille.devfest.android.shared.resources.text_report_subject
import org.gdglille.devfest.android.theme.Main
import org.gdglille.devfest.android.widgets.NetworkingAppWidget
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.stringResource
import java.io.File
Expand Down Expand Up @@ -61,6 +65,7 @@ class MainActivity : ComponentActivity() {
onDispose {}
}
navController = rememberNavController()
val scope = rememberCoroutineScope()
val exportSubject = stringResource(Resource.string.text_export_subject)
val reportSubject = stringResource(Resource.string.text_report_subject)
val reportAppTarget = stringResource(Resource.string.text_report_app_target)
Expand Down Expand Up @@ -119,6 +124,9 @@ class MainActivity : ComponentActivity() {
.build()
workManager.enqueue(request)
},
onProfileCreated = {
scope.launch { NetworkingAppWidget().updateAll(context = this@MainActivity) }
},
navController = navController
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package org.gdglille.devfest.android

import android.content.Context
import androidx.glance.appwidget.updateAll
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import org.gdglille.devfest.android.widgets.AgendaAppWidget
import org.gdglille.devfest.repositories.AgendaRepository
import org.koin.core.component.KoinComponent

class ScheduleWorkManager(
context: Context,
private val context: Context,
parameters: WorkerParameters,
private val repository: AgendaRepository
) :
CoroutineWorker(context, parameters), KoinComponent {
) : CoroutineWorker(context, parameters), KoinComponent {
override suspend fun doWork(): Result {
return try {
repository.fetchAndStoreAgenda()
AgendaAppWidget().updateAll(context = context)
Result.success()
} catch (_: Throwable) {
Result.failure()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.gdglille.devfest.android.R
import org.gdglille.devfest.android.theme.m3.navigation.Screen
import org.gdglille.devfest.android.widgets.feature.SessionsWidget
import org.gdglille.devfest.repositories.AgendaRepository
import org.gdglille.devfest.repositories.EventRepository
Expand Down Expand Up @@ -46,17 +47,17 @@ class AgendaAppWidget : GlanceAppWidget(), KoinComponent {
iconId = R.drawable.ic_launcher_foreground,
onUpdate = {
prefs.toMutablePreferences().apply {
this.lastUpdate =
Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
.toString()
this.lastUpdate = Clock.System.now()
.toLocalDateTime(TimeZone.currentSystemDefault())
.toString()
}
update(context, id)
},
onItemClick = {
actionStartActivity(
intent = Intent(
Intent.ACTION_VIEW,
"c4h://event/schedules/$it".toUri()
"c4h://event/${Screen.Schedule.route(it)}".toUri()
)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.gdglille.devfest.android.widgets

import android.content.Context
import android.content.Intent
import androidx.core.net.toUri
import androidx.glance.GlanceId
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.provideContent
import org.gdglille.devfest.android.R
import org.gdglille.devfest.android.theme.m3.navigation.Screen
import org.gdglille.devfest.android.widgets.feature.NetworkingWidget
import org.gdglille.devfest.repositories.UserRepository
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class NetworkingAppWidget : GlanceAppWidget(), KoinComponent {
private val userRepository: UserRepository by inject()

override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceTheme {
NetworkingWidget(
userRepository = userRepository,
iconId = R.drawable.ic_launcher_foreground,
onNewProfile = actionStartActivity(
intent = Intent(
Intent.ACTION_VIEW,
"c4h://event/${Screen.NewProfile.route}".toUri()
)
),
onMyProfile = actionStartActivity(
intent = Intent(
Intent.ACTION_VIEW,
"c4h://event/${Screen.MyProfile.route}".toUri()
)
)
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.gdglille.devfest.android.widgets

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver

class NetworkingWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = NetworkingAppWidget()
}
10 changes: 10 additions & 0 deletions androidApp/src/main/res/xml/networking_widget_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/glance_default_loading_layout"
android:minWidth="150dp"
android:minHeight="100dp"
android:targetCellWidth="3"
android:targetCellHeight="2"
android:minResizeWidth="150dp"
android:minResizeHeight="100dp"
android:resizeMode="horizontal|vertical" />
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ class UserRepositoryImpl(
@NativeCoroutineScope
private val coroutineScope: CoroutineScope = MainScope()

override fun fetchProfile(): Flow<UserProfileUi?> = combine(
userDao.fetchProfile(eventId = eventDao.getEventId()),
userDao.fetchUserPreview(eventId = eventDao.getEventId()),
transform = { profile, preview ->
return@combine profile ?: preview
override fun fetchProfile(): Flow<UserProfileUi?> = eventDao.fetchEventId()
.flatMapConcat {
combine(
userDao.fetchProfile(eventId = it),
userDao.fetchUserPreview(eventId = it),
transform = { profile, preview ->
return@combine profile ?: preview
}
)
}
)

override fun saveProfile(email: String, firstName: String, lastName: String, company: String) {
val qrCode = qrCodeGenerator.generate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ fun Main(
onShareClicked: (text: String) -> Unit,
onItineraryClicked: (lat: Double, lng: Double) -> Unit,
onScheduleStarted: () -> Unit,
onProfileCreated: () -> Unit,
navController: NavHostController,
viewModel: MainViewModel = koinViewModel()
) {
Expand All @@ -40,6 +41,7 @@ fun Main(
onShareClicked = onShareClicked,
onItineraryClicked = onItineraryClicked,
onScheduleStarted = onScheduleStarted,
onProfileCreated = onProfileCreated,
savedStateHandle = navController.currentBackStackEntry?.savedStateHandle,
navController = navController
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ fun MainNavigation(
onShareClicked: (text: String) -> Unit,
onItineraryClicked: (lat: Double, lng: Double) -> Unit,
onScheduleStarted: () -> Unit,
onProfileCreated: () -> Unit,
modifier: Modifier = Modifier,
savedStateHandle: SavedStateHandle? = null,
navController: NavHostController = rememberNavController(),
Expand Down Expand Up @@ -217,16 +218,34 @@ fun MainNavigation(
isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
)
}
composable(Screen.MyProfile.route) {
composable(
route = Screen.MyProfile.route,
deepLinks = listOf(
navDeepLink {
uriPattern = "$rootUri/${Screen.MyProfile.route}"
}
)
) {
NetworkingCompactVM(
onCreateProfileClicked = { navController.navigate(Screen.NewProfile.route) },
onContactScannerClicked = { navController.navigate(Screen.ScannerVCard.route) },
onContactExportClicked = onContactExportClicked
)
}
composable(route = Screen.NewProfile.route) {
composable(
route = Screen.NewProfile.route,
deepLinks = listOf(
navDeepLink {
uriPattern = "$rootUri/${Screen.NewProfile.route}"
}
)
) {
ProfileInputVM(
onBackClicked = { navController.popBackStack() }
onBackClicked = { navController.popBackStack() },
onProfileCreated = {
onProfileCreated()
navController.popBackStack()
}
)
}
composable(Screen.PartnerList.route) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.koin.androidx.compose.koinViewModel
@Composable
fun ProfileInputVM(
onBackClicked: () -> Unit,
onProfileCreated: () -> Unit,
modifier: Modifier = Modifier,
viewModel: ProfileInputViewModel = koinViewModel()
) {
Expand All @@ -30,7 +31,7 @@ fun ProfileInputVM(
onValueChanged = viewModel::fieldChanged,
onValidation = {
viewModel.saveProfile()
onBackClicked()
onProfileCreated()
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.gdglille.devfest.android.widgets.feature

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.gdglille.devfest.models.ui.UserProfileUi
import org.gdglille.devfest.repositories.UserRepository

sealed class NetworkingUiState {
data object Loading : NetworkingUiState()
data class Success(val user: UserProfileUi?) : NetworkingUiState()
}

class NetworkingViewModel(
userRepository: UserRepository,
coroutineScope: CoroutineScope = CoroutineScope(Job())
) {
val uiState: StateFlow<NetworkingUiState> = userRepository.fetchProfile()
.map { NetworkingUiState.Success(it) }
.catch { emit(NetworkingUiState.Success(null)) }
.stateIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = NetworkingUiState.Loading
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.gdglille.devfest.android.widgets.feature

import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.glance.GlanceModifier
import androidx.glance.action.Action
import org.gdglille.devfest.android.widgets.screens.NetworkingScreen
import org.gdglille.devfest.android.widgets.ui.Loading
import org.gdglille.devfest.repositories.UserRepository

@Composable
fun NetworkingWidget(
userRepository: UserRepository,
@DrawableRes iconId: Int,
onNewProfile: Action,
onMyProfile: Action,
modifier: GlanceModifier = GlanceModifier
) {
val viewModel = remember { NetworkingViewModel(userRepository) }
when (val uiState = viewModel.uiState.collectAsState().value) {
is NetworkingUiState.Loading -> Loading()
is NetworkingUiState.Success -> {
NetworkingScreen(
userInfo = uiState.user,
iconId = iconId,
onNewProfile = onNewProfile,
onMyProfile = onMyProfile,
modifier = modifier
)
}
}
}
Loading

0 comments on commit 74ac77a

Please sign in to comment.