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
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ class KMPApiBridge @Inject constructor(
activity.startActivity(intent)
}
}
override fun openStoreView() {
activity?.let {
Timber.d("Opening store view")
val intent = Intent(activity.context, MainActivity::class.java)
intent.putExtra("navigationPath", Routes.Home.STORE_WATCHFACES)
activity.startActivity(intent)
}
}
}
37 changes: 30 additions & 7 deletions android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@
package io.rebble.cobble.pigeons;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;

/** Generated class from Pigeon. */
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"})
public class Pigeons {
Expand Down Expand Up @@ -5458,6 +5457,8 @@ public interface KMPApi {

void openLockerView();

void openStoreView();

/** The codec used by KMPApi. */
static @NonNull MessageCodec<Object> getCodec() {
return KMPApiCodec.INSTANCE;
Expand Down Expand Up @@ -5500,6 +5501,28 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable KMPApi api
api.openLockerView();
wrapped.add(0, null);
}
catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
}
reply.reply(wrapped);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.KMPApi.openStoreView", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
try {
api.openStoreView();
wrapped.add(0, null);
}
catch (Throwable exception) {
ArrayList<Object> wrappedError = wrapError(exception);
wrapped = wrappedError;
Expand Down
1 change: 1 addition & 0 deletions android/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ kotlin {
implementation(libs.compose.viewmodel)
implementation(libs.compose.components.resources)
implementation(libs.compose.components.reorderable)
api("io.github.kevinnzou:compose-webview-multiplatform:1.9.40")
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ class AuthClient(

return res.body() ?: error("Failed to deserialize account")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ object RWS: KoinComponent {
private val token: StateFlow<CurrentToken> by inject(named("currentToken"))
private val scope = CoroutineScope(Dispatchers.Default)

val currentTokenFlow: StateFlow<CurrentToken> get() = token

val appstoreClientFlow = token.map {
it.tokenOrNull?.let { t -> AppstoreClient("https://appstore-api.$domainSuffix/api", t) }
}.stateIn(scope, SharingStarted.Eagerly, null)
Expand All @@ -32,4 +34,4 @@ object RWS: KoinComponent {
get() = authClientFlow.value
val timelineClient: TimelineClient?
get() = timelineClientFlow.value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.rebble.cobble.shared.domain.store

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class JsFrame<T>(
val methodName: String,
val callbackId: Int,
val data: T,
)

@Serializable
data class LoadAppToDeviceAndLocker(
val id: String,
val uuid: String,
val title: String,
@SerialName("list_image")
val listImage: String,
@SerialName("icon_image")
val iconImage: String,
@SerialName("screenshot_image")
val screenshotImage: String,
val type: String,
@SerialName("pbw_file")
val pbwFile: String,
val links: AppLinks,
)

@Serializable
data class AppLinks(
val add: String,
val remove: String,
val share: String,
@SerialName("add_flag")
val addFlag: String? = null,
@SerialName("add_heart")
val addHeart: String? = null,
@SerialName("remove_flag")
val removeFlag: String? = null,
@SerialName("remove_heart")
val removeHeart: String? = null,
)

@Serializable
data class SetNavBarTitle(
val title: String,
val browserTitle: String? = null,
@SerialName("show_search")
val showSearch: Boolean = true,
@SerialName("show_share")
val showShare: Boolean? = null,
)

@Serializable
data class OpenURL(
val url: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.rebble.cobble.shared.domain.store

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

class PebbleBridge {
companion object {
internal val json =
Json {
ignoreUnknownKeys = true
encodeDefaults = true
}

internal val className = "PebbleBridge"

fun searchRequest(
query: String,
section: String,
): String {
val request = PebbleBridgeSearchRequest(query = query, section = section)
return createRequest(request)
}

fun navigateRequest(url: String): String {
val request = PebbleBridgeNavigateRequest(url = url)
return createRequest(request)
}

fun refreshRequest(): String {
val request = PebbleBridgeRefreshRequest()
return createRequest(request)
}

fun lockerResponse(
callbackId: Int,
addedToLocker: Boolean,
): String {
val response = PebbleBridgeLockerResponse(addedToLocker)
return createResponse(callbackId, response)
}

private inline fun <reified T> createRequest(request: T): String {
val requestJson = json.encodeToString(request)
return "$className.handleRequest($requestJson)"
}

private inline fun <reified T> createResponse(
callbackId: Int,
response: T,
): String {
val responseData = PebbleBridgeResponse(callbackId, response)
val responseJson = json.encodeToString(responseData)
return "$className.handleResponse($responseJson)"
}
}
}

@Serializable
data class PebbleBridgeResponse<T>(
val callbackId: Int,
val data: T,
)

@Serializable
data class PebbleBridgeLockerResponse(
@SerialName("added_to_locker")
val addedToLocker: Boolean,
)

@Serializable
data class PebbleBridgeSearchRequest(
val methodName: String = "search",
val query: String,
val section: String,
)

@Serializable
data class PebbleBridgeNavigateRequest(
val methodName: String = "navigate",
val url: String,
)

@Serializable
data class PebbleBridgeRefreshRequest(
val methodName: String = "refresh",
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ object Routes {
object Home {
const val LOCKER_APPS = "locker_apps"
const val LOCKER_WATCHFACES = "locker_watchfaces"
const val STORE_APPS = "store_apps"
const val STORE_WATCHFACES = "store_watchfaces"
const val TEST_PAGE = "test_page"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.rebble.cobble.shared.ui.view.dialogs.AppInstallDialog
import io.rebble.cobble.shared.ui.view.home.HomePage
import io.rebble.cobble.shared.ui.view.home.HomeScaffold
import io.rebble.cobble.shared.ui.view.home.locker.LockerTabs
import io.rebble.cobble.shared.ui.view.home.store.StoreTabs
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.Json.Default.decodeFromString
import org.koin.compose.KoinContext
Expand All @@ -43,6 +44,12 @@ fun MainView(navController: NavHostController = rememberNavController()) {
composable(Routes.Home.LOCKER_APPS) {
HomeScaffold(HomePage.Locker(LockerTabs.Apps), onNavChange = navController::navigate)
}
composable(Routes.Home.STORE_WATCHFACES) {
HomeScaffold(HomePage.Store(StoreTabs.Watchfaces), onNavChange = navController::navigate)
}
composable(Routes.Home.STORE_APPS) {
HomeScaffold(HomePage.Store(StoreTabs.Apps), onNavChange = navController::navigate)
}
composable(Routes.Home.TEST_PAGE) {
HomeScaffold(HomePage.TestPage, onNavChange = navController::navigate)
}
Expand All @@ -60,4 +67,4 @@ fun MainView(navController: NavHostController = rememberNavController()) {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ import io.rebble.cobble.shared.ui.common.RebbleIcons
import io.rebble.cobble.shared.ui.nav.Routes
import io.rebble.cobble.shared.ui.view.home.locker.Locker
import io.rebble.cobble.shared.ui.view.home.locker.LockerTabs
import io.rebble.cobble.shared.ui.view.home.store.Store
import io.rebble.cobble.shared.ui.view.home.store.StoreTabs
import kotlinx.coroutines.launch

open class HomePage {
class Locker(val tab: LockerTabs) : HomePage()
class Store(val tab: StoreTabs) : HomePage()
object TestPage : HomePage()
}

@Composable
fun HomeScaffold(page: HomePage, onNavChange: (String) -> Unit) {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
val searchingState = remember { mutableStateOf(false) }
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
/*topBar = {
Expand All @@ -51,6 +53,12 @@ fun HomeScaffold(page: HomePage, onNavChange: (String) -> Unit) {
icon = { RebbleIcons.locker() },
label = { Text("Locker") }
)
NavigationBarItem(
selected = page is HomePage.Store,
onClick = { onNavChange(Routes.Home.STORE_WATCHFACES) },
icon = { RebbleIcons.rebbleStore() },
label = { Text("Store") }
)
}
},
floatingActionButton = {
Expand All @@ -60,10 +68,14 @@ fun HomeScaffold(page: HomePage, onNavChange: (String) -> Unit) {
modifier = Modifier
.padding(16.dp),
onClick = {
searchingState.value = true
if (page.tab == LockerTabs.Watchfaces) {
onNavChange(Routes.Home.STORE_WATCHFACES)
} else {
onNavChange(Routes.Home.STORE_APPS)
}
},
content = {
RebbleIcons.search()
RebbleIcons.plusAdd()
},
)
}
Expand All @@ -73,7 +85,12 @@ fun HomeScaffold(page: HomePage, onNavChange: (String) -> Unit) {
Box(modifier = Modifier.padding(innerPadding)) {
when (page) {
is HomePage.Locker -> {
Locker(searchingState, page.tab, onTabChanged = {
Locker(page.tab, onTabChanged = {
onNavChange(it.navRoute)
})
}
is HomePage.Store -> {
Store(page.tab, onTabChanged = {
onNavChange(it.navRoute)
})
}
Expand All @@ -87,4 +104,4 @@ fun HomeScaffold(page: HomePage, onNavChange: (String) -> Unit) {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ enum class LockerTabs(val label: String, val navRoute: String) {
}

@Composable
fun Locker(searchingState: MutableState<Boolean>, page: LockerTabs, lockerDao: LockerDao = getKoin().get(), viewModel: LockerViewModel = viewModel { LockerViewModel(lockerDao) }, onTabChanged: (LockerTabs) -> Unit) {
fun Locker(page: LockerTabs, lockerDao: LockerDao = getKoin().get(), viewModel: LockerViewModel = viewModel { LockerViewModel(lockerDao) }, onTabChanged: (LockerTabs) -> Unit) {
val entriesState: LockerViewModel.LockerEntriesState by viewModel.entriesState.collectAsState()
val modalSheetState by viewModel.modalSheetState.collectAsState()
val watchIsConnected by viewModel.watchIsConnected.collectAsState()
val searchQuery: String? by viewModel.searchQuery.collectAsState()
val focusRequester = remember { FocusRequester() }
val searchingState = remember { mutableStateOf(false) }
val (searching, setSearching) = searchingState

Column {
Expand Down Expand Up @@ -95,4 +96,4 @@ fun Locker(searchingState: MutableState<Boolean>, page: LockerTabs, lockerDao: L
val sheetViewModel = (modalSheetState as LockerViewModel.ModalSheetState.Open).viewModel
LockerItemSheet(onDismissRequest = { viewModel.closeModalSheet() }, watchIsConnected = watchIsConnected, viewModel = sheetViewModel)
}
}
}
Loading
Loading