Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

This Is Latest Release

$version_release = 3.0.2
$version_release = 3.0.3

What's New?? (v2.3.7 → v3.0.2)
What's New?? (v2.3.7 → v3.0.3)

* New Module: frogo-compose-android - Jetpack Compose base classes *
* New Module: frogo-compose-ui - 60+ reusable Compose widgets & templates *
Expand Down Expand Up @@ -82,16 +82,16 @@ dependencyResolutionManagement {
```

### Step 2: Add Dependencies
Latest Version: `3.0.2`
Latest Version: `3.0.3`

```kotlin
dependencies {
// Core SDK
implementation("com.github.frogobox:frogo-sdk:3.0.2")
implementation("com.github.frogobox:frogo-sdk:3.0.3")

// Or specific modules (recommended)
implementation("com.github.frogobox.frogo-sdk:frogo-compose-ui:3.0.2")
implementation("com.github.frogobox.frogo-sdk:frogo-ext-ads:3.0.2")
implementation("com.github.frogobox.frogo-sdk:frogo-compose-ui:3.0.3")
implementation("com.github.frogobox.frogo-sdk:frogo-ext-ads:3.0.3")
}
```

Expand Down
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@
android:name=".appcompose.MainComposeActivity"
android:exported="false" />

<activity
android:name=".appcompose.SampleNavComposeActivity"
android:exported="false" />

<activity
android:name=".appcompose.SampleFragmentComposeActivity"
android:exported="false" />

<activity
android:name=".appcomposeui.MainComposeUiActivity"
android:exported="false" />
Expand Down
71 changes: 61 additions & 10 deletions app/src/main/java/com/frogobox/appcompose/MainComposeActivity.kt
Original file line number Diff line number Diff line change
@@ -1,26 +1,77 @@
package com.frogobox.appcompose

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.frogobox.compose.view.FrogoComposeActivity
import com.frogobox.sdk.ext.startActivityExt

class MainComposeActivity : FrogoComposeActivity() {

@Composable
override fun SetupCompose() {
Box(
modifier = Modifier.Companion.fillMaxSize(),
contentAlignment = Alignment.Companion.Center
) {
Text(
color = Color.Companion.White,
text = "Hello from Frogo Compose!"
)
Scaffold { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Frogo Compose Samples",
style = MaterialTheme.typography.headlineMedium
)

Spacer(modifier = Modifier.height(32.dp))

// Sample 1: Single Activity Compose (this class itself demonstrates FrogoComposeActivity)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
startActivityExt<SampleNavComposeActivity> {}
}
) {
Text("Navigation Compose (NavHost)")
}

Spacer(modifier = Modifier.height(16.dp))

// Sample 2: Fragment Compose hosted in an Activity
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
startActivityExt<SampleFragmentComposeActivity> {}
}
) {
Text("Fragment Compose (SampleFragment)")
}

Spacer(modifier = Modifier.height(16.dp))

// Sample 3: Bottom Sheet Fragment Compose (shown as dialog)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
SampleBottomSheetFragment().show(supportFragmentManager, "bottom_sheet")
}
) {
Text("Bottom Sheet Fragment Compose")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.frogobox.appcompose

import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.frogobox.compose.view.FrogoComposeBottomSheetFragment

class SampleBottomSheetFragment : FrogoComposeBottomSheetFragment() {

@Composable
override fun setupCompose() {
Column(
modifier = Modifier
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
)
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Sample Bottom Sheet Fragment",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "This bottom sheet UI is built entirely using Jetpack Compose hosted inside a BottomSheetDialogFragment.",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
Toast.makeText(context, "Action from Bottom Sheet!", Toast.LENGTH_SHORT).show()
dismiss()
}) {
Text("Dismiss Sheet")
}
}
}
Comment on lines +22 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the Fragment's nullable context property directly inside a Composable is discouraged. Instead, use LocalContext.current to safely retrieve the non-null Context associated with the composition tree.

    @Composable
    override fun setupCompose() {
        val context = androidx.compose.ui.platform.LocalContext.current
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .background(
                    color = MaterialTheme.colorScheme.surface,
                    shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
                )
                .padding(24.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = "Sample Bottom Sheet Fragment",
                style = MaterialTheme.typography.titleLarge,
                color = MaterialTheme.colorScheme.onSurface
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = "This bottom sheet UI is built entirely using Jetpack Compose hosted inside a BottomSheetDialogFragment.",
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onSurfaceVariant
            )
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = {
                Toast.makeText(context, "Action from Bottom Sheet!", Toast.LENGTH_SHORT).show()
                dismiss()
            }) {
                Text("Dismiss Sheet")
            }
        }
    }

}
57 changes: 57 additions & 0 deletions app/src/main/java/com/frogobox/appcompose/SampleFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.frogobox.appcompose

import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.frogobox.compose.view.FrogoComposeFragment

class SampleFragment : FrogoComposeFragment() {

private lateinit var sampleViewModel: SampleViewModel

override fun setupViewModel() {
super.setupViewModel()
sampleViewModel = SampleViewModel()
}
Comment on lines +24 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The sampleViewModel property is declared and directly instantiated via SampleViewModel(), which is an anti-pattern in Android (ViewModels should be managed by the lifecycle/ViewModelStore, not instantiated directly). Furthermore, this property is completely unused because setupCompose correctly retrieves the ViewModel using the viewModel() delegate. We should remove this redundant and incorrect initialization.


@Composable
override fun setupCompose() {
val vm: SampleViewModel = viewModel()
val state by vm.uiState.collectAsState()

Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Sample Fragment Compose", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(8.dp))
Text(text = state.description, style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Counter: ${state.counter}", style = MaterialTheme.typography.titleLarge)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
vm.incrementCounter()
Toast.makeText(context, "Clicked from Compose Fragment!", Toast.LENGTH_SHORT).show()
}) {
Text("Increment Counter")
}
}
}
Comment on lines +31 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In Jetpack Compose, the idiomatic way to obtain the Android Context is by using LocalContext.current. Relying on the Fragment's context property directly can be unsafe because it is nullable (Context?) and can lead to crashes if the Fragment is detached when the click action is triggered.

    @Composable
    override fun setupCompose() {
        val vm: SampleViewModel = viewModel()
        val state by vm.uiState.collectAsState()
        val context = androidx.compose.ui.platform.LocalContext.current

        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(16.dp),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(text = "Sample Fragment Compose", style = MaterialTheme.typography.headlineMedium)
            Spacer(modifier = Modifier.height(8.dp))
            Text(text = state.description, style = MaterialTheme.typography.bodyMedium)
            Spacer(modifier = Modifier.height(16.dp))
            Text(text = "Counter: ${state.counter}", style = MaterialTheme.typography.titleLarge)
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = {
                vm.incrementCounter()
                Toast.makeText(context, "Clicked from Compose Fragment!", Toast.LENGTH_SHORT).show()
            }) {
                Text("Increment Counter")
            }
        }
    }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.frogobox.appcompose

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

/**
* Host Activity for SampleFragment (Compose-based Fragment).
* Demonstrates hosting a FrogoComposeFragment inside an Activity.
*/
class SampleFragmentComposeActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(android.R.id.content, SampleFragment())
.commit()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.frogobox.appcompose

import android.os.Bundle
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.frogobox.compose.view.FrogoComposeNavActivity

class SampleNavComposeActivity : FrogoComposeNavActivity() {

override fun onCreateExt(savedInstanceState: Bundle?) {
super.onCreateExt(savedInstanceState)
// Additional configuration during onCreate if needed
}

@Composable
override fun SetupNavigation(navController: NavHostController) {
Comment on lines +23 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The onCreateExt method is overridden but only calls super.onCreateExt(savedInstanceState) without adding any custom logic. This redundant override can be safely removed to keep the codebase clean.

Suggested change
class SampleNavComposeActivity : FrogoComposeNavActivity() {
override fun onCreateExt(savedInstanceState: Bundle?) {
super.onCreateExt(savedInstanceState)
// Additional configuration during onCreate if needed
}
@Composable
override fun SetupNavigation(navController: NavHostController) {
class SampleNavComposeActivity : FrogoComposeNavActivity() {
@Composable
override fun SetupNavigation(navController: NavHostController) {

NavHost(navController = navController, startDestination = "first") {
composable("first") {
FirstScreen(
onNavigateToSecond = { navController.navigate("second") },
onShowBottomSheet = {
SampleBottomSheetFragment().show(supportFragmentManager, "bottom_sheet")
}
)
}
composable("second") {
SecondScreen(
onBack = { navController.popBackStack() }
)
}
}
}

@Composable
private fun FirstScreen(onNavigateToSecond: () -> Unit, onShowBottomSheet: () -> Unit) {
Scaffold { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "First Screen (Navigation Compose)", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onNavigateToSecond) {
Text("Go to Second Screen")
}
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onShowBottomSheet) {
Text("Open Bottom Sheet Dialog")
}
}
}
}

@Composable
private fun SecondScreen(onBack: () -> Unit) {
Scaffold { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Second Screen (Navigation Compose)", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onBack) {
Text("Go Back")
}
}
}
}
}
23 changes: 23 additions & 0 deletions app/src/main/java/com/frogobox/appcompose/SampleViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.frogobox.appcompose

import com.frogobox.compose.view.FrogoComposeStateViewModel

data class SampleUiState(
val title: String = "Frogo SDK Compose Sample",
val description: String = "This is a demonstration of UDF UI state.",
val counter: Int = 0
)

sealed interface SampleUiEffect {
data class ShowToast(val message: String) : SampleUiEffect
}

class SampleViewModel : FrogoComposeStateViewModel<SampleUiState, SampleUiEffect>(SampleUiState()) {

fun incrementCounter() {
updateState {
copy(counter = counter + 1)
}
emitEffect(SampleUiEffect.ShowToast("Counter incremented to ${currentState.counter}"))
}
}
Loading
Loading